[Design Mode] Creator Mode (Singleton & Factory & Prototype & Builder)

Preamble:

The main focus of the creational pattern is 怎样创建对象?that its main characteristics are将对象的创建与使用分离

This can reduce the coupling of the system, and users do not need to pay attention to the details of object creation.

Creational patterns are divided into:

  • singleton pattern
  • factory pattern
  • prototype pattern
  • builder mode

1. Singleton design pattern

1.1 Overview of singleton mode

Singleton Pattern (Singleton Pattern) is one of the simplest design patterns in Java. This type of design pattern is a creational pattern, which provides an optimal way to create objects.

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its only object, directly, without instantiating an object of this class.

The main roles of the singleton mode are as follows:

  • 单例类: A class that can only create one instance
  • 访问类: use a singleton class

Why use the singleton pattern?

In our system, there are some objects that we only need one, such as: thread pool, cache, dialog box, registry, log object, object that acts as a device driver for printers, graphics cards, etc. In fact, this type of object can only have one instance. If multiple instances are created, it may cause some problems, such as: abnormal behavior of the program, excessive resource usage, or inconsistent results. So here you need to use the singleton mode

What are the benefits of using the singleton pattern?

  • For frequently used objects, the time spent on creating objects can be omitted, which is a considerable system overhead for those heavyweight objects
  • Since the number of new operations is reduced, the frequency of system memory usage will also be reduced, which will reduce GC pressure and shorten GC pause time

1.2 Implementation of singleton mode

There are two types of singleton design patterns:

  • Hungry Chinese style : class loading will cause the single instance object to be created
  • Lazy style : class loading will not cause the single instance object to be created, but will be created when the object is used for the first time

1.2.1 Hungry style (static variable mode)

public class Singleton {
    
    
    //私有构造方法
    private Singleton() {
    
    }

    //在成员位置创建该类的对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    
    
        return instance;
    }
}

This method declares a static variable of the Singleton type at the member position and creates an object instance of the Singleton class. The instance object is created as the class is loaded. If the object is large enough, it will cause a waste of memory if it has not been used.

1.2.2 Hungry style (static code block method)

public class Singleton {
    
    
    //私有构造方法
    private Singleton() {
    
    }

    //在成员位置创建该类的对象
    private static Singleton instance;

    static {
    
    
        instance = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    
    
        return instance;
    }
}

This method declares a static variable of the Singleton type at the member position, and the creation of the object is in the static code block, which is also created for the loading of the class. Therefore, it is basically the same as the static variable method of Hungry Man, but of course this method also has the problem of memory waste.

1.2.3 Lazy style (thread unsafe)

public class Singleton {
    
    
    //私有构造方法
    private Singleton() {
    
    }

    //在成员位置创建该类的对象
    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    
    

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

From the above code, we can see that this method declares a static variable of Singleton type in the member position, and does not perform object assignment, so when is it assigned? When the getInstance() method is called to obtain the object of the Singleton class, the object of the Singleton class is created, thus achieving the effect of lazy loading. However, if it is a multi-threaded environment, there will be thread safety issues.

1.2.4 Lazy style (double check lock)

public class Singleton4 {
    
    
    //构造私有
    private Singleton4() {
    
    
        System.out.println("private Singleton4()");
    }

    //唯一实例
    //这里volatile的作用是保证共享变量有序性!
    private static volatile Singleton4 INSTANCE = null;

