23 design patterns in Java: detailed analysis of the five creative patterns

23 design patterns in Java: detailed analysis of the five creative patterns

This is the 16th day that I participated in the more text challenge. For details of the event, please see: more text challenge

Creation mode

Factory Method

There are three types of factory method patterns : normal factory pattern, multiple factory method patterns, and static factory method patterns.

Ordinary factory model
  • Establish a factory class to create instances of some classes that implement the same interface:

--- Send email and SMS -Interface public interface Sender { public void Send () ; } -Implementation class public class MailSender implements Sender { @Override public void Send () { System.out.println( "MailSender Method" ); } } -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------ public class SmsSender implements Sender { @Override public void Send () { System.out.println( "SmsSender Method" ); } } -Factory public class SendFactory { public Sender produce (String type) { if ( "mail" .equals(type)){ return new MailSender(); } else if ( "sms" .equals(type)){ return new SmsSender(); } else { System.out.println( "Please input right type!" ); } } } -Testing public class FactoryTest { public static void main (String[] args) { SendFactory factory = new SendFactory(); Sender sender=factory.produce( "sms" ); sender.Send(); } } Copy code
Multiple factory method patterns
  • The multiple factory method pattern is an improvement of the common factory method pattern
  • In the normal factory method mode, if the passed string is wrong, the object cannot be created
  • Multiple factory method patterns provide multiple factory methods to create objects separately

-SendFactory class public class SendFactory { public Sender produceMail () { return new MailSender(); } public Sender produceSms () { return new SmsSender(); } } -Testing public class FactoryTest { public static void main (String[] args) { SendFactory factory = new SendFactory(); Sender sender=factory.produceMail(); sender.Send(); } } Copy code
Static factory method pattern
  • Set the methods in multiple factory method patterns as static methods, no need to create an instance , just call it directly
- SendFactory public class SendFactory{ public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } - FactoryTest public class FActoryTest{ public static void main(String[] args){ Sender sender=SenderFactory.produceMail(); sender.Send(); } }
  • , , :
    • : ,
    • ,
    • ,

Abstract Factory

  • Factory method pattern problem: the creation of a class depends on the factory class. If you want to extend the program, you must modify the factory class, which violates the principle of closure
  • Abstract factory mode: Create multiple factory classes. Once you need to add new functions, you can directly add the factory class without modifying the previous factory class.

-Sender public interface Sender { public void Sender () ; } -Two implementation classes -MailSender public class MailSender implements Sender { @Override public void Send () { System.out.println( "This is MailSender!" ); } } -SmsSender public class SmsSender implements Sender { @Override public void Send () { System.out.println( "This is SmsSender!" ); } } -Two factory classes -Factory interface: public interface Provider { public Sender produce () ; } -SendMailFactory public class SendMailFactory implements Provider { @Override public Sender produce () { return new MailSender(); } } -SendSmsFactory public class SendSmsFactory implements Provider { @Override public Sender produce () { return new SmsSender(); } } -Test public class Test { public static void main (String[] args) { Provider provider = new SendMailFactory(); Sender sender=provider.produce(); sender.Send(); } } Copy code
  • The advantage of the abstract factory model is that it is highly scalable:
    • If you need to add a function, for example: send timely information
      • Just make an implementation class to implement the Sender interface
      • Make a factory class and implement the Provider interface

Singleton mode (Singleton)

  • Singleton mode : ensure that only one instance of a singleton object exists in a JVM
  • Advantages of singleton mode:
    • Certain classes are more cumbersome to create, and for some large objects, a lot of system overhead can be reduced
    • The new operator is omitted, the frequency of system memory usage is reduced, and the pressure of GC (Garbage Collection) is reduced.
    • Some classes, such as the core trading engine of an exchange, control the trading process. If multiple classes of this type can be created, the system will be completely chaotic. All only by using the singleton mode can the core trading server independently control the entire process
