Common java design patterns

Title: common Java design patterns
tags: java
cover: /img/image-20210722094649829.png

Singleton mode

The singleton pattern ensures that a class has only one instance, and instantiates itself and provides this instance to the whole system. In the computer system, the driver objects of thread pool, cache, log object, dialog box, printer and graphics card are often designed as singletons.

In short, the singleton mode is selected to avoid inconsistency and political bullshit.

characteristic

1. A singleton class can only have one instance.
2. A singleton class must create its own unique instance.
3. The singleton class must provide this instance to all other objects.

Implementation mode

Hungry Han formula (static constant)

available

Advantages: this method is relatively simple, that is, instantiation is completed when the class is loaded. Thread synchronization problems are avoided.
Disadvantages: the instantiation is completed when the class is loaded, which does not achieve the effect of Lazy Loading. If this instance is never used from beginning to end, it will cause a waste of memory.

private final static Object class= new Object();

And configure the GET method

Hungry Chinese (static code block)

available

This method is actually similar to the above method, except that the class instantiation process is placed in the static code block. When the class is loaded, the code in the static code block is executed to initialize the class instance. The advantages and disadvantages are the same as above

private static Object class;

static {

object= new object;

}

Provide get method

Lazy (thread unsafe)

Not available

This writing method has the effect of Lazy Loading, but it can only be used under single thread. If, under multithreading, one thread enters the if (singleton == null) judgment statement block and has not yet had time to execute, and another thread also passes the judgment statement, multiple instances will be generated. Therefore, this method cannot be used in a multithreaded environment.

private static  Object class;

public static Object getInstance(){
	if (class==null) class=new Object();
	return class
}

Lazy (thread safe)

Not recommended

To solve the thread insecurity problem of the third implementation method above, just do thread synchronization, so thread synchronization is performed on the getInstance() method

Disadvantages: the efficiency is too low. When each thread wants to obtain an instance of a class, it must synchronize the getInstance() method. In fact, this method only executes the instantiation code once. If you want to obtain this class instance later, just return it directly. The synchronization efficiency of method is too low and needs to be improved.

private static  Object class;

public static  synchronized Object getInstance(){
	if (class==null) class=new Object();
	return class
}

Lazy (thread safe, using synchronous code blocks)

Not available

Because the synchronization efficiency of the fourth implementation method is too low, the synchronization method is abandoned and the instantiated code block is generated synchronously. However, this synchronization does not play the role of thread synchronization. Consistent with the situation encountered in the third implementation method, if a thread enters the if (singleton == null) judgment statement block and has not had time to execute, another thread also passes the judgment statement, multiple instances will be generated.

private static  Object class;

public static Object getInstance(){
	if (class==null) 
	synchronized(class.Class){
	class=new Object();
	}
	return class
}

duplication check

The concept of double check is no stranger to multithreading developers. As shown in the code, we have conducted two if (singleton == null) checks to ensure thread safety. In this way, the instantiation code is executed only once. When accessing again later, judge if (singleton == null) and return the instantiated object directly.
Advantages: thread safety; Delayed loading; High efficiency.

private static  Object class;

public static Object getInstance(){
	if (class==null) {
	synchronized(class.Class){
	if (class==null)  class=new Object();
	}
	}
	return class
}

Static inner class

This method is similar to the mechanism adopted by the hungry Han method, but it is different. Both adopt the mechanism of class loading to ensure that there is only one thread when initializing the instance. The difference is that as long as the object class is loaded, it will be instantiated without the role of lazy loading. However, the static internal class method will not be instantiated immediately when the object class is loaded. Instead, when instantiation is required, the SingletonInstance class will be loaded by calling the getInstance method, so as to complete the instantiation of Singleton.
The static properties of the class will only be initialized when the class is loaded for the first time. Therefore, the JVM helps us ensure the safety of threads. When the class is initialized, other threads cannot enter.
Advantages: it avoids thread insecurity, delayed loading and high efficiency.

private static Class Objectclass{
private static final Object class=new Singleton();

}
public static Object getInstance(){
return  Objectclass.class
}

enumeration

The singleton mode is implemented with the help of enumeration added in JDK1.5. It can not only avoid the problem of multi-threaded synchronization, but also prevent deserialization and re creation of new objects.

public enum Object{
	class;
	public void whateverMethod(){

	}
}

advantage

There is only one object of this class in the system memory, which saves system resources. For some objects that need to be created and destroyed frequently, the singleton mode can improve the system performance.

shortcoming