    //双检锁优化
    public static Singleton4 getInstance() {
    
    
        //实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁
        if (INSTANCE == null) {
    
    
            //可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断
            synchronized (Singleton4.class) {
    
    
                //也许有其他线程已经创建实例,所以再判断一次
                if (INSTANCE == null) {
    
    
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }
}

1.2.5 Lazy style (static inner class)

In the static inner class singleton mode, the instance is created by the inner class. Since the JVM will not load the static inner class during the process of loading the outer class, only when the properties/methods of the inner class are called will it be loaded and initialized. static properties. Static properties are guaranteed to be instantiated only once due to being staticmodified , and the order of instantiation is strictly guaranteed.

public class Singleton {
    
    

    //私有构造方法
    private Singleton() {
    
    }

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

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}
  • When the Singleton class is loaded for the first time, INSTANCE will not be initialized. Only when getInstance is called for the first time, the virtual machine loads SingletonHolder and initializes INSTANCE, which not only ensures thread safety, but also ensures the uniqueness of the Singleton class.
  • The static inner class singleton pattern is an excellent singleton pattern, and it is a commonly used singleton pattern in open source projects. Without adding any locks, the security under multi-threading is guaranteed, and there is no performance impact and space waste.

1.2.6 Enumeration method

The enumeration class implements the singleton mode is a highly recommended singleton implementation mode, because the enumeration type is thread-safe and will only be loaded once. The designer makes full use of this feature of the enumeration to implement the singleton mode. The enumeration The writing method is very simple, and the enumeration type is the only singleton implementation mode that cannot be destroyed in the singleton implementation used. (The enumeration belongs to the hungry Chinese style)

public enum Singleton{
    
    
    INSTANCE;

    //枚举的构造方法默认是private的,可以不写
    Singleton() {
    
    
    }

    //获得实例方法(这个可以不要,枚举变量都是public的)
    public static Singleton getInstance() {
    
    
        return INSTANCE;
    }
}

1.3 Existing problems

Look here→ Detailed explanation of singleton mode

1.4 Embodiment of JDK singleton mode

Look here→ Detailed explanation of singleton mode

2. Factory mode

2.1 Overview of factory pattern

Here we use a case to understand the factory pattern.

Requirements: Design a coffee shop ordering system.

Design a coffee class (Coffee) and define its two subclasses (American Coffee [AmericanCoffee] and Latte Coffee [LatteCoffee]); then design a coffee shop class (CoffeeStore), the coffee shop has the function of ordering coffee.

The specific classes are designed as follows:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-nxNEBbke-1649210957049)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403100500227.png)]

Coffee:

public abstract  class Coffee {
    
    
    public abstract String getName();
    //加糖
    public  void  addSugar(){
    
    
        System.out.println("加糖");
    }
    //加奶
    public void addMilk(){
    
    
        System.out.println("加奶");
    }
}

Americano:

public class LatteCoffee extends Coffee{
    
    
    @Override
    public String getName() {
    
    
        return "拿铁咖啡";
    }
}

latte:

public class AmericanCoffee extends Coffee{
    
    
    @Override
    public String getName() {
    
    
        return "美式咖啡";
    }
}

Coffee shop category:

public class CoffeeStore {
    
    
    public Coffee orderCoffee(String type) {
    
    
        //声明Coffee的变量,根据不同类型创建不同的Coffee子类对象
        Coffee coffee = null;
        if ("american".equals(type)) {
    
    
            coffee = new AmericanCoffee();
        } else if ("latte".equals(type)) {
    
    
            coffee = new LatteCoffee();
        } else {
    
    
            throw new RuntimeException("对不起,你所点的coffee没有!");
        }

        //加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

It can be seen that if we want to add new coffee types, we must modify the coffee shop class, which violates the principle of opening and closing!

In java, everything is an object, and these objects need to be created. If you directly new the object when you create it, the object will be severely coupled. If we want to replace the object, all the places of the new object need to be modified, which obviously violates the The open-closed principle of software design.

If we use the factory to produce objects, we can only deal with the factory and completely decouple from the object. If we want to replace the object, we can directly replace the object in the factory, achieving the purpose of decoupling from the object

Therefore, the biggest advantage of the factory model is: decoupling

2.2 Simple factory pattern

2.2.1 Structure

A simple factory contains the following roles:

  • 抽象产品: Defines the specification of the product, describing the main features and functions of the product.
  • 具体产品: implement or inherit the subclass of the abstract product
  • 具体工厂: Provides a method to create a product, and the caller obtains the product through this method.

2.2.2 Implementation

Now use a simple factory to improve the above case, the class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-84DWWZJp-1649210957053)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403101317880.png)]

The factory class code is as follows:

public class SimpleCoffeeFactory {
    
    
    public Coffee createCoffee(String type) {
    
    
        Coffee coffee = null;
        if ("american".equals(type)) {
    
    
            coffee = new AmericanCoffee();
        } else if ("latte".equals(type)) {
    
    
            coffee = new LatteCoffee();
        } else {
    
    
            throw new RuntimeException("对不起,您所点的咖啡没有!");
        }
        return coffee;
    }
}

