Have you ever wondered what the design pattern is? With this article you can see why design patterns are so important, and some examples of Python show why design patterns are needed and how to use them.
What is the design pattern?
Design patterns are summarized, optimized, and reusable solutions to some of the programming problems we often encounter. A design pattern is not like a class or a library that can directly act on our code. Conversely, a more advanced design pattern is a method template that must be implemented in a particular situation. Design patterns do not bind to specific programming languages. A good design pattern should be able to be implemented in most programming languages (if not all, depending on the language feature). Most importantly, the design pattern is also a double-edged sword, if the design pattern is used in inappropriate circumstances will cause disaster, and thus bring endless trouble. However, if the design pattern is used in the right place at the right time, it will be your savior.
At first, you would think that the "pattern" was a sensible move to solve a particular kind of problem. Yes, it does look like one of the most versatile and flexible solutions to work with a lot of people and look at problems from a different perspective. Maybe you've seen or solved these problems, but your solution is probably not as complete as the pattern.
Although called "Design patterns", they are not closely related to the "Design" field. Design patterns are different from the traditional analysis, design and implementation, in fact the design pattern will be a complete idea rooted in the program, so it may appear in the analysis phase or higher level of the design phase. Interestingly, because the design pattern is specific to the program code, it may make you think that it will not appear before the implementation phase (in fact you are unaware that you are using a specific design pattern before entering the implementation phase).
Patterns can be understood through the basic concepts of programming: adding an abstraction layer. Abstract one thing is to isolate any specific detail, and this is done to separate the unchanging core from other details. When you find that some parts of your program are often changed for some reason, and you don't want the parts of the changes to trigger changes in other parts, you need to think about the design methods that don't change. Doing so will not only make the code more maintainable, but will make the code easier to understand, reducing development costs.
Designing an elegant, easy-to-maintain program the difficulty lies in discovering what I call the "vector of Change" (Here, "vector" refers to the maximum gradient direction (maximum gradient), not to a container class). It means finding the most important part of the change in the system, or in other words, finding out where the biggest cost of impacting the system is. Once you have found the vector of change, you can design your program around this focus.
So the purpose of the design pattern is to separate the mutable parts of the code. If you look at the problem this way, you'll see multiple design patterns right away. For example, object-oriented inheritance (inheritance) can be seen as a design pattern (albeit implemented by the compiler). It allows the same interface (unchanged parts) to behave differently (changing parts). A combination can also be thought of as a design pattern, because it allows you to change the object of the implementation class and their behavior in a dynamic or static manner.
Another common design pattern example is an iterator. Iterators have existed since the beginning of Python, and have been explicitly used as a feature in the Python2.2 version. An iterator hides the specific implementation inside the container, providing a way to access each element within the container object in turn. So, you can use common code to manipulate each element of a sequence without having to ignore how the sequence is built. So your code can work on any object that can produce an iterator.
Here are three of the most basic design patterns:
- Structured patterns, which are typically used to deal with the relationships between entities, enable these entities to work better together.
- Create a pattern that provides an instantiated method that provides the appropriate object creation method for the appropriate condition.
- Behavior patterns, which are used to communicate in different entities, provide an easier and more flexible means of communication for communication between entities.
Why should we use design patterns?
Theoretically speaking, the design pattern is a better solution to the problem of the program. Countless programmers have encountered these problems, and they use these solutions to deal with these problems. So when you have the same problem, why do you want to think about creating a solution instead of being ready and proving effective?
Example
Suppose you now have a task that requires you to find a valid way to merge two classes that do different things, and in the existing system these two classes are used extensively in many different places, so removing the two classes or altering the existing code is extremely difficult. Not only that, changing existing code can result in a lot of testing, because in a system that relies on a large number of different components, these changes always introduce some new errors. To avoid these problems, you can implement a variant of the strategy pattern (strategy pattern) and the adapter pattern (Adapter pattern), which can handle this problem well.
Class Strategyandadapterexampleclass (): def __init__ (self, context, Class_one, class_two): self.context = Context Self.class_one = class_one self.class_two = Class_two def operation1 (self): if Self.context = = "Context_for_class_one": self.class_one.operation1_in_class_one_context () else: Self.class_ Two.operational_in_class_two_context ()
It's simple, isn't it? Now let's take a closer look at the strategy model.
Policy mode
A policy pattern is a behavior-related design pattern that allows you to determine the action of a program at run time, based on a specified context. You can encapsulate different algorithms in two classes and determine exactly which policy to execute when the program runs.
In the above example, the policy is determined based on the value of the context variable at the time of instantiation. If the value of the given context variable is "Class_one", the Class_one will be executed, otherwise class_two will be executed.
Where do I use it?
Suppose you are now writing a class that can update or create a new user record, receive the same input parameters (such as name, address, phone number, etc.), but invoke the corresponding update or creation method depending on the situation. Of course, you might be able to handle this problem with a if-else, but what if you need to use this class in a different place? Then you have to constantly rewrite if-else judgment. Why not simply address the problem by specifying the context.
Class User (): def create_or_update (self, name, address, Mobile, Userid=none): if UserID: # It means the User D OESN ' t exist yet, create a new record else: # It means the user already exists, just update based on the given use Rid
The general policy pattern involves encapsulating the algorithm into another class, but if so, that class is too wasteful. Remember not to rote the template, to grasp the core concept of flexible flexibility, the most important is to solve the problem.
Adapter mode
The adapter pattern is a structural design pattern that allows for a new use of a class through different interfaces, which enables systems with different invocation methods to use this class.
It also allows you to change the input parameters received through the client class to accommodate the relevant functions of the appropriate ligand.
How to use?
Another place to use the adapter class is the wrapper (wrapper), which allows you to wrap an action into a class that can then be reused in the appropriate situation. A typical example is when you create a domain class for a table class, you can encapsulate all of the same actions that correspond to different tables into an adapter class instead of calling these different actions one after the other. This not only allows you to reuse all the actions you want, but you don't have to rewrite the code when you use the same action in different places.
Compare these two implementations:
Scenarios with no adapters
Class User (object): def create_or_update (self): Pass Class Profile (object): def create_or_update (self): Pass user = User () user.create_or_update () Profile = profile () profile.create_or_update ()
If we need to do the same thing in different places or reuse the code in different projects, then we need to knock it again.
Solutions for using wrapper classes
Let's see what we can do to counter the word:
Account_domain = account () Account_domain. NewAccount ()
In this case, we implement the account domain class through a wrapper class:
Class User (object): def create_or_update (self): Pass Class Profile (object): def create_or_update (self): Pass Class account (): def new_account (self): user = user () user.create_or_update () Profile = Profile () profile.create_or_update ()
That way, you can use account domain when you need it, or you could wrap other classes under the domain class.
Factory mode
Factory mode is a type of design pattern that acts as its name: This is a class that produces object instances like a factory.
The primary purpose of this pattern is to encapsulate object creation processes that may involve many classes into a single method. Outputs the specified object instance by the given context.
When do I use it?
The best time to use Factory mode is when you need to use multiple variants of a single entity. For example, you have a button class that has a variety of variants, such as a button, an input box button, or a Flash button. Then you will need to create different buttons on different occasions, so you can create different buttons from a factory.
Let's start by creating three classes:
Class Button (object): html = "" def get_html (self): return self.html class Image (button): html = "" Class Input (Button): HTML = ""Class Flash (Button): html =" "
Then create our factory class:
Class Buttonfactory (): def create_button (self, Typ): Targetclass = typ.capitalize () return globals () [ Targetclass] ()
Globals () will return all global variables in a dictionary, so Targetclass = Typ.capitalize () will get the class name (Image, input, or flash) through the incoming Typ string, and Globals () [ Targetclass] will take the class name to the class (see Meta Class), and Globals () [Targetclass] () will create an object of this class.
We can use factory classes like this:
Button_obj = buttonfactory () button = [' Image ', ' input ', ' Flash ']for b in button: print Button_obj.create_button (b). Get_html ()
The output will be the HTML attribute for all button types. This way you can specify different types of buttons depending on the situation and are easy to reuse.
Adorner mode
Adorner mode is a structural pattern that allows us to add new or additional behavior to an object at run time, depending on the situation.
The purpose is to apply extended function methods to a particular object instance, and also to produce an original object without a new method. It allows multiple adorners to be combined with one instance, so you won't have instances bundled with a single adorner. This pattern is an optional way to implement subclass inheritance, which refers to the integration of the corresponding functionality from the parent class. Unlike subclass inheritance, which must be added at compile time, the adorner allows you to add new behavior as needed at run time.
You can implement the adorner mode according to the following steps:
- Creates an adorner class with the original component class as the base class.
- Add a pointer field to a component class in the adorner class
- Passing a component to the constructor of the adorner class to initialize the component class pointer
- In the adorner class, all the component methods are pointed to the component class pointers, and
- In the adorner class, override each component method that needs to modify the functionality.
Related Wikipedia (Http://en.wikipedia.org/wiki/Decorator_pattern)
When do I use it?
The best time to use adorner mode is when you have an entity that needs to add new behavior as needed. Suppose you have an HTML link element, a logout link, and you want to make minor changes to the specific behavior based on the current page. In this case, we can use the adorner mode.
First, build the decorative pattern we need.
If we are on the homepage and are already logged in, then the logout link is tagged with the H2 tag.
If we are on a different page and have logged in, then mark the link with an underscore tag
If you are logged in, use the bold tag link.
Once the decoration mode is established, we can start the work.
Class Htmllinks (): Def set_html (self, html): self.html = html def get_html (self): return self.html def render (s ELF): Print (Self.html) class Logoutlink (htmllinks): def __init__ (self): self.html = "Logout" Class Logoutlinkh2de Corator (htmllinks): Def __init__ (self, logout_link): Self.logout_link = Logout_link self.set_html ("{0}". Format (SE Lf.logout_link.get_html ()) def call (self, name, args): Self.logout_link.name (Args[0]) class Logoutlinkunderlinedecor Ator (htmllinks): Def __init__ (self, logout_link): Self.logout_link = Logout_link self.set_html ("{0}". Format (self). Logout_link.get_html ()) def call (self, name, args): Self.logout_link.name (Args[0]) class Logoutlinkstrongdecorator (H Tmllinks): Def __init__ (self, logout_link): Self.logout_link = Logout_link self.set_html ("{0}". Format (self.logout_link.get_html ())) def call (self, name, args): Self.logout_link.name (args[0]) Logout_link = Logou Tlink () is_logged_in = 0in_home_page = 0 if Is_logged_in:logout_link = Logoutlinkstrongdecorator (logout_link) if in_home_ Page:logout_link = Logoutlinkh2decorator (logout_link) Else:logout_link = Logoutlinkunderlinedecorator (Logout_link) Logout_link.render ()
Single-Case mode
Singleton mode is a created design pattern that ensures that the runtime only has a single instance object for a class and provides a global access point to access the instance object.
Because this globally unique access point "coordinates" the access request for the singleton object for other objects that invoke the singleton, the singleton variables that the callers see will be the same copy.
When can I use it?
The singleton pattern is probably the simplest design pattern, and it provides a unique object of a specific type. In order to achieve this goal, you must control the generation of objects outside of the program. A convenient way is to use a single object of a private inner class as a singleton object.
Class Onlyone: class __onlyone: def __init__ (self, arg): self.val = arg def __str__ (self): return Repr (self) + self.val instance = None def __init__ (self, arg): if not onlyone.instance: Onlyone.instance = Onlyone.__onlyone (ARG) else: OnlyOne.instance.val = arg def __getattr__ (self, name) : return GetAttr (self.instance, name) x = Onlyone (' sausage ') print (x) y = onlyone (' eggs ') print (y) z = onlyone (' spam ') Print (z) print (x) print (y) print (' x ') print (' Y ') print (' z ') output = "' <__main__.__onlyone instance at 0076b7ac> Sausage<__main__.__onlyone instance at 0076b7ac>eggs<__main__.__onlyone instance at 0076b7ac>spam<__ Main__.__onlyone instance at 0076b7ac>spam<__main__.__onlyone instance at 0076b7ac>spam<__main__. Onlyone instance at 0076c54c><__main__. Onlyone instance at 0076daac><__main__. Onlyone instance at 0076aa3c> "
Because the built-in class is named with a double underscore, it is private and cannot be accessed directly by the user. The built-in class contains all the methods you want to put in the normal class, and controls its creation through the constructor of the outer wrapper class. When you first create a onlyone, initialize an instance object, and later ignore the request to create a new instance.
Access by proxy, using the __getattr__ () method to point all calls to a single instance. You can see from the output that although it looks like you have created multiple objects (Onlyone), there is only one __onlyone object. Although there are multiple onlyone instances, they are the only proxies for the __onlyone object.
Note that the method above does not limit you to creating only one object, which is also a technique for creating a pool of objects of limited use. In that case, however, you may encounter problems with objects in the shared pool. If this is really a problem, you can solve this problem by designing the "check-in" and moving Out "check-out" mechanisms for shared objects.
Summarize
In this article, I've only listed several design patterns that I think are very important in programming, and there are many design patterns that need to be learned. If you are interested in other design patterns, the Wikipedia design pattern section can provide a lot of information. If not enough, you can look at gang of four's "design pattern: The basis of reusable object-oriented software," a book about design patterns.
One last thing: When using design mode, make sure that you are used to solve the problem correctly. As I mentioned before, the design pattern is a double-edged sword: If used improperly, they create potential problems, and if used properly, they will be indispensable.