When you want to instantiate a singleton class, you must remember to use the corresponding method to obtain the object instead of new, which may cause trouble to other developers, especially when you can't see the source code.

Usage scenario

• objects that need to be created and destroyed frequently;
• objects that take too much time or resources to create objects, but are often used;
• tool objects;
• objects that frequently access databases or files.

Factory mode

characteristic

Factory mode is suitable: when a large number of products need to be created and have common interfaces, they can be created through factory method mode. In the above three modes, the first one cannot create objects correctly if the incoming string is incorrect. Compared with the second one, the third one does not need an instance factory class. Therefore, in most cases, we will choose the third one - static factory method mode.

General factory mode

We use an example of sending email and SMS

//Create an interface that both share
public interface Sender{
	public void Send();
}
//Create implementation class
public class MailSender implements Sender{
	@Override
	public void Send(){
	System.out.println("send Mail");
	}
}
public class SmsSender implements Sender{
	@Override
	public void Send(){
	System.out.println("send sms");
	}
}

//Create factory class
public class SendFactory{
	public Sender producce(String type){
	if ("mail".equals(type)) return new MailSender;
	else if ("sms".equals(type)) return newSmsSender
	else System.out.println("error");
	
	return  null;
	}
}


Multiple factory method patterns

It is an improvement of the ordinary factory method mode. In the ordinary factory method mode, if the string passed is wrong, the object cannot be created correctly. Multiple factory method modes provide multiple factory methods

//Class will not be written repeatedly
public calss SendFactory{
	public Sender produceMail(){
	return new MailSender();
	}
	public Sender produceSms(){
	return new SmaSender();
	}
}

Static factory method mode

Set the methods in the above multiple factory method patterns as static. You can call them directly without creating an instance

public calss SendFactory{
	public static Sender produceMail(){
	return new MailSender();
	}
	public static Sender produceSms(){
	return new SmaSender();
	}
}

Abstract factory pattern

A problem with the factory method pattern is that the creation of classes depends on factory classes, that is, if you want to expand the program, you must modify the factory class, which violates the closure principle. Therefore, from the design point of view, there are certain problems. How to solve them? The abstract factory pattern is used to create multiple factory classes. In this way, once new functions need to be added, new factory classes can be added directly without modifying the previous code. Because the abstract factory is not easy to understand, let's look at the diagram first, and then compare it with the code, which is easier to understand

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-oyhheqdg-163523819237) (D: \ blog \ langblog \ public \ img \ image-20210908201342435. PNG)]

//Define an interface
public interface Provider {  
    public Sender produce();  
}  
//Two implementation classes
public class MailSender implements Sender{
	@Override
	public void Send(){
	System.out.println("send Mail");
	}
}
public class SmsSender implements Sender{
	@Override
	public void Send(){
	System.out.println("send sms");
	}
}
//Two factory classes
public class SendMailFactory implements Provider {  
      
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}  
public class SendSmsFactory implements Provider{  
  
    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
}  

In this way, when we want to add a function, we only need to add a class to implement the Sender interface and a factory class to implement the Provider interface. It has good expansibility

Observer mode

characteristic:

Observer mode (also known as Publish/Subscribe mode) is a kind of behavioral mode. It defines a one to many dependency that allows multiple observer objects to listen to a topic object at the same time. When the status of the topic object changes, it will notify all observer objects so that they can update themselves automatically.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-pnrfdtqf-163523819241) (D: \ blog \ langblog \ public \ img \ image-20210908202235947. PNG)]

public interface subject{
    //Subscription operation
    void attach(Observer observer);
    //Unsubscribe
    void detach(Observer observer);
    //Notice of change
    void notifyChanged();
}

//Define observer observer object
public interface Observer{
    void update();
}
//Create the implementation classes of Subject and observer
public static class RealSubject implements Subject {
    //Define a consumer queue
    private List<Observer> observerList = new ArrayList<>();
    @Override
	public void attach(Observer observer) {
    observerList.add(observer);
	}
    @Override
	public void detach(Observer observer) {
	    observerList.remove(observer);
	}
    @Override
	public void notifyChanged() {
        //Traverse the consumer queue and send messages to each consumer
    	for (Observer observer : observerList) {
        	observer.update();
    }
    
}
    
//Create the implementation class of Observer
public static class RealObject implements Observer {
    @Override
    public void update() {
        System.out.println("Notification received");
    }
}

Strategy mode

The essence of policy pattern is: separate algorithm and choose implementation

advantage