Coffee shop class code modification:

public class CoffeeStore {
    
    
    public Coffee orderCoffee(String type) {
    
    
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        Coffee coffee = factory.createCoffee(type);
        //加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

test:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //创建咖啡店类对象
        CoffeeStore store=new CoffeeStore();
        Coffee coffee = store.orderCoffee("latte");
        System.out.println(coffee.getName());
    }
}

The factory (factory) handles the details of object creation. Once the SimpleCoffeeFactory exists, the orderCoffee() in the CoffeeStore class becomes the client of this object. If you need a Coffee object later, you can directly obtain it from the factory.

In this way, the coupling with the Coffee implementation class is released, and new couplings are generated at the same time, the coupling between the CoffeeStore object and the SimpleCoffeeFactory factory object, and the coupling between the factory object and the commodity object.

If we add new types of coffee later, we will definitely need to modify the code of SimpleCoffeeFactory, which violates the principle of opening and closing. There may be many clients of the factory class, such as creating Meituan Waimai, etc. In this way, only the code of the factory class needs to be modified, and other modification operations are omitted.

2.2.4 Advantages and disadvantages

advantage:

  • The process of creating an object is encapsulated, and the object can be obtained directly through parameters. Separate the object creation from the business logic layer, so as to avoid modifying the customer code in the future. If you want to implement a new product, you can directly modify the factory class without modifying the original code, which reduces the possibility of customer code modification and is easier expand.

shortcoming:

  • When adding new products, it is still necessary to modify the code of the factory class, which violates the "open and close principle".

2.2.5 Static factory

In development, some people also define the function of creating objects in the factory class as static. This is the static factory pattern, and it is not one of the 23 design patterns. code show as below:

public class SimpleCoffeeFactory {
    
    

    public static Coffee createCoffee(String type) {
    
    
        Coffee coffee = null;
        if("americano".equals(type)) {
    
    
            coffee = new AmericanoCoffee();
        } else if("latte".equals(type)) {
    
    
            coffee = new LatteCoffee();
        }
        return coffe;
    }
}

Coffee shop class modification:

public class CoffeeStore {
    
    
    public Coffee orderCoffee(String type) {
    
    
        Coffee coffee = SimpleCoffeeFactory.createCoffee(type);
        //加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

2.3 Factory method pattern

2.3.1 Concept

Factory method pattern:

Define an interface for creating objects and let subclasses decide which product class object to instantiate. Factory methods defer instantiation of a product class to its factory subclasses.

2.3.2 Structure

The main role of the factory method pattern:

  • 抽象工厂: Provides an interface to create a product, through which the caller accesses the factory method of a specific factory to create a product.
  • 具体工厂: It is mainly to realize the abstract method in the abstract factory and complete the creation of specific products.
  • 抽象产品: Defines the specification of the product, describing the main features and functions of the product.
  • 具体产品: It implements the interface defined by the abstract product role, and is created by the specific factory, and it corresponds to the specific factory one-to-one.

2.3.3 Implementation

Use the factory method pattern to improve the above example, the class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-v9HIWblB-1649210957055)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403102639077.png)]

Abstract factory:

public interface CoffeeFactory {
    
    

    Coffee createCoffee();
}	

Specific factory:

public class LatteCoffeeFactory implements CoffeeFactory {
    
    

    public Coffee createCoffee() {
    
    
        return new LatteCoffee();
    }
}

public class AmericanCoffeeFactory implements CoffeeFactory {
    
    

    public Coffee createCoffee() {
    
    
        return new AmericanCoffee();
    }
}

Coffee shop category:

public class CoffeeStore {
    
    
    private CoffeeFactory coffeeFactory;

    public void setCoffeeFactory(CoffeeFactory coffeeFactory){
    
    
        this.coffeeFactory=coffeeFactory;
    }
    //点咖啡功能
    public Coffee orderCoffee() {
    
    
        Coffee coffee = coffeeFactory.createCoffee();
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

test:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //创建咖啡店类对象
        CoffeeStore store=new CoffeeStore();
        CoffeeFactory factory=new AmericanCoffeeFactory();
        store.setCoffeeFactory(factory);
        Coffee coffee = store.orderCoffee();
        System.out.println(coffee.getName());
    }
}

