Design mode - front page - single case mode, factory mode

Posted by ashell on Fri, 08 Oct 2021 05:32:21 +0200

Singleton mode

definition

Singleton design pattern generally refers to the singleton design pattern of a class, which is to take certain methods to ensure that there can only be one object instance for a class in the whole software system, and the class only provides a method to obtain its object instance (static method).

example

For example, Hibernate's SessionFactory acts as a proxy for the data storage source and is responsible for creating Session objects. SessionFactory is not lightweight. Generally, only one SessionFactory is required for a project, which requires the singleton mode.

Hungry Han formula (static constant)

realization

1) Constructor privatization.

2) Class.

3) Expose a public method

code

class Singleton {

    //1)
    private Singleton() {};

    //2)
    private static Singleton instance = new Singleton();

    //3)
    public static Singleton getInstance() {
        return instance;
    }
}

advantage

The instantiation is completed when the class is loaded, which avoids the problem of thread synchronization.

shortcoming

Instantiation is completed during class loading, which does not achieve the effect of lazy loading (ps: there are many reasons for class loading, so it is uncertain whether there are other methods other than getInstance method to cause class loading). If this instance is not used once, it will cause a waste of memory.

Hungry Chinese (static code block)

realization

When a static code block is executed, a singleton object is created.

code

class Singleton {
    
    private Singleton() {};

    private static Singleton instance;
    //Static code block
    static {
        instance = new Singleton();
    }

    public static Singleton getInstance() {
        return instance;
    }
}

advantage

shortcoming

This has the same advantages and disadvantages as the previous one, except that the instantiation process is placed in static code blocks.

Lazy (thread unsafe)

realization

The singleton object is created only when the getInstance method is called.

code

class Singleton {
    private Singleton() {};

    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

advantage

It can achieve the effect of lazy loading.

shortcoming

In the case of multithreading, thread safety problems will occur. One thread passes the if judgment but has not yet executed the creation of a single instance, and the other thread also passes the judgment, which will produce multiple instances. For singleton mode, this method will not be used in actual development.

Lazy (thread safety, synchronization method)

realization

Synchronize using the synchronized getInstance method

code

class Singleton {
    private Singleton() {};

    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

advantage

It inherits the advantages of the former method and solves the thread safety problem.

shortcoming

Using synchronize makes it inefficient. In the actual scenario, only the instantiation is executed for the first time, and the rest are obtained and returned directly. Each time, the getInstance method has to be synchronized, which is too inefficient.

Lazy (* thread (unsafe, synchronous code block)

realization

The synchronization mechanism is not applied to methods, but to code blocks.

code

class Singleton {
    private Singleton() {};

    private static Singleton instance;

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

advantage

shortcoming

This is a very easy mistake. It is mistakenly believed that locking the code blocks related to instantiation can solve the problem of low efficiency of the former method, but the actual writing method can not even ensure thread safety. The reason is the same as the lazy type (thread unsafe) mentioned above.

*Double check

realization

1) Use volatile to ensure the visibility of instance.

2) The synchronization mechanism works on code blocks.

3) Use two if judgment checks

code

class Singleton {
    private Singleton() {};

    private static volatile Singleton instance;

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

advantage

Support lazy loading to ensure thread safety and high efficiency

shortcoming

There are no obvious disadvantages. In actual development, it is recommended to use this method to design the singleton mode

Static inner class

realization

Instantiation through static inner classes

code

class Singleton {

    //Constructor privatization
    private Singleton() {};

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

advantage

Class loading mechanism is adopted to ensure that there is only one initialization instance and thread safety; The static internal class will not be instantiated immediately when the Singleton class is loaded. When instantiation is needed, the internal class will be loaded only by calling the getInstance method, so as to complete the instantiation of Singleton and avoid wasting memory because it is not used; efficient.

shortcoming

No obvious defects, recommended

enumeration

realization

Using enumeration classes

code

enum Singleton {
    INSTANCE;
    public void op1() {
        System.out.println("op1");
    }
}

advantage

Avoid multi-threaded synchronization problems and prevent deserialization from re creating new objects.

shortcoming

No obvious defects, recommended.

Recommended use

Double check, static inner class, enumeration. If the singleton mode must be used at least once, the hungry Chinese style can also be used.

Singleton mode application

Obviously, the Runtime in the jdk source code uses the starving Han style (static constant) in the singleton mode. It is not only thread safe and efficient, but also because the Runtime can be used basically, it will not cause memory waste.  

matters needing attention

1) The singleton mode ensures that there is an object for this type of value in the system memory, saving system resources. For some objects that need to be created and destroyed frequently, using singleton mode can improve system performance.

2) When you want to instantiate a singleton class, use the corresponding method to get the object instead of new.

3) Usage scenario: objects that need to be created and destroyed frequently; Objects that take too much time or resources to create, but are often used, tool objects, and objects that frequently access databases or files (such as data sources, session factories, etc.).