  • Opening and closing principle;
  • Avoid using multiple conditional transition statements;
  • It improves the confidentiality and security of the algorithm: Policy patterns can be used to avoid exposing complex and algorithm related data structures.

The policy pattern embodies two very important principles in object-oriented programming:

  1. Encapsulate the concept of change.
  2. Programming uses interfaces instead of concrete implementation classes (interface oriented programming)

For example, we take TreeSet in Java as an example. TreeSet only knows that it only receives a Comparator interface type, but TreeSet doesn't care about the specific implementation class. TreeSet itself doesn't know the implementation class before it is really passed into TreeSet, so we can implement the Comparator interface ourselves, and then encapsulate ourselves in the implementation class For example, we need to sort the elements of a set, but whether we want to sort in ascending or descending order is entirely up to us. We can encapsulate the content of this change into our own implementation class and know the specific implementation only when it is really running

The definition uses the policy pattern to implement a simple addition, subtraction, multiplication and division function

//Define abstract policy roles, which are usually implemented using interfaces or abstract classes
public interface Strategy {
    //Two numbers can be calculated
    public int calc(int num1,int num2);
}

//Define specific policy roles
public class AddStrategy implements Strategy {
    @Override
    public int calc(int num1, int num2) { //Implement the method in the interface to complete the sum of two numbers
        return num1+num2;
    }
}
public class SubStrategy implements Strategy {
    @Override
    public int calc(int num1, int num2) { //Implement the method in the interface to complete the difference between two numbers
        return num1-num2;
    }
//Define the environment role, be responsible for interacting with specific policy classes, and internally hold a reference to the policy class to call the client
public class Environment {
    //Holds a reference to the policy class
    private Strategy strategy;
    //The construction method with parameters is injected through the constructor
    public Environment(Strategy strategy) {
        this.strategy = strategy;
    }
    public int calulate(int a,int b){
        return strategy.calc(a,b);
    }
}

proxy pattern

  • When we want to hide a class, we can provide a proxy for it;
  • When a class needs to provide different call permissions for different calls, it can be implemented using a proxy class (there may not be only one proxy class, we can establish multiple proxy classes to implement it, and the page can relay the permission half of a proxy class to make function calls with different permissions);
  • When we want to extend a function, we can use the proxy mode to extend it in the proxy mode (only for the extension, before and after the statement referencing the delegate class)

Usage scenario

Proxy mode provides a proxy for other objects to control access to this object. In some cases, a client does not want or cannot directly reference another object, and the proxy object can start the role of mediation between the client and the target object. The client deals with the proxy and communicates with the target object through the proxy.

1. Abstract role (Interface): declare the common interface between real object and proxy object.

2. Proxy role: the proxy object role contains a reference to the real object, so that the real object can be operated. At the same time, the proxy object provides the same interface as the real object, so that it can replace the real object at any time. The colleague proxy object can attach other operations when performing the real object operation, which is equivalent to encapsulating the real object

3. Real role: the real object represented by the proxy role is the object we will eventually reference.

Agent type

Static proxy

  • Advantages: the target function can be extended without modifying the function of the target object;
  • Disadvantages: the proxy object needs to implement the same interface as the target object, so there will be too many proxy classes. At the same time, once the interface adds methods, both the target object and the proxy object must be maintained;
//Abstract role code
public interface ISinger(){
    void sing();
}
//Real character code
public class Singer implements ISinger(){
    @0verride
    public void sing(){
        System.out.println("sing a song");
    }
}
//Agent role code
public class SingerProxy implements ISinger{
    private ISinger Isinger;
    
    public SingerProxy(ISinger iSinger){
        this.Isinger=iSinger;
    }
    @Override
    public void sing(){
        System.out.println("Say hello to the audience");
        iSinger.sing();
        System.out.println("Thank you");
    }
    
}
//Test class code
 		Singer singer = new Singer();
        //Create a proxy role, and the real role is required in the construction method;
        ISinger singerProxy = new SingerProxy(singer);
        //Proxy role execution method (call the method corresponding to the real role inside the proxy role)
        singerProxy.sing();

jdk dynamic agent

  • Advantages: the proxy object does not need to implement the interface. The JDK API is used to dynamically build the proxy object in memory (we need to specify the type of interface to create the proxy object / target object)

  • Disadvantages: the target object must implement the interface, otherwise it cannot use dynamic proxy

    The classes used by the Java dynamic proxy are located under the java.lang.reflect package. Generally, the following two classes are mainly involved;