From the code written above, we can see that when adding product categories, factory categories should be added accordingly, and there is no need to modify the code of the factory category , which solves the shortcomings of the simple factory model.

The factory method pattern is a further abstraction of the simple factory pattern. Due to the use of polymorphism, the factory method pattern maintains the advantages of the simple factory pattern and overcomes its shortcomings.

2.3.4 Advantages and disadvantages

advantage:

  • Users only need to know the name of the specific factory to get the product they want, without knowing the specific creation process of the product
  • When adding new products to the system, only specific product categories and corresponding specific factory categories need to be added, without any modification to the original factory, meeting the principle of opening and closing

shortcoming:

  • Every time a product is added, a specific product category and a corresponding specific factory category must be added, which increases the complexity of the system

2.4 Abstract factory pattern

2.4.1 Concept

Abstract factory pattern:

  • It is a pattern structure that provides an access class with an interface to create a group of related or interdependent objects, and the access class can obtain different levels of products of the same family without specifying the specific class of the desired product.
  • The abstract factory pattern is an upgraded version of the factory method pattern. The factory method pattern only produces one level of products, while the abstract factory pattern can produce multiple levels of products.

The factory method model introduced above considers the production of one type of product, such as livestock farms that only raise animals, TV factories that only produce TVs, and Chuanzhi podcasts that only train students majoring in computer software.

These factories only produce the same type of products, and the same type of products are called products of the same level , that is to say: the factory method model only considers the production of products of the same level , but in real life, many factories are comprehensive factories that can produce multiple levels ( types), for example, an electrical appliance factory produces both TV sets and washing machines or air conditioners, and a university has both software majors and biology majors.

The abstract factory pattern will consider the production of multi-level products, and a group of products at different levels produced by the same specific factory is called a product family .

The horizontal axis shown in the figure below is the product grade, that is, the same type of product; the vertical axis is the product family, that is, the products of the same brand, and the products of the same brand are produced in the same factory.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-eqfTyF8A-1649210957057)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403103358521.png)]

2.4.2 Structure

The main roles of the abstract factory pattern are as follows:

  • 抽象工厂: Provides an interface for creating products, which contains multiple methods for creating products, and can create multiple products of different levels.
  • 具体工厂: It is mainly to implement multiple abstract methods in the abstract factory to complete the creation of specific products.
  • 抽象产品: Defines the specification of the product, describes the main features and functions of the product, and the abstract factory pattern has multiple abstract products.
  • 具体产品: It implements the interface defined by the abstract product role, and is created by a specific factory. It has a many-to-one relationship with the specific factory.

2.4.3 Implementation

  1. The current coffee shop business has changed, not only to produce coffee but also to produce desserts, such as tiramisu, matcha mousse, etc.
  2. If you want to follow the factory method pattern, you need to define the tiramisu class, matcha mousse class, tiramisu factory, matcha mousse factory, and dessert factory class, which are prone to explosions.
  3. Among them, latte coffee and American coffee are one product grade, both of which are coffee; tiramisu and matcha mousse are also one product grade; latte coffee and tiramisu are the same product family (that is, both belong to Italian style), Americano and matcha mousse are the same product family (that is, both belong to the American flavor).
  4. So this case can be implemented using the abstract factory pattern.

The class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-iCoXV2Zg-1649210957059)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403104827294.png)]

Dessert abstract class:

public abstract class Dessert {
    
    
    public abstract void show();
}

Dessert implementation class:

public class Trimisu extends Dessert{
    
    
    @Override
    public void show() {
    
    
        System.out.println("提拉米苏");
    }
}
public class MatchaMoousse extends Dessert{
    
    
    @Override
    public void show() {
    
    
        System.out.println("抹茶慕斯");
    }
}

Abstract factory:

public interface DessertFactory {
    
    

    Coffee createCoffee();

    Dessert createDessert();
}

Specific factory:

//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {
    
    

    public Coffee createCoffee() {
    
    
        return new AmericanCoffee();
    }

    public Dessert createDessert() {
    
    
        return new MatchaMousse();
    }
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {
    
    

    public Coffee createCoffee() {
    
    
        return new LatteCoffee();
    }

    public Dessert createDessert() {
    
    
        return new Tiramisu();
    }
}

test:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        ItalyDessertFactory factory=new ItalyDessertFactory();
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
        coffee.addMilk();
        coffee.addSugar();
        System.out.println(coffee.getName());
        dessert.show();
    }
}

If you want to add the same product family, you only need to add a corresponding factory class, and you don't need to modify other classes.

2.4.4 Advantages and disadvantages

advantage:

When multiple objects in a product family are designed to work together, it guarantees that clients always use only objects from the same product family.

shortcoming:

When a new product needs to be added to the product family, all factory classes need to be modified.

2.4.5 Usage Scenarios

  • When the objects to be created are a series of interrelated or interdependent product families, such as TV sets, washing machines, and air conditioners in electrical appliances factories.

  • There are multiple product families in the system, but only one of them is used at a time. For example, someone only likes to wear clothes and shoes of a certain brand.

  • The product class library is provided in the system, and all products have the same interface, and the client does not depend on the creation details and internal structure of the product instance.

Such as: change the skin of the input method, and change the whole set together. Generate programs for different operating systems.

2.5 Pattern extension (simple factory + configuration file decoupling)

The coupling of factory objects and product objects can be decoupled through factory mode + configuration file. Load the full class name in the configuration file in the factory class, and create an object for storage. If the client needs the object, it can be obtained directly.

Step 1: Define the configuration file

For the convenience of demonstration, we use the properties file as the configuration file, namedbean.properties

american=com.xpp.pattern.factory.configFactory.AmericanCoffee
latte=com.xpp.pattern.factory.configFactory.LatteCoffee

Step 2: Improve the factory class

public class CoffeeFactory {
    
    
    //加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储

    //1.定义容器对象存储咖啡对象
    private static Map<String, Coffee> map = new HashMap<String, Coffee>();

    //2.加载配置文件,只需要加载一次
    static {
    
    
        Properties p = new Properties();
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
    
    
            p.load(is);
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
    
    
                String className = p.getProperty((String) key);
                //通过反射创建对象
                Class aClass = Class.forName(className);
                Coffee coffee = (Coffee) aClass.newInstance();
                //将名称和对象存储到容器中
                map.put((String) key, coffee);
            }

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public static Coffee createCoffee(String name) {
    
    
        return map.get(name);
    }
}

test:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Coffee latte = CoffeeFactory.createCoffee("latte");
        System.out.println(latte.getName());
    }
}

Static member variables are used to store created objects (the key stores the name, and the value stores the corresponding object), while reading configuration files and creating objects are written in static code blocks, so that they only need to be executed once.

2.6 JDK source code analysis - Collection.iterator method

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        list.add("令狐冲");
        list.add("风清扬");
        list.add("任我行");

        //获取迭代器对象
        Iterator<String> it = list.iterator();
        //使用迭代器遍历
        while(it.hasNext()) {
    
    
            String ele = it.next();
            System.out.println(ele);
        }
    }
}

You should be familiar with the above code, using iterators to traverse the collection and get the elements in the collection. The method of obtaining iterators for single-column collections uses the factory method pattern. Let's look at the structure through the class diagram:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-mZtMyCZy-1649210957065)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220404090717811.png)]

  • The Collection interface is an abstract factory class
  • ArrayList is a concrete factory class
  • The Iterator interface is an abstract commodity class
  • The Iter inner class in the ArrayList class is a specific commodity class
  • In the specific factory class, the iterator() method creates a specific commodity class object

besides:

  1. The getInstance() method in the DateFormt class uses the factory pattern
  2. The getInstance() method in the Calendar class uses the factory pattern

3. Prototype pattern

3.1 Overview

Use an already created instance as a prototype, and create a new object identical to the prototype object by copying the prototype object.

3.2 Structure

The Prototype pattern contains the following roles:

  • 抽象原型类: Specifies the clone() method that the specific prototype object must implement.
  • 具体原型类: Implement the clone() method of the abstract prototype class, which is an object that can be copied.
  • 访问类: Use the clone() method in the concrete prototype class to copy the new object.

The class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-WPKlqQS4-1649210957068)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403110307782.png)]

3.3 Implementation