Factory mode

Simple factory mode

definition

1) It belongs to the creation mode. It is a kind of factory mode. It is the simplest and practical mode in the factory mode family.

2) A factory object determines which product class instance to create.

3) Define a class to create an object, which encapsulates the behavior of the instantiated object.

example

If there are two types of pizza, BeefPizza and CheesePizza, the production of pizza includes prepare, bake, cut and box. We need to complete the predetermined function of pizza.

Define abstract class Pizza

public abstract class Pizza {
    protected String name;

    /**
     * Prepare raw materials
     */
    public abstract void prepare();

    public void bake() {
        System.out.println(name + " baking...");
    }

    public void cut() {
        System.out.println(name + " cutting...");
    }

    public void box() {
        System.out.println(name + " boxing...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Create BeefPizza and CheesePizza classes, inherit the abstract Pizza class, and override the prepare method

public class BeefPizza extends Pizza{

    @Override
    public void prepare() {
        setName("Sevennotes");
        System.out.println("Raw materials for preparing beef pizza");
    }
}
public class CheesePizza extends Pizza{

    @Override
    public void prepare() {
        setName("Cheese pizza");
        System.out.println("Raw materials for preparing cheese pizza");
    }
}

Design in traditional mode:

public class OrderPizza {
    public OrderPizza() {
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getType();
            if ("beef".equals(orderType)) {
                pizza = new BeefPizza();
            } else if ("cheese".equals(orderType)) {
                pizza = new CheesePizza();
            } else {
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    public String getType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the type of pizza ordered: beef/cheese,input-1 end");
        do {
            String next = scanner.next();
            if ("-1".equals(next)) {
                break;
            }
            return next;
        } while (true);
        return null;
    }
}

In traditional mode:

1) Advantages: easy to understand and operate.

2) Disadvantages: it violates the opening and closing principle. When a new kind of pizza needs to be added, the OrderPizza code needs to be modified.

Use simple factory mode:

Encapsulate the creation of Pizza object instances into a class, so that when adding new types of Pizza, you only need to modify the class, and other codes that create Pizza classes do not need to be modified.

Improved code:

Add simple factory class

public class SimpleFactory {

    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        System.out.println("Use simple factory mode");
        switch (orderType) {
            case "beef":
                pizza = new BeefPizza();
                break;
            case "cheese":
                pizza = new CheesePizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

Modify OrderPizza class

public class OrderPizza {

    public OrderPizza(SimpleFactory simpleFactory) {
        setFactory(simpleFactory);
    }

    /**
     *polymerization
     */
    SimpleFactory simpleFactory;

    Pizza pizza = null;

    public void setFactory(SimpleFactory factory) {
        String orderType = "";
        this.simpleFactory = factory;
        do {
            orderType = getType();
            pizza = this.simpleFactory.createPizza(orderType);
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } else {
                break;
            }
        } while (true);
    }

    public String getType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the type of pizza ordered: beef/cheese,input-1 end");
        do {
            String next = scanner.next();
            if ("-1".equals(next)) {
                break;
            }
            return next;
        } while (true);
        return null;
    }
}

  Traditional vs Simple Factory class diagram

The difference between the two designs can be seen more intuitively in the figure

Tradition:

 

Simple factory:

  supplement

Simple factory mode is also called static factory mode. That is, make the method of generating object instances of simple factory classes static.

  Add the static keyword to the createPizza method in SimpleFactory

public class SimpleFactory {

    public static Pizza createPizza(String orderType) {
        Pizza pizza = null;
        System.out.println("Use simple factory mode");
        switch (orderType) {
            case "beef":
                pizza = new BeefPizza();
                break;
            case "cheese":
                pizza = new CheesePizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

To modify the OrderPizza class, setFactory is not required.

public class OrderPizza {

    public OrderPizza() {
        String orderType = "";
        do {
            orderType = getType();
            pizza = SimpleFactory.createPizza(orderType);
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } else {
                break;
            }
        } while (true);
    }

    Pizza pizza = null;

    public String getType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the type of pizza ordered: beef/cheese,input-1 end");
        do {
            String next = scanner.next();
            if ("-1".equals(next)) {
                break;
            }
            return next;
        } while (true);
        return null;
    }
}

Factory method model

definition

Define an abstract method to create an object, and the subclass determines the class to be instantiated. The factory method pattern defers the instantiation of objects to subclasses relative to the simple factory pattern.

example

In the example of the simple factory model, the new demand is expanded. In the case of pizza types, there is also a regional flavor, such as Beijing cheese pizza, Beijing beef pizza, London cheese pizza, etc.

At this time, if you use the simple factory pattern, you need to create different simple factory classes, such as BJSimpleFactory and LDSimpleFactory. In this case, scalability and maintainability are not good.

Use factory method mode:

The instantiation function of pizza is abstracted as an abstract method and implemented in subclasses with different regional flavors.

Code, abstract base class Pizza, four subclasses inherit Pizza and rewrite its prepare method

public class BJCBeefPizza extends Pizza {

    @Override
    public void prepare() {
        setName("Beijing beef pizza");
        System.out.println("Raw materials for preparing Beijing beef pizza");
    }
}
public class BJCheesePizza extends Pizza {
    @Override
    public void prepare() {
        setName("Beijing cheese pizza");
        System.out.println("Raw materials for preparing Beijing cheese pizza");
    }
}
public class LDBeefPizza extends Pizza {
    @Override
    public void prepare() {
        setName("London beef pizza");
        System.out.println("Raw materials for preparing London beef pizza");
    }
}
public class LDCheesePizza extends Pizza {
    @Override
    public void prepare() {
        setName("London cheese pizza");
        System.out.println("Raw materials for preparing London cheese pizza");
    }
}

Define an abstract method createPizza in the OrderPizza class

public abstract class OrderPizza {

    //Define an abstract method
    abstract Pizza createPizza(String orderType);

    public OrderPizza() {
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getType();
            pizza = createPizza(orderType);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }


    public String getType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the type of pizza ordered: beef/cheese,input-1 end");
        do {
            String next = scanner.next();
            if ("-1".equals(next)) {
                break;
            }
            return next;
        } while (true);
        return null;
    }
}

Define the details of BJOrderPizza and LDOrderPizza that inherit OrderPizza and implement its abstract methods

public class BJOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if ("beef".equals(orderType)) {
            pizza = new BJCBeefPizza();
        } else if ("cheese".equals(orderType)) {
            pizza = new BJCheesePizza();
        }
        return pizza;
    }
}
public class LDOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if ("beef".equals(orderType)) {
            pizza = new LDBeefPizza();
        } else if ("cheese".equals(orderType)) {
            pizza = new LDCheesePizza();
        }
        return pizza;
    }
}