    Interface InvocationHandler: only one method public object invoke (object obj, method, object [] args) is defined in the interface. In actual use, the first parameter obj generally refers to the proxy class, and method refers to the proxy method. For example, sing() in the above code, args is the parameter array of the method;
    Proxy: this class is a dynamic proxy class. Its function is similar to that of ProxySubject in the above example. It mainly includes the following contents:
    1) . newProxyInstance can be understood in this way to generate a proxy object, which can proxy the real object and complete the operation of the real object and its own additional operations;
    2) The so-called dynamic proxy is a kind of calss: it is a class generated at runtime. When generating it, you must provide it with a set of interfaces, and then the class claims that it implements these interfaces. Of course, you can use the modified class instance as any of these interfaces. Of course, this dynamic proxy is actually a proxy, It will not do substantive work for you. When generating its instance, you must provide a handler to take over the actual work;

//Abstract role code
public interface ISinger(){
    void sing();
}
//Define a class, implement abstract code, and rewrite related methods;
public class Singer implements ISinger{

    @Override
    public void sing() {
        System.out.println("sing a song");
    }
}
//There is no code for dynamic proxy, but it is implemented when used
public class Mainclass{
    public static void main(String[] args){
        //Create a real character
        Singer singer=new Singer();
   //Call the Proxy.newProxyInstance method to build an InvocationHandler object
   //target.getClass().getClassLoader(): get the class loader to generate the proxy object
   //target.getClass().getInterfaces() get interface meta information
   ISinger iSinger =(ISinger) Proxy.newProxyInstance(singer.getClass().getClass().getClassLoader(),singer.get(
   //Rewrite an invoke method and call method.invoke(target,args); And add your own code logic above and below the method
       @Override
       public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
           //Write your own business logic "before the method of the real object is called"
           System.out.println("Dynamic agent---Say hello to the audience");
           Object returnValue=method.invoke(singer,args);
           System.out.println("Dynamic agent---Say hello to the audience");
           return returnValue
       }
   );
    iSinger.sing();                                               }
    
}

CGLIB dynamic proxy

  • Advantages: when the class to be implemented does not implement an interface and wants to proxy this class without changing the original code logic, CGLIB dynamic proxy is the most appropriate;
  • Disadvantages: it is relatively more complex and cannot be modified by final (if modified by final, it cannot be inherited, subclasses cannot be generated, and agents cannot be implemented);

The target class cannot be final. If the method of the target object is final / static, it will not be intercepted, that is, additional business methods of the target object will not be executed

//Abstract code does not exist in the CGLIB proxy
//Real objects in dynamic proxies
public class Singer {
    public void sing(){
        System.out.println("sing a song");
    }
}
//Factory class code
public class ProxyFactory implements MethodInterceptor{
    private Object target;
    public ProxyFactory(Object target){
        thhis.target=taeget;
    }
    public Object getProxyInstance(){
        // Tool class
        Enhancer en=new Enhancer();
        // Set parent class
        en.setSuperclass(target.getClass);
        //Set callback function
        en.setCallback(this);
        //Create subclass
        return en.create();
    }
    @Override 
    public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws excption{
        //Here, write your own business logic before the real object method is executed
        System.out.println("CGLIB Dynamic agent -- say hello to the audience");
        //Calling the method corresponding to the real object;
         Object returnValue = method.invoke(target,objects);
        //Here, write your own business logic before the real object method is executed
        System.out.println("CGLIB Dynamic agent - thank you");
        return returnValue;
    }
}
//Test class code
public class MainClass {
    public static void main(String[] args) {
        Singer singer = new Singer();
        Singer proxySinger = (Singer) new ProxyFactory(singer).getProxyInstance();
        proxySinger.sing();
    }
}

Finally, the two methods of dynamic agent are true and a little unclear. It seems that you have to read it several times before you understand it. Part of the code is transferred to other people's blog s

logic
System.out.println("CGLIB dynamic agent -- say hello to the audience");
//Calling the method corresponding to the real object;
Object returnValue = method.invoke(target,objects);
//Here, write your own business logic before the real object method is executed
System.out.println("CGLIB dynamic agent - thank you");
return returnValue;
}
}
//Test class code
public class MainClass {
public static void main(String[] args) {
Singer singer = new Singer();
Singer proxySinger = (Singer) new ProxyFactory(singer).getProxyInstance();
proxySinger.sing();
}
}

Finally, it shows that the two dynamic agent methods are true and a little unclear. It seems that you have to read them several times before you understand them. Some of the code is handled by others blog

Tags: Java Back-end

Posted on Tue, 26 Oct 2021 03:54:20 -0400 by mashamit