The cloning of the prototype mode is divided into shallow cloning and deep cloning:

  • Shallow clone : ​​create a new object, the attributes of the new object are exactly the same as the original object, and for non-basic type attributes, it still points to the memory address of the object pointed to by the original attribute.
  • Deep clone : ​​When a new object is created, other objects referenced in the attribute will also be cloned and no longer point to the original object address.

The Object class in Java provides clone()methods to implement shallow cloning. CloneableThe interface is the abstract prototype class in the above class diagram, and the sub-implementation class that implements the Cloneable interface is the concrete prototype class. code show as below:

Realizetype (concrete prototype class):

public class Realizetype implements Cloneable{
    
    
    public Realizetype(){
    
    
        System.out.println("具体的原型对象创建完成!");
    }
    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
    
    
        System.out.println("具体原型复制成功!");
        return (Realizetype) super.clone();
    }
}

PrototypeTest (test access class):

public class PrototypeTest {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Realizetype r1 = new Realizetype();
        Realizetype r2 = r1.clone();

        System.out.println("对象r1和r2是同一个对象?" + (r1 == r2));//false
    }
}

3.4 Case

Generating "Three Good Students" Award Certificates with Prototype Mode

The "Three Good Students" certificates of the same school are the same except for the names of the winners. You can use the prototype mode to copy multiple "Three Good Students" certificates, and then modify the names on the certificates.

The class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-4OdQAFDK-1649210957070)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403163758324.png)]

code show as below:

public class Citation implements Cloneable {
    
    
    //三好学生的姓名
    private String name;

    public String getName() {
    
    
        return name;
    }

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

    void show() {
    
    
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
    
    
        return (Citation) super.clone();
    }
}

test:

public class CitaionTest {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        //1.创建原型对象
        Citation citation=new Citation();
        //2.克隆奖状对象
        Citation citation1 = citation.clone();

        citation.setName("张三");
        citation1.setName("李四");

        //3.调用show方法展示
        citation.show();
        citation1.show();
    }
}

Output result:

张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!

3.5 Usage Scenarios

  • The creation of objects is very complicated, and you can use the prototype mode to quickly create objects.
  • Performance and security requirements are relatively high.

3.6 Deep Cloning

Change the name attribute of the Citation class in the case of the "Three Good Students" certificate above to an attribute of the Student type. code show as below:

public class Citation implements Cloneable {
    
    
    //三好学生的姓名
    private Student student;

    public Student getStudent() {
    
    
        return student;
    }

    public void setStudent(Student student) {
    
    
        this.student = student;
    }

    void show() {
    
    
        System.out.println(student.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
    
    
        return (Citation) super.clone();
    }
}

Student class:

public class Student {
    
    
    private String name;
	//省略get,set,toString方法
}

test:

public class CitaionTest {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        //1.创建原型对象
        Citation citation = new Citation();
        Student student = new Student();
        student.setName("张三");
        citation.setStudent(student);
        //2.克隆奖状对象
        Citation citation1 = citation.clone();
        Student student1 = citation1.getStudent();
        student1.setName("李四");

        //3.调用show方法展示
        citation.show();
        citation1.show();
    }
}

Output result:

李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!

The student object and the student1 object are the same object, and the value of the name attribute in the student1 object will be changed to "Li Si", and the two Citation (certificate) objects will display Li Si. This is the effect of shallow cloning, copying the attributes of the reference type in the concrete prototype class (Citation). This situation requires the use of deep cloning, and deep cloning requires the use of object streams.

code show as below:

public class CitaionTest {
    
    
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
    
    
        //1.创建原型对象
        Citation citation = new Citation();
        Student student = new Student();
        student.setName("张三");
        citation.setStudent(student);

        //创建对象输出流对象
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:/a.txt"));
        //写对象
        oos.writeObject(citation);
        //释放资源
        oos.close();
        //创建对象输入流对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:/a.txt"));
        //读取对象
        Citation citation1 = (Citation) ois.readObject();
        //释放资源
        ois.close();

        citation1.getStudent().setName("李四");
        citation1.show();
        citation.show();
    }
}

Output result:

李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!

**Note:** The Citation class and the Student class must implement the Serializable interface, otherwise a NotSerializableException will be thrown.

4. Builder mode

4.1 Overview