Structural design class diagram

 

Abstract factory pattern

definition

1) Define an interface to create clusters of related or dependent objects without specifying a specific class.

2) Integrate the simple factory model with the factory method model.

3) From the design level, it is a further abstraction of the simple factory pattern.

4) The factory is abstracted into two layers, the abstract factory and the factory subclass of concrete implementation. According to the created object type, the corresponding factory subclass is used to turn a single factory class into a factory cluster, which is more conducive to code maintenance and expansion.

example

It is still an example of pizza, improved on the basis of the factory method model.

Define an interface AbsFactory

public interface AbsFactory {

    public Pizza createPizza(String orderType);
}

BJFactory and LDFactory implement AbsFactory interface respectively

public class BJFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if ("cheese".equals(orderType)) {
            pizza = new BJCheesePizza();
        } else if ("beef".equals(orderType)) {
            pizza = new BJCBeefPizza();
        }
        return pizza;
    }
}
public class LDFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if ("cheese".equals(orderType)) {
            pizza = new LDCheesePizza();
        } else if ("beef".equals(orderType)) {
            pizza = new LDBeefPizza();
        }
        return pizza;
    }
}

Modify the OrderPizza class and remove the BJOrderPizza and LDOrderPizza classes

public class OrderPizza {

    /**
     * Aggregation relation
     */
    AbsFactory factory;

    public OrderPizza(AbsFactory factory) {
        setFactory(factory);
    }

    private void setFactory(AbsFactory factory) {
        Pizza pizza = null;
        String orderType = "";
        this.factory = factory;
        do {
            orderType = getType();
            pizza = factory.createPizza(orderType);
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } else {
                break;
            }
        } while (true);
    }

    public String getType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Please enter the type of pizza ordered: beef/cheese,input-1 end");
        do {
            String next = scanner.next();
            if ("-1".equals(next)) {
                break;
            }
            return next;
        } while (true);
        return null;
    }
}

Structural design class diagram

 

Factory mode application

The simple factory pattern is used in the source code of java.util.Calendar.

 

  Create the corresponding instance according to the time zone, language and country at the time of creation.

Summary of plant mode

significance

The code of the instantiated object is extracted and put into a class for unified management and maintenance, so as to decouple the dependency relationship with the main project, so as to improve the expansion and maintenance of the project.

matters needing attention

1) When creating an object instance, do not directly the new class, but put the action of the new class in a factory method and return it. (variables do not directly hold references to specific classes).

2) Instead of letting classes inherit concrete classes, they inherit abstract classes or implement interfaces.

3) Do not override methods already implemented in the base class. (Richter substitution principle)

Topics: Java Design Pattern Singleton pattern