Classification of design patterns
Generally speaking, design patterns are divided into three categories:
There are five kinds of creation mode: factory method mode, abstract factory mode, singleton mode, builder mode and prototype mode.
There are seven structural modes: adapter mode, decorator mode, agent mode, appearance mode, bridge mode, combination mode and sharing mode.
There are eleven behavioral modes: strategy mode, template method mode, observer mode, iteration sub mode, responsibility chain mode, command mode, memo mode, state mode, visitor mode, mediator mode and interpreter mode.
A. Creation mode (5)
Factory method pattern, abstract factory pattern, singleton pattern, builder pattern, prototype pattern.
1 factory mode
1.1 simple factory mode
Definition: defines a class for creating objects, which encapsulates the behavior of instantiating objects.
Example: (let's take an example of pizza factory)
Pizza factory produces three types of pizza: chess, pepper and great. These three types of objects are instantiated through the factory class (SimplePizzaFactory). The class diagram is as follows:
Factory class code:
public class SimplePizzaFactory { public Pizza CreatePizza(String ordertype) { Pizza pizza = null; if (ordertype. equals( "cheese")) { pizza = new CheesePizza(); } else if (ordertype. equals( "greek")) { pizza = new GreekPizza(); } else if (ordertype. equals( "pepper")) { pizza = new PepperPizza(); } return pizza; } }
Problems and solutions of simple factory: one problem of simple factory mode is that the creation of class depends on factory class, that is, if you want to expand the program, you must modify the factory class, which violates the opening and closing principle. Therefore, from the perspective of design, how to solve some problems? We can define an abstract method for creating objects and create multiple different factory classes to implement the abstract method. In this way, once we need to add new functions, we can directly add new factory classes without modifying the previous code. This method is the factory method mode we will talk about next.
1.2 factory method mode
Definition: defines an abstract method to create an object, and the subclass determines the class to be instantiated. The factory method pattern defers instantiation of objects to subclasses.
For example: (we still give the example of pizza factory, but in this example, there are two places of origin of pizza: London and New York). A new origin is added. If we use the simple factory mode, we need to modify the factory code and add a bunch of if else statements. The factory method mode overcomes the disadvantage that a simple factory needs to modify the code. It will directly create two factories, New York factory and London factory. The class diagram is as follows:
There is an abstract method in OrderPizza:
abstract Pizza createPizza();
Two factory classes inherit OrderPizza and implement abstract methods:
public class LDOrderPizza extends OrderPizza { Pizza createPizza( String ordertype) { Pizza pizza = null; if (ordertype.equals( "cheese")) { pizza = new LDCheesePizza(); } else if (ordertype.equals( "pepper")) { pizza = new LDPepperPizza(); } return pizza; } } public class NYOrderPizza extends OrderPizza { Pizza createPizza( String ordertype) { Pizza pizza = null; if (ordertype.equals( "cheese")) { pizza = new NYCheesePizza(); } else if (ordertype.equals( "pepper")) { pizza = new NYPepperPizza(); } return pizza; } }
. different instantiated objects will be obtained through different factories. The code of PizzaStroe is as follows:
public class PizzaStroe { public static void main(String[] args) { OrderPizza mOrderPizza; mOrderPizza = new NYOrderPizza(); } }
Solved the problem of simple factory mode: add a new pizza origin (Beijing), just add a BJOrderPizza class:
public class BJOrderPizza extends OrderPizza { Pizza createPizza( String ordertype) { Pizza pizza = null; if (ordertype.equals( "cheese")) { pizza = new LDCheesePizza(); } else if (ordertype.equals( "pepper")) { pizza = new LDPepperPizza(); } return pizza; } }
In fact, the advantage of this mode is that if you want to add a function now, you only need to make an implementation class, and there is no need to change the ready-made code. In this way, the expansibility is better!
Problems and solutions of factory method: the client needs to create a specific instance of the class. To put it simply, if the user wants to order pizza from the New York factory, he must go to the New York factory. If he wants to order pizza from the London factory, he must go to the London factory. When the factories in London and New York change, the users will also change, which undoubtedly increases the complexity of users' operation. In order to solve this problem, we can abstract the factory class as an interface. Users only need to find the default factory and put forward their own requirements (pass in parameters), so they can get the products they want, instead of finding different factories according to the products, which is convenient for users to operate. This is the abstract factory pattern we will talk about next.
1.3 abstract factory mode
Definition: defines an interface for creating related or dependent object families without specifying specific classes.
For example: (we still give the example of pizza factory. There are two pizza factories: New York factory and London factory). The class diagram is as follows:
Factory interface:
public interface AbsFactory { Pizza CreatePizza( String ordertype) ; }
Realization of the plant:
public class LDFactory implements AbsFactory { @Override public Pizza CreatePizza(String ordertype) { Pizza pizza = null; if ( "cheese".equals(ordertype)) { pizza = new LDCheesePizza(); } else if ( "pepper".equals(ordertype)) { pizza = new LDPepperPizza(); } return pizza; } }
The code of PizzaStroe is as follows:
public class PizzaStroe { public static void main(String[] args) { OrderPizza mOrderPizza; mOrderPizza = new OrderPizza( "London"); } }
It solves the problem of factory method pattern: in the abstract factory, PizzaStroe can instantiate objects only by passing in parameters.
1.4 applicable occasions of factory mode
A large number of products need to be created, and these products have a common interface.
1.5 # selection of three factory modes
Simple factory: used to produce any product in the same hierarchical structure. (expansion and addition of products are not supported)
Factory method: used to produce fixed products in the same grade structure. (support the expansion and addition of products)
Abstract factory: used to produce all products of different product families. (support the expansion and addition of products; support the addition of product families)
Application of simple factory: only London factory (only this grade), and this factory produces only three types of pizza: chess, pepper and green (fixed products).
Applicable occasions of factory method: there are not only London factories, but also New York factories (still with the same hierarchical structure, but supporting product expansion). These two factories still produce only three types of pizza: cheese, pepper and green (fixed products).
Application of abstract factory: not only the New York factory is added (it is still the same hierarchical structure, but supports the expansion of products), but also a new type of pizza: chinese pizza is added to the two factories.
Therefore, the abstract factory is like a factory, and the factory method is like a product production line of a factory. Therefore, we can create factories with abstract factory pattern and production lines with factory method pattern. For example, we can use the abstract factory pattern to create factories in London and New York, and use the factory method to realize the production of cheese pizza and great pizza. Class diagram is as follows:
Summarize the three modes:
The simple factory pattern is to create a class of instantiated objects in which multiple objects are instantiated. The factory method pattern defines an abstract method to create an object, and the subclass determines the class to be instantiated. The advantage of this is that there are new types of objects that need to be instantiated. Just add subclasses. The abstract factory pattern defines an interface for creating an object family without specifying a specific class. The abstract factory also gives the instantiation of objects to subclasses, that is, it supports extension. At the same time, it provides the client interface to avoid the user directly operating the subclass factory.
2. Singleton mode
Definition: ensure that there is at most one instance of a class and provide a global access point
Singleton mode can be divided into two types: preload and lazy load
2.1 preloading
As the name suggests, it is preloaded. A further explanation is that the singleton object has not been used, but the singleton object has been loaded into memory.
public class PreloadSingleton { public static PreloadSingleton instance = new PreloadSingleton(); //Other classes cannot instantiate objects of singleton classes private PreloadSingleton() { }; public static PreloadSingleton getInstance() { return instance; } }
Obviously, without using the singleton object, the object is loaded into memory, which will cause a waste of memory.
2.2 lazy loading
In order to avoid the waste of memory, we can use lazy loading, that is, we can create the singleton object when it is used.
public class Singleton { private static Singleton instance= null; private Singleton(){ }; public static Singleton getInstance() { if(instance== null) { instance= new Singleton(); } return instance; } }
2.3 singleton mode and thread safety
(1) Preloading has only one statement return instance, which obviously ensures thread safety. However, we know that preloading can cause a waste of memory.
(2) Lazy loading does not waste memory, but it cannot ensure the safety of threads. First, if judgment and its memory execution code are non atomic. Secondly, new Singleton() cannot guarantee the order of execution.
If atomicity or sequencing is not satisfied, threads are definitely unsafe. This is basic common sense and will not be repeated. I'll mainly talk about why new Singleton() can't guarantee sequencing. We know that there are three steps to create an object:
memory=allocate() ;//1: Initialize memory space ctorInstance( memory) ;//2: Initialize object instance=memory() ;//3: Set instance to point to the memory address just allocated
In order to improve the execution performance of the program, the jvm will reorder the code without dependencies. The above 2 and 3 lines of code may be reordered. We use two threads to illustrate that threads are unsafe. Both thread a and thread b create objects. Among them, the reordering of A2 and A3 will cause thread B to judge that instance is not empty at B1, and thread B will then access the object referenced by instance. At this point, thread B will access an uninitialized object (thread unsafe).
2.4 ensure thread safety of lazy loading
Our first thought is to use the synchronized keyword. Synchronized loading on getInstance() function does ensure thread safety. However, if the getInstance () method is called frequently, the thread will wake up and block whether the instance is initialized or not. In order to avoid the context switching of threads consuming a lot of time, if the object has been instantiated, we don't need to use synchronized locking to return the object directly.
public class Singleton { private static Singleton instance = null; private Singleton() { }; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
We add synchronized to the if(instance==null) judgment statement to ensure that the lock is added only when the instance is not instantiated
public class Singleton { private static Singleton instance = null; private Singleton() { }; public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
After the discussion in 2.3, we know that the code of a new object cannot guarantee the order. Therefore, we need to use another keyword volatile to ensure the order of the object instantiation process.
public class Singleton { private static volatile Singleton instance = null; private Singleton() { }; public static synchronized Singleton getInstance() { if (instance == null) { synchronized (instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
At this point, we ensure the thread safety of lazy loading.
3 generator mode
Definition: encapsulates a complex object construction process and allows step-by-step construction.
Definition and explanation: we can understand the generator pattern as if we have an object to be built, which is composed of multiple components. The establishment of each Component is complex, but it is very simple to use components to build the required objects, so we can separate the steps of building complex components from using components to build objects, Using builder mode, you can build.
3.1 structure and code examples of patterns
There are four roles in the generator schema structure:
(1) Product: a complex object to be constructed by a specific producer;
(2) Abstract generator: the abstract generator is an interface that defines several methods for each component that creates a Product object, and also defines the method to return the Product object (define the construction steps);
(3) Concrete Builder: the class that implements the builder interface. The concrete builder will implement the methods defined by the builder interface (produce various components);
(4) Director: a director is a class that needs to contain variables declared by the Builder interface. The commander's responsibility is to provide the user with a specific generator, that is, the commander will request the specific generator class to construct the Product object required by the user. If the requested specific generator successfully constructs the Product object, the commander can let the specific generator return the constructed Product object. (assemble the parts according to the steps and return to the Product)
For example (if we build a computer, we may need the following steps: (1) a host (2) a monitor (3) a keyboard (4) a mouse)
Although when we build a host, the actual steps of each object are different. For example, some objects build i7cpu hosts, some objects build i5cpu hosts, some objects build ordinary keyboards, and some objects build mechanical keyboards. But in any case, you always need to go through a step, that is to build a host and a keyboard. For this example, we can use the generator mode to generate a computer, which needs to be generated in multiple steps. The class diagram is as follows:
Construction steps of ComputerBuilder class definition:
public abstract class ComputerBuilder { protected Computer computer; public Computer getComputer() { return computer; } public void buildComputer() { computer = new Computer(); System. out.println( "Generated a computer!!!"); } public abstract void buildMaster(); public abstract void buildScreen(); public abstract void buildKeyboard(); public abstract void buildMouse(); public abstract void buildAudio(); }
HPComputerBuilder defines various components:
public class HPComputerBuilder extends ComputerBuilder { @Override public void buildMaster() { // TODO Auto-generated method stub computer.setMaster( "i7,16g,512SSD,1060"); System.out.println( "(i7,16g,512SSD,1060)HP host"); } @Override public void buildScreen() { // TODO Auto-generated method stub computer.setScreen( "1080p"); System.out.println( "(1080p)HP display"); } @Override public void buildKeyboard() { // TODO Auto-generated method stub computer.setKeyboard( "cherry Green axis mechanical keyboard"); System.out.println( "(cherry Green axis mechanical keyboard)Keyboard for"); } @Override public void buildMouse() { // TODO Auto-generated method stub computer.setMouse( "MI mouse"); System.out.println( "(MI mouse)Mouse"); } @Override public void buildAudio() { // TODO Auto-generated method stub computer.setAudio( "Philips audio"); System.out.println( "(Philips audio)Sound"); } }
The Director class assembles components and generates products
public class Director { private ComputerBuilder computerBuilder; public void setComputerBuilder(ComputerBuilder computerBuilder) { this.computerBuilder = computerBuilder; } public Computer getComputer() { return computerBuilder.getComputer(); } public void constructComputer() { computerBuilder.buildComputer(); computerBuilder.buildMaster(); computerBuilder.buildScreen(); computerBuilder.buildKeyboard(); computerBuilder.buildMouse(); computerBuilder.buildAudio(); } }
3.2 advantages and disadvantages of generator mode
advantage
-
Decompose an object into components
-
Encapsulate the structure of object components
-
You can control the generation process of the whole object
shortcoming
-
For different types of objects, you need to implement different concrete constructor classes, which may greatly increase the number of classes
3.3 difference between generator mode and factory mode
When building objects with generator mode, the process of building objects usually requires multiple steps, such as the host, display screen, mouse and so on in our example. The function of generator mode is to encapsulate these complex construction processes. When building objects in factory mode, there is usually only one step. Calling a factory method can generate an object.
4 prototype mode
Definition: create a new instance by copying an existing instance without knowing the information of the corresponding class.
Simply understand that when we need to create a specified object, we happen to have such an object, but we can't use it directly. I will clone a new object like a feather to use; Basically, this is the prototype pattern. Keyword: clone.
4.1 deep and light copies
Shallow copy: after copying an object, the variables of the basic data type will be recreated, and the reference type still points to the original object.
Deep copy: after copying an object, both basic data types and reference types are recreated. In short, deep replication is a complete and complete replication, while shallow replication is not complete. Clone is obviously a deep copy, and the objects produced by clone cannot affect the prototype objects
4.2 structure and code example of prototype pattern
Client: user
Prototype: interface (abstract class), which declares that it has clone capability, such as clonable interface in java
ConcretePrototype: concrete prototype class
It can be seen that the design pattern is still relatively simple, focusing on the Prototype interface and the implementation class ConcretePrototype of the Prototype interface. Concrete implementation of Prototype pattern: a Prototype class only needs to implement the clonable interface and override the clone method. Here, the clone method can be changed to any name, because the clonable interface is an empty interface. You can arbitrarily define the method name of the implementation class, such as cloneA or cloneB, because the focus here is super Clone(), super Clone() calls the clone() method of Object.
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super. clone(); return proto; } }
For example (the bank sends a lot of emails, and the time comparison between using clone and not using clone): we simulate that it takes a long time to create an object. Therefore, in the constructor, we let the current thread sleep for a while
public Mail(EventTemplate et) { this.tail = et.geteventContent(); this.subject = et.geteventSubject(); try { Thread.sleep( 1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
Send ten emails without clone
public static void main(String[] args) { int i = 0; int MAX_COUNT = 10; EventTemplate et = new EventTemplate( "9 Monthly credit card bill", "National Day lucky draw..."); long start = System.currentTimeMillis(); while (i < MAX_COUNT) { // Here are the different parts of each email Mail mail = new Mail(et); mail.setContent(getRandString( 5) + ",Mr. (Ms.):Your credit card bill..." + mail.getTail()); mail.setReceiver(getRandString( 5) + "@" + getRandString( 8) + ".com"); // Then send an email sendMail(mail); i++; } long end = System.currentTimeMillis(); System. out.println( "Time use:" + (end - start)); }
Time: 10001
Using clone, send ten emails
public static void main(String[] args) { int i = 0; int MAX_COUNT = 10; EventTemplate et = new EventTemplate( "9 Monthly credit card bill", "National Day lucky draw..."); long start=System.currentTimeMillis(); Mail mail = new Mail(et); while (i < MAX_COUNT) { Mail cloneMail = mail.clone(); mail.setContent(getRandString( 5) + ",Mr. (Ms.):Your credit card bill..." + mail.getTail()); mail.setReceiver(getRandString( 5) + "@" + getRandString( 8) + ".com"); sendMail(cloneMail); i++; } long end=System.currentTimeMillis(); System. out.println( "Time use:"+(end-start)); }
Time: 1001
4.3 summary
The essence of prototype pattern is clone, which can solve the problem of resource consumption in building complex objects and improve the efficiency of building objects in some scenes; Another important use is protective copy, which can realize read-only restriction by returning a copy object.
B. Structure mode (7 kinds)
Adapter mode, decorator mode, agent mode, appearance mode, bridge mode, combination mode and sharing mode.
5 adapter mode
Definition: the adapter pattern converts the interface of a class into another interface representation expected by the client. The purpose is to eliminate the compatibility problems of classes caused by interface mismatch.
It is mainly divided into three categories: class adapter mode, object adapter mode and interface adapter mode.
Type 5.1 adapter mode
The adaptation is realized by multiple inheritance of the target interface and the Adaptee class
For example (converting USB interface to VGA interface), the class diagram is as follows:
USBImpl Code:
public class USBImpl implements USB{ @Override public void showPPT() { // TODO Auto-generated method stub System.out.println( "PPT Content presentation"); } }
AdatperUSB2VGA first inherits the function of USBImpl to obtain USB, and then implements VGA interface, indicating that the type of this class is VGA.
public class AdapterUSB2VGA extends USBImpl implements VGA { @Override public void projection() { super.showPPT(); } }
The Projector maps USB to VGA. Only the VGA interface can be connected to the Projector for projection
public class Projector< T> { public void projection(T t) { if (t instanceof VGA) { System. out.println( "Start projection"); VGA v = new VGAImpl(); v = (VGA) t; v.projection(); } else { System. out.println( "Interface mismatch, unable to project"); } } }
test code
@Test public void test2(){ //Create a VGA object through the adapter, which actually uses the showPPT () method of USB VGA a= new AdapterUSB2VGA(); //Projection Projector p1= new Projector(); p1.projection(a); }
5.2 object adapter mode
Object adapter and class adapter use different methods to realize adaptation. Object adapter uses combination and class adapter uses inheritance.
For example (converting USB interface to VGA interface), the class diagram is as follows:
public class AdapterUSB2VGA implements VGA { USB u = new USBImpl(); @Override public void projection() { u.showPPT(); } }
Implement the VGA interface, indicating that the adapter class is VGA type, and the USB object is directly used in the adapter method.
5.3 interface adapter mode
When you do not need to implement all the methods provided by the interface, you can first design an abstract class to implement the interface and provide a default implementation (empty method) for each method in the interface. Then the subclass of the abstract class can selectively override some methods of the parent class to implement the requirements. It is applicable to the case that an interface does not want to use all its methods.
For example (converting USB interface to VGA interface, b() and c() in VGA will not be implemented), the class diagram is as follows:
AdapterUSB2VGA abstract class
public abstract class AdapterUSB2VGA implements VGA { USB u = new USBImpl(); @Override public void projection() { u.showPPT(); } @Override public void b() { }; @Override public void c() { }; }
AdapterUSB2VGA is implemented without implementing the b() and c() methods.
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA { public void projection() { super.projection(); } }
5.4 summary
Summarize the application scenarios of three adapter modes:
Class adapter mode: when you want to convert a class into a class that meets another new interface, you can use the class adapter mode to create a new class, inherit the original class and implement the new interface.
Object adapter mode: when you want to transform an object into another object that satisfies another new interface, you can create a Wrapper class, holding an instance of the original class, and invoke the instance method in the Wrapper class method.
Interface adapter mode: when you don't want to implement all the methods in an interface, you can create an abstract class Wrapper to implement all the methods. When we write other classes, we can inherit the abstract class.
Naming rules:
I personally understand that the three naming methods are named according to the form src gives to the Adapter (the form in the Adapter).
Class Adapter is given by class. In the Adapter, src is inherited as a class
The object Adapter is given by object. In the Adapter, src is held as an object.
The interface Adapter is given by the interface. In the Adapter, src is implemented as an interface.
Use selection:
According to the principle of composite reuse, combination is greater than inheritance. Therefore, the adapter pattern of the class should be used less.
6 decorator mode
Definition: dynamically attach new functions to objects. In terms of object function expansion, it is more flexible than inheritance.
6.1 decorator mode structure diagram and code example
1.Component (base class of decorated object)
Define an object interface to dynamically add responsibilities to these objects.
2.ConcreteComponent (specific decorated object)
Define an object to which you can add some responsibilities.
3.Decorator (decorator abstract class)
The interface defined by Component is consistent with an instance of Component.
4. Concrete decorator
Specific decorative objects, add specific responsibilities to the specific decorated objects held internally.
The decorated object and decorator inherit from the same superclass
For example (cafe order item: 1), coffee type: Espresso, ShortBlack, LongBlack, Decaf2), seasoning (decorator): Milk, Soy, Chocolate), the class diagram is as follows:
The decorated object and decorator inherit from the same superclass
public abstract class Drink { public String description= ""; private float price= 0f;; public void setDescription(String description) { this.description=description; } public String getDescription() { return description+ "-"+ this.getPrice(); } public float getPrice() { return price; } public void setPrice(float price) { this.price=price; } public abstract float cost(); }
The decorated object does not need to be transformed. How to write it in the past is still how to write it now.
public class Coffee extends Drink { @Override public float cost() { // TODO Auto-generated method stub return super.getPrice(); } }
Implementation of coffee class
public class Decaf extends Coffee { public Decaf() { super.setDescription( "Decaf"); super.setPrice( 3.0f); } }
Decorator
Decorators should not only consider themselves, but also consider the objects modified by them. It continues to add modifications to the modified objects. For example, coffee with milk and chocolate. The price after adding sugar is coffee+milk. The price is milk + coffee.
public class Decorator extends Drink { private Drink Obj; public Decorator( Drink Obj) { this. Obj = Obj; }; @Override public float cost() { // TODO Auto-generated method stub return super.getPrice() + Obj.cost(); } @Override public String getDescription() { return super.description + "-" + super.getPrice() + "&&" + Obj.getDescription(); } }
Garnish (with milk). The modified object should be instantiated.
public class Milk extends Decorator { public Milk( Drink Obj) { super( Obj); // TODO Auto-generated constructor stub super.setDescription( "Milk"); super.setPrice( 2.0f); } }
coffee shop: initialize a modified object. The modifier instance needs to instantiate the modified object to modify the specific modified object.
public class CoffeeBar { public static void main(String[] args) { Drink order; order = new Decaf(); System. out.println( "order1 price:" + order.cost()); System. out.println( "order1 desc:" + order.getDescription()); System. out.println( "****************"); order = new LongBlack(); order = new Milk(order); order = new Chocolate(order); order = new Chocolate(order); System. out.println( "order2 price:" + order.cost()); System. out.println( "order2 desc:" + order.getDescription()); } }
6.2 summary
The decorator and the decorated must be of the same type, that is, they must have a common superclass. Here, application inheritance is not to copy methods, but to match types. Because the decorator and the decorated are of the same type, the decorator can replace the decorated, which makes the decorated have the unique behavior of the decorator. According to the concept of decorator model, we can realize new decorators and add new behaviors at any time. If you use inheritance, you have to modify the original program whenever you need to add a new behavior.
7 agent mode
Definition: proxy mode provides a proxy object for an object, and the proxy object controls the reference to the original object. Generally speaking, the agency model is a common intermediary in our life.
For example: if I want to buy a second-hand car now, although I can find the car source and do a series of vehicle ownership transfer processes such as quality inspection, it really wastes my time and energy. I just want to buy a car. Why do I have to do so many extra things? So I bought a car through an intermediary company. They came to find me a car source and help me handle the vehicle ownership transfer process. I was just responsible for selecting the car I liked and paying for it. It is shown as follows:
7.1 why use proxy mode?
Mediation isolation function: in some cases, a client class does not want or cannot directly reference a delegate object, while the proxy class object can act as an intermediary between the client class and the delegate object. Its feature is that the proxy class and the delegate class implement the same interface.
Opening and closing principle and adding functions: in addition to being the intermediary between the customer class and the delegate class, the agent class can also expand the functions of the delegate class by adding additional functions to the agent class. In this way, we only need to modify the agent class without modifying the delegate class, which is in line with the opening and closing principle of code design. The agent class is mainly responsible for preprocessing messages for the delegate class, filtering messages, forwarding messages to the delegate class, and processing the returned results afterwards. The proxy class itself does not really implement services, but calls the relevant methods of the delegate class to provide specific services. The real business function is still implemented by the delegate class, but some public services can be added before and after the execution of the business function. For example, if we want to add caching and logging functions to the project, we can use the proxy class instead of opening the encapsulated delegate class.
The agency mode is divided into three categories: 1 Static agent 2 Dynamic agent 3 Cglib agent
7.2 static agent
For example (buying a house), the class diagram is as follows:
Step 1: create a service class interface
public interface BuyHouse { void buyHosue(); }
Step 2: implement the service interface
public class BuyHouseImpl implements BuyHouse { @Override public void buyHosue() { System.out.println( "I want to buy a house"); } }
Step 3: create a proxy class
public class BuyHouseProxy implements BuyHouse { private BuyHouse buyHouse; public BuyHouseProxy(final BuyHouse buyHouse) { this.buyHouse = buyHouse; } @Override public void buyHosue() { System.out.println( "Preparation before buying a house"); buyHouse.buyHosue(); System.out.println( "Decoration after buying a house"); } }
Summary:
Advantages: it can expand the function of the target object in accordance with the opening and closing principle.
Disadvantages: to implement the same interface between the proxy object and the target object, we have to create proxy classes for each service, which is too heavy and difficult to manage. At the same time, once the interface changes, the proxy class must be modified accordingly.
7.3 dynamic agent
Dynamic agents have the following characteristics:
1. The proxy object does not need to implement the interface
2. The generation of proxy object is to dynamically build proxy object in memory by using JDK API (we need to specify the type of interface to create proxy object / target object)
Proxy classes no longer implement interfaces. However, the proxy object must have an interface.
Dynamic proxy implementation:
Java.lang.reflect.Proxy class can directly generate a proxy object
-
Proxy. Newproxyinstance (classloader, loader, class <? > [] interfaces, invocationhandler h) generates a proxy object
-
Parameter 1: classloader the classloader of the proxy object generally uses the classloader of the proxy object
-
Parameter 2: class <? > [] interfaces the interface to be implemented by the proxy object is generally the interface implemented by the proxy object
-
Parameter 3:InvocationHandler h (Interface) execution processing class
-
-
Invoke (object proxy, method, method, object [] args) method in InvocationHandler: this method will be executed when calling any method of proxy class
-
Parameter 3.1: proxy object (use with caution)
-
Parameter 3.2: currently executed method
-
Parameter 3.3: the parameter passed by the running time of the currently executed method
-
Step 1: write a dynamic processor
public class DynamicProxyHandler implements InvocationHandler { private Object object; public DynamicProxyHandler( final Object object) { this. object = object; } @Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "Preparation before buying a house"); Object result = method.invoke( object, args); System.out.println( "Decoration after buying a house"); return result; } }
Step 2: write a test class
public class DynamicProxyTest { public static void main( String[] args) { BuyHouse buyHouse = new BuyHouseImpl(); BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse. class.getClassLoader(), new Class[]{BuyHouse. class}, new DynamicProxyHandler(buyHouse)); proxyBuyHouse.buyHosue(); } }
Summary of dynamic agent: although compared with static agent, dynamic agent greatly reduces our development tasks, reduces the dependence on business interface and reduces the degree of coupling. However, there is still a little regret, that is, it can not get rid of the shackle of only supporting interface proxy (we want to use the interface of the object being proxied), because its design is doomed to this regret.
7.4 CGLIB agent
CGLIB principle: dynamically generate a subclass of the class to be proxied, and the subclass overrides all non final methods of the class to be proxied. In the subclass, the method interception technology is adopted to intercept the calls of all parent methods, and the crosscutting logic is weaved into it. It is faster than JDK dynamic proxy using java reflection.
CGLIB bottom layer: use bytecode processing framework ASM to convert bytecode and generate new classes. Direct use of ASM is not encouraged because it requires you to be familiar with the internal structure of JVM, including the format of class file and instruction set.
CGLIB disadvantage: for the final method, proxy cannot be performed.
Implementation steps of CGLIB:
Step 1: build interceptor
public Object intercept( Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println( "Preparation before buying a house"); Object result = methodProxy.invoke( object, args); System.out.println( "Decoration after buying a house"); return result; }
Parameters: Object is the proxy class instance dynamically generated by CGLib, Method is the proxy Method reference called by the entity class above, Object [] is the parameter value list, and MethodProxy is the proxy reference of the generated proxy class to the Method.
Return: the value returned from the method call of the proxy instance.
Where proxy Invokesuper (obj, Arg) calls the parent method of the proxy method on the proxy class instance (that is, the corresponding method in the entity class TargetObject)
Step 2: generate dynamic proxy class
public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance( final Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept( Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println( "Preparation before buying a house"); Object result = methodProxy.invoke( object, args); System.out.println( "Decoration after buying a house"); return result; } }
Here, Enhancer class is a bytecode Enhancer in CGLib. It can easily extend the class you want to handle. You will often see it in the future.
First, set the proxy class TargetObject as the parent class, then set the interceptor TargetInterceptor, and finally execute enhancer Create() dynamically generates a proxy class and forcibly transforms it from Object to the parent type TargetObject.
Step 3: Test
public class CglibProxyTest { public static void main(String[] args){ BuyHouse buyHouse = new BuyHouseImpl(); CglibProxy cglibProxy = new CglibProxy(); BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse); buyHouseCglibProxy.buyHosue(); } }
Cglib proxy summary: the dynamic proxy object created by cglib has higher performance than that created by JDK, but cglib takes much more time to create proxy object than JDK. Therefore, for singleton objects, because there is no need to create objects frequently, cglib is appropriate. On the contrary, JDK is more appropriate. At the same time, because cglib adopts the method of dynamically creating subclasses, the method modified by final cannot be represented.
8 appearance mode
Definition: it hides the complexity of the system and provides an interface for the client to access the system.
8.1 pattern structure and code examples
In short, this mode encapsulates some complex processes into an interface for external users to use more simply. In this mode, three roles are designed.
1). Facade role: the core of appearance mode. It is called by the customer role, and it is familiar with the functions of the subsystem. According to the needs of the customer's role, several combinations of functions are reserved internally. (the customer calls and calls the subsystem function at the same time)
2). Subsystem role: it realizes the functions of the subsystem. It is unknown to the customer role and Facade. It can interact with each other within the system, or it can be called by the interface for the outside world. (realize specific functions)
3). Customer role: complete the function to be realized by calling Facede (calling facade role).
For example (each Computer has CPU, Memory and Disk. When the Computer is turned on and off, the corresponding components will also be turned on and off). The class diagram is as follows:
The first is the subsystem class:
public class CPU { public void start() { System. out.println( "cpu is start..."); } public void shutDown() { System. out.println( "CPU is shutDown..."); } } public class Disk { public void start() { System. out.println( "Disk is start..."); } public void shutDown() { System. out.println( "Disk is shutDown..."); } } public class Memory { public void start() { System. out.println( "Memory is start..."); } public void shutDown() { System. out.println( "Memory is shutDown..."); } }
Then there is the Facade
public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer() { cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void start() { System. out.println( "Computer start begin"); cpu.start(); disk.start(); memory.start(); System. out.println( "Computer start end"); } public void shutDown() { System. out.println( "Computer shutDown begin"); cpu.shutDown(); disk.shutDown(); memory.shutDown(); System. out.println( "Computer shutDown end..."); } }
Finally, the customer role
public class Client { public static void main(String[] args) { Computer computer = new Computer(); computer.start(); System. out.println( "================="); computer.shutDown(); } }
8.2 advantages
- loose coupling
Make the client and subsystem decoupled, and make the module functions inside the subsystem easier to expand and maintain;
- easy to use
The client does not need to know the internal implementation of the subsystem or the internal composition of the subsystem at all. It only needs to interact with the Facade class.
- better division of access levels
Some methods are used outside the system, while others are used interactively within the system. The subsystem concentrates the functions exposed to the outside into the facade, so that the use of the client can be realized, and the internal details of the subsystem are well hidden.
9 Bridge Mode
Definition: separate the abstract part from its implementation part so that they can change independently.
9.1 cases
See the class diagram of mobile phone and mobile phone software in the figure below
Adding a new mobile phone software requires adding corresponding mobile phone software classes under all mobile phone brands. When there are many types of mobile phone software, the number of classes will expand sharply and it is difficult to maintain
What is the relationship between mobile phone and software in mobile phone?
In essence, the software in the mobile phone is not a kind of mobile phone. The mobile phone software running in the mobile phone is a relationship between inclusion and inclusion, rather than a relationship between parent and child or general and special. It is against the general law to realize the design of mobile phone software class by inheriting the mobile phone class.
If the Oppo mobile phone realizes the wifi function, the Oppo application mall that inherits it will also inherit the wifi function, and any change in the Oppo mobile phone class will affect its subclasses
Another solution
From the class diagram, it looks more like the class diagram of mobile phone software, which involves the functions related to the mobile phone itself, such as wifi function. In which class do you implement it? It is obviously inappropriate to implement it in OppoAppStore
There are two elements that cause the change of the whole structure, one is the mobile phone brand and the other is the mobile phone software, so we extract these two points and encapsulate them respectively
9.2 bridge mode structure and code examples
Class diagram:
realization:
public interface Software { public void run(); } public class AppStore implements Software { @Override public void run() { System.out.println( "run app store"); } } public class Camera implements Software { @Override public void run() { System.out.println( "run camera"); } }
Abstract:
public abstract class Phone { protected Software software; public void setSoftware( Software software) { this.software = software; } public abstract void run(); } public class Oppo extends Phone { @Override public void run() { software.run(); } } public class Vivo extends Phone { @Override public void run() { software.run(); } }
Compared with the original design, the abstract part (mobile phone) is separated from its implementation part (mobile phone software class), and the implementation part is abstracted into separate classes, so that they can change independently. The whole class diagram looks like a bridge, so it is called bridging mode
Inheritance is a strong coupling relationship. The implementation of a subclass has a very close dependency on its parent class. Any change in the parent class will lead to changes in the subclass. Therefore, inheritance or strong coupling seriously affects the flexibility of the class and ultimately limits its reusability
From the design of bridge mode, we can see that aggregation is a weaker association than inheritance. Mobile phone and software classes can change independently and will not affect each other
9.3 applicable scenarios
The bridging mode is generally applicable to the following scenarios.
-
When a class has two independently changing dimensions and both dimensions need to be extended.
-
When a system does not want to use inheritance or the number of system classes increases sharply due to multi-level inheritance.
-
When a system needs to add more flexibility between the abstract and concrete roles of components.
9.4 advantages and disadvantages
advantage:
(1) In many cases, the bridge mode can replace the multi-layer inheritance scheme. The multi-layer inheritance scheme violates the "single responsibility principle", has poor reusability, and has a large number of classes. The bridge mode is a better solution than the multi-layer inheritance scheme, which greatly reduces the number of subclasses.
(2) The bridging mode improves the scalability of the system. Expanding one of the two change dimensions without modifying the original system conforms to the "opening and closing principle".
Disadvantages:
The use of bridging mode will increase the difficulty of system understanding and design. Because the correlation is based on the abstract layer, developers are required to design and program for the abstract layer from the beginning.
10 combination mode
Definition: sometimes called part whole mode, it is a mode of combining objects into a tree hierarchy, which is used to represent the "part whole" relationship and enable users to have consistent access to single objects and combined objects.
Intent: combine objects into a tree structure to represent a "part whole" hierarchy. The combination mode enables users to use single objects and combined objects consistently.
Main solution: in our tree structure problem, it blurs the concepts of simple elements and complex elements. The client program can deal with complex elements as simple elements, so as to decouple the internal structure of the client program and complex elements.
When to use: 1. You want to represent the part of the object - the overall hierarchy (tree structure). 2. You want users to ignore the difference between composite objects and individual objects, and users will use all objects in the composite structure uniformly.
How to solve: the branch and leaf realize a unified interface, and the interface is combined inside the branch.
Key code: the interface is combined inside the tree branch, and contains the internal attribute List with Component inside.
The main advantages of the combination mode are:
-
The combination mode enables the client code to deal with a single object and a combined object consistently, without caring whether it is a single object or a combined object, which simplifies the client code;
-
It is easier to add new objects into the assembly, and the client will not change the source code because of the addition of new objects, meeting the "opening and closing principle";
Its main disadvantages are:
-
The design is complex, and the client needs to spend more time to clarify the hierarchical relationship between classes;
-
It is not easy to limit the components in the container;
-
It is not easy to use the method of inheritance to add new functions of components;
10.1 pattern structure and code examples
-
Abstract Component role: its main role is to declare public interfaces for leaf components and branch components and implement their default behavior. In the transparent composition mode, the abstract Component also declares the interface to access and manage subclasses; In the safe combination mode, the interface for accessing and managing subclasses is not declared, and the management work is completed by the branch Component.
-
Leaf role: it is a leaf node object in the composition. It has no child nodes and is used to implement the public interface declared in the abstract component role.
-
Composite role: it is a branch node object in the composition, which has child nodes. It implements the interface declared in the abstract component role. Its main function is to store and manage sub components, usually including Add(), Remove(), GetChild(), etc
For example (visit a tree), the class diagram is as follows:
1 component
public interface Component { public void add(Component c); public void remove(Component c); public Component getChild(int i); public void operation(); }
2 leaves
public class Leaf implements Component{ private String name; public Leaf(String name) { this.name = name; } @Override public void add(Component c) {} @Override public void remove(Component c) {} @Override public Component getChild(int i) { // TODO Auto-generated method stub return null; } @Override public void operation() { // TODO Auto-generated method stub System.out.println( "leaf"+name+ ": Be visited!"); } }
3 branches
public class Composite implements Component { private ArrayList<Component> children = new ArrayList<Component>(); public void add(Component c) { children. add(c); } public void remove(Component c) { children. remove(c); } public Component getChild(int i) { return children. get(i); } public void operation() { for (Object obj : children) { ((Component) obj).operation(); } } }
11 yuan sharing mode
Definition: efficiently support a large number of fine-grained objects through sharing.
Main solution: when there are a large number of objects, it may cause memory overflow. We abstract the common parts. If there are the same business requests, we can directly return the existing objects in memory to avoid re creation.
When to use: 1. There are a large number of objects in the system. 2. These objects consume a lot of memory. 3. Most of the states of these objects can be externalized. 4. These objects can be divided into many groups according to the intrinsic state. When the intrinsic objects are removed from the objects, each group of objects can be replaced by an object. 5. The system does not depend on the identity of these objects, which are indistinguishable.
How to solve it: use the unique identification code to judge. If there is in memory, return the object identified by the unique identification code.
Key code: use HashMap to store these objects.
Application examples: 1. String in JAVA, if any, will be returned. If not, a string will be created and saved in the string cache pool.
Advantages: greatly reduce the creation of objects, reduce the memory of the system and improve the efficiency.
Disadvantages: it increases the complexity of the system and needs to separate the external state from the internal state. Moreover, the external state has inherent nature and should not change with the change of the internal state, otherwise it will cause the confusion of the system.
In short, we extract the external state (which cannot be shared) and internal state (which can be shared) of an object. Then, whether to create an internal state object is determined according to the external state. Internal state objects are saved through hash table. When the external state is the same, internal state objects will not be created repeatedly, so as to reduce the number of objects to be created.
11.1 structure diagram and code example of sharing meta mode
1. Flyweight: it is generally an interface or abstract class, which defines the public methods of the meta class. These methods can share the data of internal state, or call these methods to modify the external state.
2. Concreteflyweight (concrete shared meta class): the concrete shared meta class implements the method of abstract shared meta class and opens up memory space for shared meta objects to save the internal data of shared meta objects. At the same time, only one shared meta object can be created by combining with singleton mode.
3. Flyweightfactory class: the factory class creates and manages the factory class. The factory class is programmed for the factory class and manages the object by providing a pool. Generally, the shared element pool is designed as key value pairs or other storage structures. When the client requests a meta object, if there is a corresponding meta object in the meta pool, the corresponding object will be returned directly. Otherwise, the factory class creates the corresponding meta object and saves it to the meta pool.
For example, if there is a String in JAVA, it will be returned. If there is no String, it will create a String and save it in the String cache pool. The class diagram is as follows:
(1) Create meta object interface
public interface IFlyweight { void print(); }
(2) Create a concrete meta object
public class Flyweight implements IFlyweight { private String id; public Flyweight(String id){ this.id = id; } @Override public void print() { System.out.println( "Flyweight.id = " + getId() + " ..."); } public String getId() { return id; } }
(3) When creating a factory, we should pay special attention here. In order to avoid repeated creation of meta objects, we use the key value in HashMap to ensure its uniqueness.
public class FlyweightFactory { private Map<String, IFlyweight> flyweightMap = new HashMap(); public IFlyweight getFlyweight(String str){ IFlyweight flyweight = flyweightMap. get(str); if(flyweight == null){ flyweight = new Flyweight(str); flyweightMap.put(str, flyweight); } return flyweight; } public int getFlyweightMapSize(){ return flyweightMap.size(); } }
(4) In the test, we create three strings, but only two meta objects will be generated
public class MainTest { public static void main( String[] args) { FlyweightFactory flyweightFactory = new FlyweightFactory(); IFlyweight flyweight1 = flyweightFactory.getFlyweight( "A"); IFlyweight flyweight2 = flyweightFactory.getFlyweight( "B"); IFlyweight flyweight3 = flyweightFactory.getFlyweight( "A"); flyweight1. print(); flyweight2. print(); flyweight3. print(); System.out.println(flyweightFactory.getFlyweightMapSize()); } }
C. Relationship mode (11 kinds)
Let's start with a diagram to see the relationship between these 11 modes:
Class 1: implemented through the relationship between parent and child classes.
Class II: between two classes.
The third category: the status of the class.
Category 4: through intermediate classes
12 strategy mode
Definition: the policy pattern defines a series of algorithms and encapsulates each algorithm so that they can replace each other, and the change of the algorithm will not affect the customers using the algorithm.
Intention: define a series of algorithms, encapsulate them one by one, and make them interchangeable.
Main solution: when there are many similar algorithms, use if Else brings complexity and difficult to maintain.
When to use: a system has many classes, and what distinguishes them is their direct behavior.
How to solve it: encapsulate these algorithms into classes one by one and replace them arbitrarily.
Key code: implement the same interface.
Advantages: 1. The algorithm can be switched freely. 2. 2. Avoid using multiple conditional judgments. 3. Good scalability.
Disadvantages: 1. The number of policy classes will increase. 2. All policy classes need to be exposed.
12.1 policy pattern structure and sample code
Abstract policy role: This is an abstract role, which is usually implemented using interfaces or abstract classes. In contrast, it is our Comparator interface.
Specific strategy roles: packaging specific algorithms and behaviors. In contrast, it is a group of implementation classes that implement the Comparator interface.
Environment role: an abstract role reference will be held internally and called by the client.
An example is as follows (to realize an addition and subtraction function), and the class diagram is as follows:
1. Define abstract policy roles
public interface Strategy { public int calc(int num1,int num2); }
2. Define specific policy roles
public class AddStrategy implements Strategy { @Override public int calc(int num1, int num2) { // TODO Auto-generated method stub return num1 + num2; } } public class SubstractStrategy implements Strategy { @Override public int calc(int num1, int num2) { // TODO Auto-generated method stub return num1 - num2; } }
3. Environmental role
public class Environment { private Strategy strategy; public Environment(Strategy strategy) { this.strategy = strategy; } public int calculate(int a, int b) { return strategy.calc(a, b); } }
4. Testing
public class MainTest { public static void main(String[] args) { Environment environment= new Environment( new AddStrategy()); int result=environment.calculate( 20, 5); System. out.println(result); Environment environment1= new Environment( new SubstractStrategy()); int result1=environment1.calculate( 20, 5); System. out.println(result1); } }
13 template mode
Definition: define the skeleton of an algorithm in operation, and delay some steps to subclasses. The template method enables subclasses to redefine some specific steps of the algorithm without changing the structure of the algorithm.
The popular understanding is that there are several fixed steps to complete a thing, but the implementation details of each step are different according to different objects; You can define a general method to complete the event in the parent class, and call the implementation method of each step according to the steps required to complete the event. The specific implementation of each step is completed by subclasses.
13.1 pattern structure and code examples
Abstract class: it implements the template method and defines the skeleton of the algorithm.
Concrete class: implement the abstract methods in the abstract class, that is, the specific implementation details of different objects.
For example, our cooking can be divided into three steps: (1) preparation of materials; (2) specific cooking; (3) serving dishes to guests. These three parts are the skeleton of the algorithm; However, the ingredients needed to make different dishes, the method of making them, and how to dress up for the guests are all different. This is the different implementation details.). The class diagram is as follows:
a. First, write an abstract cooking parent class:
public abstract class Dish { /** * Specific whole process */ protected void dodish(){ this.preparation(); this.doing(); this.carriedDishes(); } /** * Material preparation */ public abstract void preparation(); /** * cook a dish */ public abstract void doing(); /** * Serve */ public abstract void carriedDishes (); }
b. Come down and make two EggsWithTomato and Bouilli to realize the abstract method in the parent class
public class EggsWithTomato extends Dish { @Override public void preparation() { System.out.println( "Wash and cut tomatoes and beat eggs."); } @Override public void doing() { System.out.println( "Pour the eggs into the pot, then add the tomatoes and fry them together."); } @Override public void carriedDishes() { System.out.println( "Put the fried xihongsi eggs into a plate and serve them to the guests."); } } public class Bouilli extends Dish{ @Override public void preparation() { System.out.println( "Cut pork and potatoes."); } @Override public void doing() { System.out.println( "Pour the cut pork into the pot and fry for a while, then pour in potatoes and stew."); } @Override public void carriedDishes() { System.out.println( "Put the cooked braised meat into a bowl and serve it to the guests."); } }
c. In the test class, let's Cook:
public class MainTest { public static void main(String[] args) { Dish eggsWithTomato = new EggsWithTomato(); eggsWithTomato.dodish(); System. out.println( "-----------------------------"); Dish bouilli = new Bouilli(); bouilli.dodish(); } }
13.2 advantages and disadvantages of template mode
advantage:
(1) the detailed implementation steps are defined in the subclass, and the subclass definition detailed processing algorithm will not change the overall structure of the algorithm.
(2) the basic technology of code reuse is particularly important in database design.
(3) there is a reverse control structure, which calls the operation of its subclass through a parent class, and expands the parent class through the subclass to add new behavior, which conforms to the "opening and closing principle".
Disadvantages:
Each different implementation needs to define a subclass, which will increase the number of classes and make the system larger.
14 observer mode
Definition: defines a one to many dependency between objects. When the state of an object changes, all objects that depend on it are notified and automatically updated.
It mainly solves the problem of notifying other objects of an object's state change. In addition, it should consider ease of use and low coupling to ensure a high degree of cooperation.
When to use: when the state of an object (target object) changes, all dependent objects (observer object) will be notified and broadcast.
How to solve it: using object-oriented technology can weaken this dependency.
Key code: there is an ArrayList in the abstract class to store the observers.
Advantages: 1. The observer and the observed are abstractly coupled. 2. Establish a trigger mechanism.