Separate the construction and representation of a complex object so that the same construction process can create different representations.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-VqdDxrnH-1649210957073)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403164958994.png)]

  • The construction of components (responsible by Builder) and assembly (responsible by Director) are separated. Thus complex objects can be constructed. This pattern is suitable for situations where the construction process of an object is complex.
  • Due to the decoupling of construction and assembly. Different builders and the same assembly can make different objects; the same builder and different assembly sequences can also make different objects. That is to say, the decoupling of the construction algorithm and assembly algorithm is realized, and better reuse is realized.
  • The builder pattern can separate parts from their assembly process and create a complex object step by step. The user only needs to specify the type of the complex object to get the object without knowing the details of its internal construction.

4.2 Structure

The Builder pattern includes the following roles:

  • 抽象建造者类: This interface stipulates the creation of those parts of complex objects, and does not involve the creation of specific component objects.

  • 具体建造者类: Implement the Builder interface to complete the specific creation methods of each component of the complex product. After the construction process is complete, an instance of the product is provided.

  • 产品类: The complex object to create.

  • 指挥者类: Call the specific builder to create each part of the complex object. The director does not involve the information of the specific product, but is only responsible for ensuring that the parts of the object are created completely or in a certain order.

The class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-xEEHtc6u-1649210957080)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403165707770.png)]

4.3 Examples

Create a shared bike

Producing a bicycle is a complex process that includes the production of components such as the frame and saddle. The frame is made of carbon fiber, aluminum alloy and other materials, and the seat is made of rubber, leather and other materials. For the production of bicycles, the builder pattern can be used.

Here Bike is a product, including components such as frame and seat; Builder is an abstract builder, MobikeBuilder and OfoBuilder are concrete builders; Director is a commander. The class diagram is as follows:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Th6trWbp-1649210957083)(../%E9%9D%A2%E8%AF%95%E9%97 %AE%E9%A2%98%E5%90%88%E9%9B%86/%E5%9B%BE%E7%89%87/blog-image-master/img/image-20220403165732847.png)]

Product category:

public class Bike {
    
    
    //车架
    private String frame;
    //车座
    private String seat;

    //get,set省略
}

Abstract builder class:

public abstract class Builder {
    
    

    //说明Bike类型,并进行赋值
    protected Bike bike = new Bike();

    public abstract void builderFrame();

    public abstract void builderSeat();

    public abstract Bike createBike();

}

Concrete builder class:

//摩拜单车建造类
public class MobileBuilder extends Builder {
    
    
    @Override
    public void builderFrame() {
    
    
        bike.setFrame("碳纤维车架");
    }

    @Override
    public void builderSeat() {
    
    
        bike.setSeat("真皮车座");
    }

    @Override
    public Bike createBike() {
    
    
        return bike;
    }
}

//Ofo单车建造类
public class OfoBuilder extends Builder {
    
    
    @Override
    public void builderFrame() {
    
    
        bike.setFrame("铝合金车架");
    }

    @Override
    public void builderSeat() {
    
    
        bike.setSeat("橡胶车座");
    }

    @Override
    public Bike createBike() {
    
    
        return bike;
    }
}

Commander class:

public class Director {
    
    
    //声明builder类型的变量
    private Builder builder;

    public Director(Builder builder){
    
    
        this.builder=builder;
    }

    //组装自行车
    public Bike construct(){
    
    
        builder.builderFrame();
        builder.builderSeat();
        return builder.createBike();
    }
}

Test class:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //创建指挥者镀锡
        Director director=new Director(new MobileBuilder());
        //指挥者指挥组装自行车
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
    }
}

Note : The above example is a regular usage of the Builder mode. The director class Director plays a very important role in the builder mode. It is used to guide the specific builder how to build the product, control the order of calls, and return the complete product to the caller kind

But in some cases it is necessary to simplify the system structure, you can combine the commander class with the abstract builder:

// 抽象 builder 类
public abstract class Builder {
    
    

