In software engineering, Creative mode is a design mode that deals with object creation, trying to create objects in an appropriate way according to the actual situation.Basic object creation can lead to design problems or increase design complexity.Creative mode solves problems by controlling the creation of objects in some way.
Common creation modes are: singleton mode, factory mode, abstract factory mode, prototype mode, builder mode
1. Single Case Mode
There are eight ways to write a singleton pattern:
- Hungry Han Style:
- static const
- Static Code Block
- Lazy:
- Thread insecurity
- Thread Security, Synchronization Method
- Thread Safe, Synchronize Code Blocks
- duplication check
- Static Internal Class
- enumeration
Scenarios for using the singleton mode:
Objects that need to be created and destroyed frequently; objects that take too much time to create or consume too many resources but are often used (such as session factories, data sources, etc.)
1. Hungry-Han Style-Static Constant Writing
Code implementation:
/** * Singleton mode of design mode * Hungry Han Style (Static Constant) */ public class SingletonTest01 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println("Do you get the same instance twice:" + (instance1 == instance2)); //true } } class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Define as constant to keep instance objects constant private final static Singleton instance = new Singleton(); //Get an instance by this method public static Singleton getInstance() { return instance; } }
Analysis:
Advantage:
- Simple to use, creating instance objects while loading classes avoids thread synchronization issues
Disadvantages:
- Creating instance objects when classes are loaded, but not sure when to use them or whether to use them, can cause memory waste
2. Hungry Han-Static Code Block Writing
Code implementation:
/** * Singleton mode of design mode * Hungry Han Style (Static Code Block Writing) */ class Singleton{ //Private construction method so that it cannot be instantiated externally through a constructor private Singleton(){ } //Define as constant to keep instance objects constant private final static Singleton instance; static { instance = new Singleton(); } //Get an instance by this method public static Singleton getInstance(){ return instance; } }
Analysis:
Consistent with static constants, except in a different initialization location, one is initialized in a static block of code, the other is initialized directly at the constant declaration
3. Lazy-Thread Insecurity
Code implementation:
/** * Singleton mode of design mode * Lazy (thread insecure) */ class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Declare instance objects private static Singleton instance; //Get an instance by this method public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Analysis:
Advantage:
- Satisfies the feature of "take with you" and solves the problem of memory waste
Disadvantages:
- Threads are insecure, and when multiple threads access, multiple instances may be created, so they are not available for actual development
4. Lazy-Thread-Safe-Synchronization Method Writing
Code implementation:
/** * Singleton mode of design mode * Lazy (synchronization method) */ class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Declare instance objects private static Singleton instance; //Get an instance by this method public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Analysis:
Although thread insecurity is resolved, locks are too wide and inefficient to be used in development
5. Lazy-Thread-Safe-Synchronized Code Block Writing
Code implementation:
/** * Singleton mode of design mode * Lazy (Synchronize Code Block Writing) */ class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Declare instance objects private static Singleton instance; //Get an instance by this method public static Singleton getInstance() { if (instance == null) { //Synchronization Code synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } }
Analysis:
This reduces the scope of synchronization locks, which are meant to solve efficiency problems, but also causes thread insecurity, so they cannot be used in development
6. Lazy-Dual Check (Recommended)
Code implementation:
/** * Singleton mode of design mode * duplication check */ class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Declare instance objects private static volatile Singleton instance; //Double Judgement + Synchronization Lock public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
Analysis:
It improves efficiency and resolves thread security issues, which is recommended
7. Lazy-static internal class (recommended)
Code implementation:
/** * Singleton mode of design mode * Static Internal Class */ class Singleton { //Private construction method so that it cannot be instantiated externally through a constructor private Singleton() { } //Static Internal Class private static class SingletonInstance{ private final static Singleton INSTANCE = new Singleton(); } //Get Instances public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
Analysis:
The class loading mechanism is used to ensure that there is only one thread to initialize the instance.Singleton classes are not instantiated when loaded, and SingletonInstance is loaded when the getInstance method is called
8. Lazy-enumeration (recommended)
Code implementation:
/** * Singleton mode of design mode * enumeration */ enum Singleton{ INSTANCE; }
Analysis:
Not only does it avoid thread insecurity, it also prevents deserialization from re-creating new objects
2. Factory Mode
1. Simple Factory Mode
1.1 Introduction
Strictly speaking, the simple factory mode is not one of the 23 common design modes, it is only a special implementation of the factory mode.Simple factory mode is used much less in practice than the other two because it only fits many simple situations.
The simple factory mode violates the open-close principle (but can be avoided by reflective mechanisms).Because each time you add a new feature, you need to modify the code and add branching conditions in the raw switch-case statement (or if-else statement).
1.2 Scenarios
- Fewer objects need to be created
- Client does not care about object creation
1.3 Simple Factory Mode Role Assignment
- Factory role: The core of the simple factory pattern, which implements the internal logic for creating all instances.The factory class can be called directly from the outside world to create the desired product object.
- Product role: The parent class of all objects created by simple factory mode that describes the common interfaces common to all instances.
- Specific Product role: The goal of creating a simple factory pattern where all created objects are instances of a specific class acting as this role.
1.4 Simple Factory Mode Code Implementation
Create a new abstract product, Shape
public interface Shape { void draw(); }
Specific products Circle, Square, Shape interface
public class Circle implements Shape { @Override public void draw() { System.out.println("circular"); } } public class Square implements Shape { @Override public void draw() { System.out.println("Square"); } }
Factory ShapeFactory
public class ShapeFactory { public static Shape getShape(String name){ if(name == null){ return null; } if ("SQUARE".equalsIgnoreCase(name)){ return new Square(); }else if("CIRCLE".equalsIgnoreCase(name)){ return new Circle(); }else{ return null; } } }
Client
public class Client { public static void main(String[] args) { Shape circle = ShapeFactory.getShape("circle"); circle.draw(); Shape square = ShapeFactory.getShape("square"); square.draw(); } }
Run Results
circular Square
Although the simple factory mode is implemented, when we add a requirement, we need to modify the code of the ShapeFactory class, which violates the open-close principle and we can rewrite the factory method in a reflective way
public class ShapeFactory2 { public static Object getClass(Class<? extends Shape> clazz){ if (clazz == null){ return null; } try { return clazz.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } }
test
public class Client2 { public static void main(String[] args) { Circle circle = (Circle) ShapeFactory2.getClass(Circle.class); circle.draw(); Square square = (Square) ShapeFactory2.getClass(Square.class); square.draw(); } }
Run Results
circular Square
2. Factory Method Mode
2.1 Introduction
The factory method mode should be the most used mode in the factory mode family, which is the most common mode in general projects.
The factory method mode is a step-by-step deepening of simple factories. In the factory method mode, we no longer provide a uniform factory class to create all objects, but provide different factories for different objects.That is, each object has a corresponding factory.
2.2 Scenarios
-
A class does not know the class of the object it needs: in the factory method mode, the client does not need to know the class name of the specific product class, only the corresponding factory, the specific product object is created by the specific factory class, and the client needs to know the factory class that created the specific product.
-
A class specifies which object to create through its subclasses:In the factory method mode, an abstract factory class only needs to provide an interface to create a product, and its subclasses determine the specific object to create, utilizing object-oriented polymorphisms and Richter's
-
Delegate the task of creating objects to one of several factory subclasses. Clients can create product subclasses in a factory subclass without having to worry about which factory subclass they use. They can specify dynamically when needed, and store the class names of specific factory classes in a configuration file or database.
2.3 Factory Method Mode Role Assignment
- Abstract Factory role: is the core of the factory method pattern and is application independent.Any factory class of objects created in the schema must implement this interface.
- Concrete Factory role: This is a specific factory class that implements an abstract factory interface, contains logic closely related to the application, and is invoked by the application to create a product object.
- Abstract Product role: The supertype of the object created by the factory method pattern, that is, the common parent or interface of the product object.
- Concrete Product role: This role implements the interfaces defined by the abstract product role.A specific product is created by a specific factory, and there is often a one-to-one correspondence between them
2.4 Factory Method Mode Code Implementation
Abstract Factory
public interface Factory { Shape getShape(); }
Specific factories CircleFactory, SquareFactory
public class CircleFactory implements Factory { @Override public Shape getShape() { return new Circle(); } } public class SquareFactory implements Factory { @Override public Shape getShape() { return new Square(); } }
Abstract and concrete products continue to use classes in simple factory mode
Client
public class Client { public static void main(String[] args) { Shape circle = new CircleFactory().getShape(); circle.draw(); Shape square = new SquareFactory().getShape(); square.draw(); } }
Run Results
circular Square
3. Abstract Factory Mode
3.1 Introduction
In fact, we have a latent consciousness in the mode of factory method.That is, we produce the same kind of products.The abstract factory model is a step-by-step deepening of the factory approach in which a factory class can create a set of products rather than just one product.
The factory is abstracted into two layers, the Abstract Factory and the concrete implementation of the factory subclass.Programmers can use the corresponding factory subclass based on the type of object they create.This turns a single simple factory class into a cluster of factories, which is more conducive to code maintenance and expansion.
3.2 Scenarios
-
As with the factory method, the client does not need to know the class of the object it creates.
-
When a set of objects is needed to accomplish a function together, there may be situations where multiple sets of objects accomplish different functions.(products belonging to the same product family)
-
The system structure is stable and objects are not frequently added.(because the original code needs to be modified as soon as it is added, which does not conform to the open-close principle)
3.3 Role Assignment in Abstract Factory Mode
- Abstract Factory role: is the core of the factory method pattern and is application independent.Any factory class of objects created in the schema must implement this interface.
- Specific Factory Role: This is a specific factory class that implements an abstract factory interface, contains logic closely related to the application, and is invoked by the application to create a product object.
- Abstract Product role: The supertype of the object created by the factory method pattern, that is, the common parent or interface of the product object.
- Specific Product role: Any product object created by the abstract factory pattern is an instance of a specific product class.Products created in abstract factories belong to the same product family, which is different from factories in factory mode where only a single product is created.
3.4 What is the difference between an abstract factory and a factory in the factory method?
An abstract factory is one that produces a complete set of products (at least two products), which must be related or dependent on each other, while a factory in the factory method is one that produces a single product.
3.5 Code implementation of abstract factory pattern
Abstract products: Gun, Bullet
public interface Gun { void shooting(); } public interface Bullet { void loading(); }
Specific products: AK47, AK47Bullet, M16, M16Bullet
public class AK47 implements Gun { @Override public void shooting() { System.out.println("AK47 Shooting"); } } public class AK47Bullet implements Bullet { @Override public void loading() { System.out.println("AK47 Loading bullets"); } } public class M16 implements Gun { @Override public void shooting() { System.out.println("M16 Shooting"); } } public class M16Bullet implements Bullet { @Override public void loading() { System.out.println("M16 Loading bullets"); } }
Abstract Factory: ArmsFactory
public interface ArmsFactory { Gun produceGun(); Bullet produceBullet(); }
Specific factories:
public class AK47Factory implements ArmsFactory{ @Override public Gun produceGun() { return new AK47(); } @Override public Bullet produceBullet() { return new AK47Bullet(); } } public class M16Factory implements ArmsFactory{ @Override public Gun produceGun() { return new M16(); } @Override public Bullet produceBullet() { return new M16Bullet(); } }
test
public class Client { public static void main(String[] args) { ArmsFactory factory; factory = new AK47Factory(); Gun ak47 = factory.produceGun(); Bullet ak47Bullet = factory.produceBullet(); ak47Bullet.loading(); ak47.shooting(); factory = new M16Factory(); Gun m16 = factory.produceGun(); Bullet m16Bullet = factory.produceBullet(); m16Bullet.loading(); m16.shooting(); } }
Result
AK47 Bullets AK47 Shot M16 Bullets M16 Shot
Reference resources: Deep understanding of factory mode
3. Prototype Model
A prototype pattern is one in which you specify the kind of objects you want to create with a prototype instance and create new objects by copying them.
A prototype pattern is a creative design pattern that allows one object to create another customizable object without knowing the details of how to create it
Copies can be made by rewriting clone, which can be divided into shallow and deep copies
1.Shallow copy:
-
For a member variable of a basic data type, a shallow copy passes the value directly, copying the attribute value to a new object
-
For a member variable of a reference type, shallow copy knowledge copies the reference value (memory address) of the member variable to a new object, thereby causing one object to modify the value and affecting another object
-
Use the default clone() method for shallow copies, for example:
Person = (Person)super.clone()
2.Deep copy:
- Member variable values for all basic data types of replicated objects
- Request storage for all reference type member variables and copy the objects referenced by each reference data type member variable
- Can be achieved by overriding the clone method and object serialization (recommended)
//Using the clone method @Override protected Object clone() throws CloneNotSupportedException { Company company = null; try { company = (Company) super.clone(); //Handling reference types company.employee = (Employee) employee.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return company; }
//Using serialization protected Object deepClone(){ ByteArrayInputStream bis = null; ByteArrayOutputStream bos = null; ObjectInputStream ois = null; ObjectOutputStream oos = null; try { //serialize bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //Deserialize bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return ois.readObject(); }catch (Exception e){ e.printStackTrace(); } return null; }
3. Analysis of prototype patterns:
- When creating new objects is more complex, prototype patterns can be used to simplify the process of creating objects while also improving efficiency.
- Instead of reinitializing the object, dynamically get the state of the object at run time
- If the original object changes (adds or decreases attributes), other cloned objects will also change without modifying the code
- Shallow and deep copies handle reference types differently
4. Builder Model
1. Basic Introduction
- Builder Pattern, also known as Generator Pattern, is an object-building pattern.It can abstract the construction process of complex objects (abstract categories) so that different implementations of this abstract process can construct objects with different representations (attributes).
- Builder mode is a step-by-step process for creating complex objects, which allows users to construct complex objects simply by specifying their type and content, without needing to know the specific building details inside.
2. Four roles of the builder model
- Product role: a specific product object
- Builder: Creates an interface or abstract class specified by each part of a Product Object
- Concrete Builder: Implement interfaces, build and assemble components.
- Director: Build an object that uses the Builder interface.It is mainly used to create a complex object.It has two main functions: one is to isolate the production process of customers and objects, and the other is to control the production process of product objects.
3. Principle class diagram:
4. Application of Builder Mode in JDK-StringBuilder
In the StringBuilder inheritance relationship:
StringBuilder inherits AbstractStringBuilder, which implements the Appendable interface
Role Analysis:
- Abstract Builder: Appendable
- Specific builder: AbstractStringBuilder
- Commander: StringBuilder
5. Builder mode considerations and details
- Clients (users) do not have to know the details of the internal composition of the product to decouple the product itself from the creation process so that the same creation process can create different product objects
- Each individual builder is relatively independent and independent of other specific builders, so it is easy to replace or add new specific builders, and users can get different product objects from different specific builders.
- You can control the process of creating a product more finely.Decomposition the creation steps of complex products into different methods to make the creation process clearer and easier to use programs to control the creation process
- Adding new concrete builders without modifying the code of the original class library. The commander class is programmed for the abstract builder class. The system is easy to expand and complies with the Open and Close Principle.
6. Builder Mode Code Implementation
Specific products: House
public class House { private String basic; private String wall; private String roofed; //Omit getter setter toString() method }
Abstract Builder: HouseBuilder
public abstract class HouseBuilder { protected House house = new House(); public abstract void buildBasic(); public abstract void buildWall(); public abstract void buildRoof(); public House buildHouse() { return house; } }
Specific builders: CommonHouse, HighHouse
public class CommonHouse extends HouseBuilder { @Override public void buildBasic() { System.out.println("Play 10 m Foundation"); } @Override public void buildWall() { System.out.println("Masonry 20 cm Wall of"); } @Override public void buildRoof() { System.out.println("Tile top"); } } public class HighHouse extends HouseBuilder { @Override public void buildBasic() { System.out.println("Play 20 m Foundation"); } @Override public void buildWall() { System.out.println("Masonry 50 cm Wall of"); } @Override public void buildRoof() { System.out.println("Glass top"); } }
Commander: HouseDirector
public class HouseDirector { private HouseBuilder builder; public HouseDirector(HouseBuilder builder) { this.builder = builder; } public House build(){ builder.buildBasic(); builder.buildWall(); builder.buildRoof(); return builder.buildHouse(); } }
Test:
public class Client { public static void main(String[] args) { HouseDirector builderDirector1 = new HouseDirector(new CommonHouse()); builderDirector1.build(); System.out.println("---------"); HouseDirector builderDirector2 = new HouseDirector(new HighHouse()); builderDirector2.build(); } }
Result:
Play 10m Foundation 20 cm wall Tile top --------- Hit 20m Foundation Build a 50 cm wall Glass top