-Singleton class public class Singleton { /* Private static instance, to prevent being quoted, assign value to null, the purpose is to realize lazy loading */ private static Singleton instance = null ; /* Private construction method to prevent being instantiated*/ private Singleton () { } /* Static factory method, create instance*/ public static Singleton getInstance () { if (instance== null ){ instance = new Singleton(); } return instance; } /* If the object is used for serialization, you can ensure that the object remains consistent before and after serialization*/ public Object ReadResolve () { return instance; } } Copy code
  • Considering multi-thread safety, the first thing to think of is to add the synchronized keyword to the getInstance method :
public static synchronized Singleton getInstance () { if (instance== null ){ instance = new Singleton(); } return instance; } Copy code

Since synchronized locks this object, in this usage, every time getInstance() is called, the object must be locked, which will reduce performance.

  • You only need to lock the object when you create it for the first time, and you don't need it afterwards:
public static Singleton getInstance () { if (instance== null ){ synchronized (instance){ if (instance== null ){ instance = new Singleton(); } } } return instance; } Copy code

This seems to solve the problem. The synchronized keyword is added to the internal, so that there is no need to lock when calling. The lock is only needed when the instance is null and the object is created. The performance has been improved, but this The situation is still problematic

  • There are such situations:
    • Object creation and assignment operations in Java are carried out separately
    • That is, instance=new Singleton() is executed in two steps
    • The JVM does not guarantee the order of these two operations:
      • It is possible that the JVM will allocate empty space for the new Singleton instance, and then directly assign it to the instance member
      • Then go to initialize this Singleton instance
    • This may make mistakes
  • Example:
    • A, B two threads
      • A and B threads enter the first if judgment at the same time
      • A first enters the synchronized block, because instance is null, execute instance=new Singleton()
      • Due to the optimization mechanism inside the JVM, the JVM first draws some blank memory allocated to Singleton and assigns it to the instance member. At this time, the instance has not yet been initialized, and then A leaves the synchronized block.
      • B enters synchronized. Since i nstance is not null at this time , it immediately leaves the synchronized block and returns the result to the program that called the method.
      • At this time, the B thread intends to use the Singleton instance and finds that it has not been initialized, so an error occurs
  • The code needs further optimization:
private static class SingletonFactory { private static Singleton instance = new Singleton(); } public static Singleton getInstance () { return SingletonFactory.instance; } Copy code
  • The actual situation is:
    • Singleton mode uses internal classes to maintain the implementation of singletons
    • The mechanism inside the JVM can ensure that when a class is loaded, the loading process of this class is mutually exclusive
    • When the getInstance is called for the first time, the JVM can ensure that the instance is created only once, and it will ensure that the memory assigned to the instance is initialized.
    • This method will only use the mutual exclusion mechanism when it is called for the first time, which can perfectly solve the problem of low performance.
public class Singleton { /* Private construction method to prevent being instantiated*/ private Singleton () {} /* Use internal class to maintain singleton*/ private static class SingletonFactory { private static Singleton instance = new Singleton(); } /* Get instance*/ public static Singleton getInstance () { return SingletonFactory.instance; } /* If the object is serialized, it can be guaranteed that the object remains consistent before and after serialization*/ public Object readResolve () { return getInstance(); } } Copy code

In this way, if an exception is thrown in the constructor, the instance will never be created and errors will occur. You can only choose the implementation method that is most suitable for the application scenario according to the actual scenario

  • Because it only needs to be synchronized when creating the class, it is also possible to separate the creation and getInstance() and add the synchronized keyword for the creation separately:
public class SingletonTest { private static SingletonTest instance = null ; private SingletonTest () {} private static synchronized void syncInit () { if (instance== null ){ instance = new SingletonTest(); } } public static SingletonTest getInstance () { if (instance== null ){ syncInit(); } return instance; } } Copy code
  • Use the "shadow instance" method to update the properties of the singleton object synchronously:
public class SingletonTest { private static SingletonTest instance = null ; private Vector properties = null ; public Vector getProperties () { return properties; } private SingletonTest () {} private static synchronized void syncInit () { if (instance== null ){ instance = new SingletonTest(); } } public static SingletonTest getInstance () { if (intance== null ){ syncInit(); } return instance; } public void updateProperties () { SingletonTest shadow = new SingletonTest(); properties=shadow.getProperties(); } } Copy code
  • Features of singleton mode:
    • The singleton mode is simple to understand, but it is difficult to implement in detail.\
      • Synchronize
      • asynchronous
    • The synchronized keyword locks the object. When using it, use it in the appropriate place:
      • Pay attention to the objects and processes that need to be locked, sometimes not the entire object and the entire process need to be locked
  • The static method of the class can be used to achieve the effect of the singleton mode
    • The difference between class static methods and singleton mode:
      • Static classes cannot implement interfaces:
        • From a class point of view, it is ok, but this will break the static
        • Static modified methods are not allowed in the interface, even if they are implemented, they are non-static
      • Singletons can be delayed to start:
        • Static classes are initialized when they are first loaded
        • Singleton lazy loading, because some classes are relatively large, and lazy loading can help improve performance
      • Singletons can be inherited:
        • The method in the singleton can be rewritten
        • The internal methods of static classes are static and cannot be overridden
      • Singleton is more flexible:
        • In terms of implementation, a singleton is just an ordinary Java class. As long as it meets the basic requirements of a singleton, other functions can be implemented at will.
        • Static class does not work
    • The singleton mode can be implemented with a static class

Builder mode (Builder)

  • The factory pattern provides a pattern to create a single class
  • Builder mode: Centralize various products for management to create composite objects
    • Composite object: refers to a class with different attributes
  • The builder mode is the combination of the abstract factory class mode and the Test class.
  • Code implementation: one Sender interface, two implementation classes MailSender and SmsSender
-Builder public class Builder { private List<Sender> list = new ArrayList<Sender>(); public void produceMailSender ( int count) { for ( int i = 0 ;i<count;i++){ list.add( new MailSender()); } } public void produceSmsSender ( int count) { for ( int i= 0 ;i<count;i++){ list.add( new SmsSender()); } } } -Testing public class Test { public static void main (String[] args) { Builder builder = new Builder(); builder.produceMailSender( 10 ); } } Copy code
  • The builder mode integrates many functions into one class, and this class can create more complex modules
  • The difference between builder mode and factory mode:
    • The factory model focuses on creating a single product
    • The builder mode focuses on creating conforming objects, multiple parts

Prototype

  • Prototype mode: Use an object as a prototype, copy, clone, and generate a new object similar to the original object
  • Although the prototype mode is a creation mode, it has nothing to do with the factory mode
  • In Java, an object is copied by clone () implementation
-Prototype class public class Prototype implements Cloneable { public Object clone () throws CloneNotSupportedException { Prototype proto=(Prototype) super .clone(); return proto; } } Copy code
  • A prototype class only needs to implement the Cloneable interface and override the clone() method
  • The clone method can be rewritten to any name, because the Cloneable interface is an empty interface, and the method name of the implementation class can be defined arbitrarily
  • The point is super.clone():
    • The super.clone() method calls the **clone()** method of Object
    • Object class , clone () when the method of native of
  • Deep copy and shallow copy of objects:
    • Deep copy:
      • After copying an object, whether it is a basic type or a reference type, it is re-created
      • Deep copy will make a complete copy
    • Shallow copy:
      • After copying an object, the variables of the basic data type will be recreated, and the reference type points to the reference of the original object
public class Prototype implements Cloneable , Serializable { private static final long serialVersionUID = 1L ; private String string; private SerializableObject obj; /* Shallow copy*/ public Object clone () throws CloneNotSupportedException { Prototype proto=(Prototype) super .clone(); return proto; } /* Deep copy*/ public Object clone () throws IOException,ClassNotFoundException { /* Write out the binary stream of the current object*/ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject( this ); /* Read in the new object generated by the binary stream*/ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); OnjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getString () { return string; } public void setString (String string) { this .string=string; } public SerializableObject getObj () { return obj; } public void setObj (SerializableObject obj) { this .obj=obj; } } class SerializableObject implements Serializable { private static final long serialVersionUID = 1L ; } Copy code
  • To achieve deep copy:
    • The binary input of the current object to be read in the form of a stream
    • Then write the object corresponding to the binary data