    protected Bike mBike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    public Bike construct() {
    
    
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

Note : This does simplify the system structure, but it also increases the responsibility of the abstract constructor class, and it does not conform to the single responsibility principle. If construct() is too complicated, it is recommended to encapsulate it in Director.

4.4 Advantages and disadvantages

advantage:

  • The encapsulation of the builder pattern is very good . Using the builder mode can effectively encapsulate changes. In the scenario of using the builder mode, the general product class and the builder class are relatively stable. Therefore, encapsulating the main business logic in the commander class can achieve overall Relatively good stability.
  • In the builder mode, the client does not need to know the details of the internal composition of the product, and the product itself is decoupled from the product creation process, so that the same creation process can create different product objects.
  • The product creation process can be controlled more finely . Decomposing the creation steps of complex products into different methods makes the creation process clearer and more convenient to use programs to control the creation process.
  • The builder pattern is easy to extend . If there is a new requirement, it can be completed by implementing a new builder class, basically without modifying the code that has been tested before, so it will not introduce risks to the original function. Comply with the principle of opening and closing.

shortcoming:

The products created by the builder mode generally have more in common, and their components are similar. If the products are very different, it is not suitable to use the builder mode, so its scope of use is limited .

4.5 Usage Scenarios

The builder (Builder) pattern creates complex objects, and the various parts of its products often face drastic changes, but the algorithm that combines them is relatively stable, so it is usually used in the following situations.

  • The created object is complex and consists of multiple parts, each part faces complex changes, but the construction sequence between the components is stable.
  • The algorithm for creating a complex object is independent of the constituent parts of that object and how they are assembled, i.e. the construction process and final representation of the product are independent.

4.6 Schema Extensions

In addition to the above uses, the builder mode also has a common use in development, that is, when a class constructor needs to pass in many parameters, if an instance of this class is created, the code readability will be very poor, and it will be difficult to It is easy to introduce errors, and at this time, the builder pattern can be used for refactoring.

The code before refactoring is as follows:

public class Phone {
    
    
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public Phone(String cpu, String screen, String memory, String mainboard) {
    
    
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

	//省略get,set,toString方法
}

Client code:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //构建Phone对象
        Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
        System.out.println(phone);
    }
}

The above constructs the Phone object in the client code and passes four parameters. What if there are more parameters?

The readability of the code and the cost of using it are relatively high

Code after refactoring:

public class Phone {
    
    
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    //私有构造方法
    private Phone(Builder builder) {
    
    
        cpu = builder.cpu;
        screen = builder.screen;
        memory = builder.memory;
        mainboard = builder.mainboard;
    }

    public static final class Builder {
    
    
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;

        public Builder cpu(String val) {
    
    
            cpu = val;
            //返回this是为了链式调用
            return this;
        }

        public Builder screen(String val) {
    
    
            screen = val;
            return this;
        }

        public Builder memory(String val) {
    
    
            memory = val;
            return this;
        }

        public Builder mainboard(String val) {
    
    
            mainboard = val;
            return this;
        }

        public Phone build(){
    
    
            //把当前的Builder对象传给Phone构造方法
            return new Phone(this);
        }
    }

    @Override
    public String toString() {
    
    
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

Client code:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .mainboard("华硕")
                .memory("金士顿")
                .screen("三星")
                .build();
        System.out.println(phone);
    }
}

It can be seen that the refactored code is more convenient to use and can also improve development efficiency to some extent. From the perspective of software design, the requirements for programmers are relatively high.

5. Creator mode comparison

5.1 Factory method pattern VS builder pattern

  1. The factory method pattern focuses on the creation of the overall object
  2. The builder pattern focuses on the process of component construction, intending to create a complex object through precise construction step by step.

Let's give a simple example to illustrate the difference between the two. If you want to make a superman, if you use the factory method model, you will directly produce a superman with infinite strength, ability to fly, and wear underwear; if you use the builder model, You need to assemble the hands, head, feet, torso and other parts, and then wear the underwear outside, so a superman was born.

5.2 Abstract Factory Pattern VS Builder Pattern

  1. The abstract factory pattern realizes the creation of product families. A product family is such a series of products: a product combination with different classification dimensions. The abstract factory pattern does not need to care about the construction process, but only cares about which products are produced by which factories. .
  2. The builder mode requires building products according to a specified blueprint, and its main purpose is to produce a new product by assembling spare parts.

If the abstract factory pattern is regarded as an auto parts production factory, which produces a product family of products, then the builder pattern is a car assembly factory, which can return a complete car through the assembly of parts.
insert image description here

Guess you like

Origin blog.csdn.net/qq_45966440/article/details/123983475
Recommended