One article finishes the common design patterns of Java (23 kinds in total)

introduce

The origins of design patterns can be traced back to the 1980s, when object-oriented programming became popular. During this period, some software developers started noticing that they encountered the same problems in different projects, and they started looking for reusable solutions. These solutions are called design patterns. The earliest people who proposed design patterns were Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. They published a book called "Design Patterns: The Foundation of Reusable Object-Oriented Software" in 1994. This book has become a classic in the field of design patterns. Since then, design patterns have become a very important concept in software development and are widely used in different programming languages ​​and development frameworks.

The four authors are collectively called GOF (Gang of Four). The design patterns they proposed are mainly based on the following object-oriented design principles.

  • Program to the interface rather than the implementation.
  • Prefer object composition over inheritance.

Design patterns are reusable solutions to common software design problems. They provide a way to solve problems that developers often encounter when designing software applications. There are several types of design patterns, including creational, structural, and behavioral patterns.

  • Creational patterns are used to create objects in a flexible and efficient manner. Including Singleton mode, factory mode and abstract factory mode.
  • Structural patterns are used to combine classes and objects to form larger structures. Including adapter mode, bridge mode and decorator mode, etc.
  • Behavioral patterns are used to deal with communication and control flow between classes or objects. Including observer mode, strategy mode and template method mode.

Design patterns are very useful tools in software development, which can improve the reusability and maintainability of code, and at the same time, it can improve the productivity of developers.

Six Principles of Design Patterns

  1. Single Responsibility Principle (SRP): A class should have only one reason to change it.
  2. Open Closed Principle (OCP): Software entities (classes, modules, functions, etc.) should be open for extension and closed for modification.
  3. Liskov Substitution Principle (LSP): Subtypes must be able to replace their supertypes.
  4. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules, both should depend on abstract interfaces; abstract interfaces should not depend on concrete implementations, and concrete implementations should depend on abstract interfaces.
  5. Interface Segregation Principle (ISP): A class should not be forced to implement an interface it doesn't need, the interface should be split into smaller and more specific parts so that clients only need to know the parts they are interested in.
  6. Law of Demeter (LOD): An object should know as little as possible about other objects, often called the "principle of least knowledge".

1. Creational mode

1. Singleton Pattern

Singleton Pattern (Singleton Pattern) is one of the simplest design patterns in Java, which has the following characteristics:

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

1) Hungry Chinese style

This method is more commonly used, but it is easy to generate garbage objects
. Advantages: Without locking, the execution efficiency will be improved.
Disadvantages: Initialize when the class is loaded, wasting memory.

public class Singleton {
    
    
    private static Singleton instance = new Singleton();
    private Singleton() {
    
    }
    public static Singleton getInstance() {
    
    
        return instance;
    }
}

2) Lazy style, double check lock

Add double-check locks on the basis of the lazy man to ensure thread safety and performance.

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

3) Static inner class

Use a static inner class to implement the lazy singleton pattern to ensure thread safety and performance. This method can achieve the same effect as the double-check lock method, but the implementation is simpler.

public class Singleton {
    
    
    private Singleton() {
    
    }
    private static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

4) enumeration

Use enumeration to implement singleton mode, ensure thread safety and prevent reflection attacks.
1) Convert the existing class into an enumeration singleton
PersonEnum.INSTANCE.getInstance()

@Data
public class Person {
    
    
    private String name;
    private int age;
}

public enum PersonEnum {
    
    
    INSTANCE;

    private Person instance;
    private PersonEnum(){
    
    
        instance = new Person();
    }

    public Person getInstance() {
    
    
        return instance;
    }
}

2) Directly create a new enumeration class
PersonSingleton.INSTANCE

public enum PersonSingleton {
    
    
    INSTANCE;

    private String name;
    public String getName() {
    
    
        return name;
    }
}

2. Prototype Pattern

The Prototype design pattern allows creating new objects by copying existing objects instead of instantiating a class.
Very useful when you need to create a large number of similar objects, it can avoid repeated creation of objects, thereby improving performance, and can implement shallow or deep copies as needed.
In Java, the implementation of the prototype pattern usually involves implementing the Cloneable interface and overriding the clone() method.
prototype pattern

public abstract class Shape implements Cloneable {
    
    
    private String id;
    protected String type;
    public String getId() {
    
    
        return id;
    }
    public void setId(String id) {
    
    
        this.id = id;
    }
    public String getType() {
    
    
        return type;
    }
    public void setType(String type) {
    
    
        this.type = type;
    }
    public abstract void draw();
    @Override
    public Object clone() {
    
    
        Object clone = null;
        try {
    
    
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
        return clone;
    }
}
public class Circle extends Shape {
    
    
    public Circle() {
    
    
        type = "Circle";
    }
    @Override
    public void draw() {
    
    
        System.out.println("Inside Circle::draw() method.");
    }
}
public class Square extends Shape {
    
    
    public Square() {
    
    
        type = "Square";
    }
    @Override
    public void draw() {
    
    
        System.out.println("Inside Square::draw() method.");
    }
}
public class ShapeCache {
    
    
    private static Map<String, Shape> shapeMap = new HashMap<>();
    public static Shape getShape(String shapeId) {
    
    
        Shape cachedShape = shapeMap.get(shapeId);
        return (Shape) cachedShape.clone();
    }
    // For each shape run database query and create shape
    // shapeMap.put(shapeKey, shape);
    // for example, we are adding three shapes
    public static void loadCache() {
    
    
        Circle circle = new Circle();
        circle.setId("1");
        shapeMap.put(circle.getId(), circle);
        Square square = new Square();
        square.setId("2");
        shapeMap.put(square.getId(), square);
    }
}
public class PrototypePatternDemo {
    
    
    public static void main(String[] args) {
    
    
        ShapeCache.loadCache();
        Shape clonedShape = ShapeCache.getShape("1");
        System.out.println("Shape : " + clonedShape.getType());
        Shape clonedShape2 = ShapeCache.getShape("2");
        System.out.println("Shape : " + clonedShape2.getType());
    }
}

In the above code, Shape is an abstract class that implements the Cloneable interface and overrides the clone() method. Circle and Square are concrete subclasses of Shape that implement the draw() method. The ShapeCache class is a cache that stores copies of Shape objects. The PrototypePatternDemo class is a demo class that uses the ShapeCache to get a copy of the Shape object.

In the loadCache() method, we create two copies of the Shape object and store them in the shapeMap. In the main() method, we use the getShape() method to get a copy of the Shape objects and output their types. Since we are using the prototype pattern, we can create new objects by copying existing objects without instantiating the class.

3. Factory Pattern

Create objects through a factory class without directly exposing object creation logic to clients.
The advantage of the simple factory model is that the client does not need to know the details of the creation of specific product classes, but only needs to create objects through the factory class, and the factory class can dynamically create different types of objects according to the needs of the client. But the disadvantages are also obvious. If there are many product classes to be created, the code of the factory class will become bloated and not easy to maintain.
Simple Factory Pattern

abstract class Animal {
    
    
    public abstract void sound();
}
class Cat extends Animal {
    
    
    @Override
    public void sound() {
    
    
        System.out.println("喵喵喵");
    }
}
class Dog extends Animal {
    
    
    @Override
    public void sound() {
    
    
        System.out.println("汪汪汪");
    }
}
// 创建一个工厂类
class AnimalFactory {
    
    
    // 定义一个静态方法,根据传入的参数创建具体的产品类对象
    public static Animal createAnimal(String type) {
    
    
        if (type.equalsIgnoreCase("dog")) {
    
    
            return new Dog();
        } else if (type.equalsIgnoreCase("cat")) {
    
    
            return new Cat();
        } else {
    
    
            throw new IllegalArgumentException("Invalid animal type: " + type);
        }
    }
}
// 客户端代码
public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 使用工厂类创建不同的 Animal 对象
        Animal dog = AnimalFactory.createAnimal("dog");
        dog.sound();
        Animal cat = AnimalFactory.createAnimal("cat");
        cat.sound();
    }
}

4. Abstract Factory Pattern

Objects are created by defining an interface for creating them, but leaving the implementation decision to subclasses.
In the Abstract Factory pattern, interfaces are factories responsible for creating a related object without specifying their class explicitly. Each generated factory can provide objects according to the factory pattern.
abstract factory pattern

// 创建一个抽象产品类
abstract class Animal {
    
    
    public abstract void sound();
}
class Cat extends Animal {
    
    
    @Override
    public void sound() {
    
    
        System.out.println("喵喵喵");
    }
}
// 创建具体产品类,继承自 Animal 类
class Dog extends Animal {
    
    
    @Override
    public void sound() {
    
    
        System.out.println("汪汪汪");
    }
}

abstract class AnimalFactory {
    
    
    // 定义一个抽象方法,用于创建 Animal 对象
    public abstract Animal createAnimal();
}
class CatFactory extends AnimalFactory {
    
    
    @Override
    public Animal createAnimal() {
    
    
        return new Cat();
    }
}
// 创建具体工厂类,实现创建 Animal 对象的接口
class DogFactory extends AnimalFactory {
    
    
    @Override
    public Animal createAnimal() {
    
    
        return new Dog();
    }
}
// 客户端代码
public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个 Dog 对象
        AnimalFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.createAnimal();
        dog.sound();

        // 创建一个 Cat 对象
        AnimalFactory catFactory = new CatFactory();
        Animal cat = catFactory.createAnimal();
        cat.sound();
    }
}

5. Builder Pattern

The builder pattern is a creational design pattern that allows you to create different types of objects by building complex objects step by step. It uses a builder class to encapsulate the object creation process and break it down into simple steps. This allows you to create different types of objects by changing these steps.
builder mode

In this example, we create the Car class as the complex object we want to build. We then created the CarBuilder class, which incrementally builds the Car object and returns it at the end. Finally, we use CarBuilder to build the Car object.

public class Car {
    
    
    private String make;
    private String model;
    private int year;
    private String engine;
    private int seats;
    public Car(String make, String model, int year, String engine, int seats) {
    
    
        this.make = make;
        this.model = model;
        this.year = year;
        this.engine = engine;
        this.seats = seats;
    }
	// ... getter setter ... //
}
public class CarBuilder {
    
    
    private String make;
    private String model;
    private int year;
    private String engine;
    private int seats;

    public CarBuilder setMake(String make) {
    
    
        this.make = make;
        return this;
    }

    public CarBuilder setModel(String model) {
    
    
        this.model = model;
        return this;
    }

    public CarBuilder setYear(int year) {
    
    
        this.year = year;
        return this;
    }

    public CarBuilder setEngine(String engine) {
    
    
        this.engine = engine;
        return this;
    }

    public CarBuilder setSeats(int seats) {
    
    
        this.seats = seats;
        return this;
    }

    public Car build() {
    
    
        return new Car(make, model, year, engine, seats);
    }
}

2. Structural model

1. Adapter Pattern

The Java adapter pattern is a structural design pattern that allows communication between incompatible interfaces. The Adapter pattern does this by converting the interface of one class to another interface that clients expect. This pattern enables the reuse of existing classes without modifying existing code.
Adapter pattern helps us to reuse existing classes without modifying existing code and enables communication between incompatible interfaces.

Main function: The adapter mode converts the interface of a class into another interface that the customer wants, so that those classes that could not work together due to interface incompatibility can work together.

In the following example, we have a target interface Target, a source interface Adaptee and an adapter class Adapter. The adapter class implements the target interface and forwards the request to the specific request method of the source interface. Client code creates an adapter object and uses it to call methods of the target interface.
adapter pattern

// 源接口
public class Adaptee {
    
    
    public void specificRequest() {
    
    
        System.out.println("Adaptee's specific request");
    }
}
public interface Target {
    
    
    public void request();
}
// 适配器类
public class Adapter implements Target {
    
    
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
    
    
        this.adaptee = adaptee;
    }

    public void request() {
    
    
        adaptee.specificRequest();
    }
}
// 客户端代码
public class Client {
    
    
    public static void main(String[] args) {
    
    
        Adaptee adaptee = new Adaptee();
        Target target = new Adapter(adaptee);
        target.request();
    }
}

2. Composite Pattern

The Composite pattern is a structural design pattern that allows you to compose objects into tree structures to represent "part-whole" hierarchies. This pattern allows clients to treat individual objects and object combinations uniformly.

In composite mode, there are two basic types of objects: leaf nodes and composite nodes. A leaf node represents a single object in the tree structure, while a composite node represents a group of objects in the tree structure. Composition nodes can contain other composition nodes and/or leaf nodes, forming a recursive tree structure.
combination mode

The following is a sample code that uses Java to implement the combination mode, in which we use an abstract class Component to represent the nodes in the tree structure, including leaf nodes and combination nodes. A composite node contains child nodes, and child nodes can be added and removed through the add() and remove() methods. Leaf nodes have no child nodes, but can implement common operation methods. The specific combination node inherits from the Component class and realizes its own operation method.

public interface IComponent {
    
    
   void display();
}

// Component.java
public abstract class Component implements IComponent {
    
    
    protected String name;

    public Component(String name) {
    
    
        this.name = name;
    }

    public abstract void add(IComponent component);

    public abstract void remove(IComponent component);

}
// Composite.java
public class Composite extends Component {
    
    
    private List<IComponent> children = new ArrayList<>();

    public Composite(String name) {
    
    
        super(name);
    }

    @Override
    public void add(IComponent component) {
    
    
        children.add(component);
    }

    @Override
    public void remove(IComponent component) {
    
    
        children.remove(component);
    }

    @Override
    public void display() {
    
    
        System.out.println("Composite: " + name);
        for (IComponent component : children) {
    
    
            component.display();
        }
    }
}
// Leaf.java
public class Leaf implements IComponent {
    
    
    private String name;

    public Leaf(String name) {
    
    
        this.name = name;
    }

    @Override
    public void display() {
    
    
        System.out.println("Leaf: " + name);
    }
}
// Client.java
public class Client {
    
    
    public static void main(String[] args) {
    
    
        Component root = new Composite("root");
        Component branch1 = new Composite("branch1");
        Component branch2 = new Composite("branch2");
        Component leaf1 = new Leaf("leaf1");
        Component leaf2 = new Leaf("leaf2");
        Component leaf3 = new Leaf("leaf3");
        root.add(branch1);
        root.add(branch2);
        branch1.add(leaf1);
        branch2.add(leaf2);
        branch2.add(leaf3);
        root.display();
    }
}

3. Decorator Pattern

The decorator pattern is a structural design pattern that allows you to add behavior to a single object, statically or dynamically, without affecting the behavior of other objects of the same class. This pattern is useful when you want the ability to add or remove objects at runtime, or when you want to reduce the number of subclasses required to create combinations of different behaviors.

In Java, the decorator pattern is implemented using a combination of inheritance and composition. Specifically, you create a base class or interface to define the core behavior of the object, and then create one or more decorator classes to add additional behavior to the object. Each decorator class has a reference to the object it decorates, and it can modify the object's behavior before or after delegating to the object's original behavior.

The decorator pattern is suitable for the following scenarios:

  1. Add new functionality to existing classes without modifying existing code.
  2. Add new behavior to objects dynamically at runtime.
  3. Combining objects in different ways to achieve different behaviors.

There are a few things to keep in mind when using the decorator pattern:

  1. The decorator class needs to implement the same interface as the decorated object so that the decorated object can be wrapped.
  2. A decorator class should add new behavior before or after invoking the method of the decorated object.
  3. Do not create too many decorator objects, otherwise the code will become complicated and difficult to maintain.
    decorator pattern
public interface Pizza {
    
    
    public String getDescription();
    public double getCost();
}
 // 具体组件
public class PlainPizza implements Pizza {
    
    
    public String getDescription() {
    
    
        return "薄饼";
    }
    public double getCost() {
    
    
        return 4.00;
    }
}
 // 装饰器
public abstract class ToppingDecorator implements Pizza {
    
    
    protected Pizza pizza;
    public ToppingDecorator(Pizza pizza) {
    
    
        this.pizza = pizza;
    }
    public String getDescription() {
    
    
        return pizza.getDescription();
    }
    public double getCost() {
    
    
        return pizza.getCost();
    }
}
 // 具体装饰器
public class Cheese extends ToppingDecorator {
    
    
    public Cheese(Pizza pizza) {
    
    
        super(pizza);
    }
    public String getDescription() {
    
    
        return pizza.getDescription() + ",马苏里拉奶酪";
    }
    public double getCost() {
    
    
        return pizza.getCost() + 0.50;
    }
}
 // 具体装饰器
public class Pepperoni extends ToppingDecorator {
    
    
    public Pepperoni(Pizza pizza) {
    
    
        super(pizza);
    }
    public String getDescription() {
    
    
        return pizza.getDescription() + ",意大利辣香肠";
    }
    public double getCost() {
    
    
        return pizza.getCost() + 1.00;
    }
}
 // 客户端代码
public class PizzaShop {
    
    
    public static void main(String[] args) {
    
    
        Pizza pizza = new PlainPizza();
        pizza = new Cheese(pizza);
        pizza = new Pepperoni(pizza);
        System.out.println(pizza.getDescription());
        System.out.println("成本:$" + pizza.getCost());
    }
}

In this example, we have a Pizza interface that defines the core behavior of Pizza, including its description and cost. Then we have a PlainPizza class which implements the Pizza interface.

Next, we create an abstract ToppingDecorator class that implements the Pizza interface and has a reference to the Pizza object it decorates. This allows us to add additional behavior to the Pizza object without modifying the original object.

Finally, we created two concrete decorator classes Cheese and Pepperoni that add additional behavior to the Pizza object. Each decorator class modifies the getDescription() and getCost() methods to add its own behavior before or after delegating to the original object.

In the client code, we create a PlainPizza object and decorate it with Cheese and Pepperoni objects. Then print out Pizza's description and cost to verify that the decorator modifies the behavior of the original object.

4. Facade Pattern

Facade Pattern is a structural design pattern that provides a simple interface to access subsystems in a complex system, thus hiding the complexity of the subsystems. Facade mode is an object-based mode. It decouples the client from the subsystem by creating a facade class, so that the client only needs to interact with the facade class to complete the operation.
appearance mode

class CPU {
    
    
    public void processData() {
    
    
        System.out.println("正在处理数据...");
    }
}
 class Memory {
    
    
    public void load() {
    
    
        System.out.println("正在加载内存...");
    }
}
 class HardDrive {
    
    
    public void readData() {
    
    
        System.out.println("正在读取硬盘数据...");
    }
}
 // 外观类
class ComputerFacade {
    
    
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;
     public ComputerFacade() {
    
    
        cpu = new CPU();
        memory = new Memory();
        hardDrive = new HardDrive();
    }
     public void start() {
    
    
        System.out.println("启动计算机...");
        cpu.processData();
        memory.load();
        hardDrive.readData();
        System.out.println("计算机启动完毕!");
    }
}
 // 客户端代码
public class FacadePatternDemo {
    
    
    public static void main(String[] args) {
    
    
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}

In this example we have three subsystem classes: CPU , Memory and HardDrive . Each class performs a different function of the system, such as processing data, loading memory and reading data from hard disk.

We then created a ComputerFacade class that encapsulates the three subsystem classes and provides a simple start() method that clients can use to start the computer. In the start() method, we call the methods of each subsystem in the correct order to ensure that the computer starts correctly.

Finally, in the client code, we create a ComputerFacade object and use the start() method to start the computer. Since the complexities of the system are hidden by the facade classes, the client code is very simple.

5. Flyweight Pattern

The Flyweight Pattern is a structural design pattern that reduces memory usage and object creation overhead by sharing objects. The Flyweight pattern belongs to the object-type pattern. It manages shared objects by creating a Flyweight factory and returns existing objects when needed, thereby reducing the number of object creation and destruction.
Flyweight mode

interface Shape {
    
    
    void draw();
}
// 具体享元类
class Circle implements Shape {
    
    
    private String color;
    private int x;
    private int y;
    private int radius;
     public Circle(String color) {
    
    
        this.color = color;
    }
     public void setX(int x) {
    
    
        this.x = x;
    }
     public void setY(int y) {
    
    
        this.y = y;
    }
     public void setRadius(int radius) {
    
    
        this.radius = radius;
    }
     @Override
    public void draw() {
    
    
        System.out.println("画了一个" + color + "的圆,半径为" + radius + ",位置为(" + x + "," + y + ")");
    }
}
// 享元工厂类
class ShapeFactory {
    
    
    private static final Map<String, Shape> circleMap = new HashMap<>();
     public static Shape getCircle(String color) {
    
    
        Circle circle = (Circle) circleMap.get(color);
         if (circle == null) {
    
    
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("创建了一个" + color + "的圆");
        }
         return circle;
    }
}
// 客户端代码
public class FlyweightPatternDemo {
    
    
    private static final String[] colors = {
    
     "红色", "绿色", "蓝色", "黄色", "黑色" };
     public static void main(String[] args) {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            Circle circle = (Circle) ShapeFactory.getCircle(getRandomColor());
            circle.setX(getRandomX());
            circle.setY(getRandomY());
            circle.setRadius(100);
            circle.draw();
        }
    }
     private static String getRandomColor() {
    
    
        return colors[(int) (Math.random() * colors.length)];
    }
     private static int getRandomX() {
    
    
        return (int) (Math.random() * 100);
    }
     private static int getRandomY() {
    
    
        return (int) (Math.random() * 100);
    }
}

In this example, we have a Shape interface that defines a draw() method for drawing shapes.

Then, we created a specific flyweight class Circle, which implements the Shape interface and stores information such as the color, position and radius of the circle.
Next, we created a flyweight factory class ShapeFactory, which maintains a Map for storing created circle objects. When the client needs to draw a circle, we can get the existing circle object through ShapeFactory, if it does not exist, create a new circle object and store it in the Map.

Finally, in the client code, we randomly generate 20 circles, each with a random color, position, and radius. Get the circle object through ShapeFactory, and call its draw() method to draw the shape. Since circle objects of the same color are shared, memory usage and object creation overhead are reduced.

6. Proxy Pattern

The proxy pattern is a design pattern commonly used in projects. Provides a way to access the target object indirectly; that is, through the proxy object to access the target object.
The advantage of this is that, without changing the original target object, additional extension functions can be added to the target object.
The proxy mode is divided into three implementation methods: static proxy, jdk dynamic proxy, and cglib dynamic proxy.

The three implementation methods have their own advantages and applicable scenarios:

  • Static proxy: The proxy class must be very clear, so it cannot be universal, but it is also the most efficient
  • jdk dynamic proxy: must be based on interface proxy, which has certain limitations; dynamically generate bytecode files, which can be used for general business (performance logs, etc.)
  • cglig dynamic proxy: It also dynamically generates bytecode files, and the generated proxy class inherits the target object
  • The default proxy strategy of spring aop is: if the target object implements the interface, use jdk dynamic proxy, otherwise use cglib proxy
  • After jdk8, the efficiency of jdk dynamic proxy is higher than that of cglib proxy

1) Static proxy

The proxy object and the proxy object need to implement the same interface or inherit the same parent class, so an interface or abstract class must be defined.

/**代理接口*/
public interface IHello {
    
    
    String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
    
    
    @Override
    public String hi(String key) {
    
    
        String str = "hello:" + key;
        System.out.println("HelloImpl! " + str);
        return str;
    }
}
/**静态代理类*/
public class HelloStaticProxy implements IHello {
    
    

    private IHello hello;

    public HelloStaticProxy(IHello hello) {
    
    
        this.hello = hello;
    }

    @Override
    public String hi(String key) {
    
    
        System.out.println(">>> static proxy start");
        String result = hello.hi(key);
        System.out.println(">>> static proxy end");
        return result;
    }
}
/**测试*/
public class DemoTest {
    
    

    public static void main(String[] args) {
    
    
        IHello helloProxy = new HelloStaticProxy(new HelloImpl());
        helloProxy.hi("world");
    }
}

2) jdk dynamic proxy

jdk dynamic proxy is an interface-based proxy method, and the target object must implement the interface.

The principle is that by using the reflection mechanism, an anonymous class is dynamically generated to inherit the Proxy class and implement the interface to be proxied. Since java does not support multiple inheritance, the JDK dynamic proxy cannot proxy the class.

/**代理接口*/
public interface IHello {
    
    
    String hi(String key);
}
/**代理接口实现类*/
public class HelloImpl implements IHello {
    
    
    @Override
    public String hi(String key) {
    
    
        String str = "hello:" + key;
        System.out.println("HelloImpl! " + str);
        return str;
    }
}


/**jdk动态代理类*/
public class JdkProxy implements InvocationHandler {
    
    

    private Object target;

    public JdkProxy(Object target) {
    
    
        this.target = target;
    }

    /**
     * 获取被代理接口实例对象
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
    
    
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println(">>> JdkProxy start");
        Object result = method.invoke(target, args);
        System.out.println(">>> JdkProxy end");
        return result;
    }
}

/**测试*/
public class Demo2Test {
    
    

    public static void main(String[] args) {
    
    
        JdkProxy proxy = new JdkProxy(new HelloImpl());
        IHello helloProxy = proxy.getProxy();
        helloProxy.hi(" jdk proxy !");
    }
}

3) cglib dynamic proxy

The target object does not need to implement the interface, and cannot act as a proxy for the final class.
The principle is that the dynamically generated class inherits the target object. The corresponding jar package must be introduced to use cglib

 <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.7</version>
</dependency>
/**目标类*/
public class HelloImpl {
    
    
    public String hi(String key) {
    
    
        String str = "hello:" + key;
        System.out.println("HelloImpl! " + str);
        return str;
    }
}

/**cglib代理类*/
public class CglibProxy implements InvocationHandler {
    
    

    private Object target;

    /**
     * 获取被代理接口实例对象
     */
    public <T> T getProxy() {
    
    
        //1创建增强器对象
        Enhancer e = new Enhancer();
        //2设置增强器的类加载器
        e.setClassLoader(target.getClass().getClassLoader());
        //3设置代理对象父类类型
        e.setSuperclass(target.getClass());
        //4设置回调函数
        e.setCallback(this);
        //5创建代理对象
        return (T) e.create();
    }

    public CglibProxy(Object target) {
    
    
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println(">>> cglib start");
        Object obj = method.invoke(target, args);
        System.out.println(">>> cglib end");
        return obj;
    }
}

/**测试*/
public class Demo3Test {
    
    

    public static void main(String[] args) {
    
    
        HelloImpl hello = new HelloImpl();
        CglibProxy cglibProxy = new CglibProxy(hello);
        HelloImpl proxy = cglibProxy.getProxy();
        proxy.hi(" cglib ");
    }
}

7. Bridge Pattern

The bridge pattern is a design pattern that decouples abstraction and implementation. It uses basic technologies such as encapsulation, aggregation, and inheritance to bridge two independently changing dimensions in an abstract way, thereby reducing the degree of coupling between them and making the system more flexible.
bridge mode

The following is a sample code for the bridge pattern implementation in Java:

// 首先,我们定义一个 Color 接口,它表示颜色:
public interface Color {
    
    
    void applyColor();
}
// 然后,我们定义一个 Shape 抽象类,它包含了一个 Color 对象:
public abstract class Shape {
    
    
    protected Color color;
    public Shape(Color color) {
    
    
        this.color = color;
    }
    public abstract void applyColor();
}
// 接下来,我们定义两个实现了 Color 接口的具体类:
public class Red implements Color {
    
    
    @Override
    public void applyColor() {
    
    
        System.out.println("Applying red color");
    }
}

public class Blue implements Color {
    
    
    @Override
    public void applyColor() {
    
    
    System.out.println("Applying blue color");
    }
}
// 最后,我们定义两个实现了 Shape 抽象类的具体类:
public class Circle extends Shape {
    
    
    public Circle(Color color) {
    
    
        super(color);
    }
    @Override
    public void applyColor() {
    
    
        System.out.print("Circle applying color: ");
        color.applyColor();
    }
}

public class Square extends Shape {
    
    
    public Square(Color color) {
    
    
        super(color);
    }
    @Override
    public void applyColor() {
    
    
        System.out.print("Square applying color: ");
        color.applyColor();
    }
}
// 现在,我们可以使用这些类来创建出对应的对象并调用它们的方法:
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Color blue = new Blue();
        Shape square = new Square(new Red());
        Shape circle = new Circle(blue);
        square.applyColor();
        circle.applyColor();
    }
}

The output is as follows:

Square applying color: Applying red color
Circle applying color: Applying blue color

This is a simple bridge pattern implementation that allows us to dynamically change the color of the Shape class at runtime without affecting Shape subclasses, and also allows us to add new color and shape classes without changing other existing classes.

3. Behavioral Model

1. Chain of Responsibility Pattern

The Chain of Responsibility pattern is a behavioral design pattern that allows a request to be passed along a processing chain until a handler handles it. Implementing the Chain of Responsibility pattern in Java usually requires the following steps:

  1. Defines a handler interface that contains a method for handling requests.
  2. Create an abstract handler class that implements the handler interface and contains a reference to the next handler.
  3. Create concrete handler classes that inherit from abstract handler classes and implement methods for handling requests.
  4. In client code, create a chain of handlers, and send the request to the first handler in the chain.

The following is a simple Java implementation of the Chain of Responsibility pattern:
chain of responsibility model

public interface Handler {
    
    
    Handler setNextHandler(Handler nextHandler);
    void handleRequest(Request request);
}
// 创建抽象处理器类
public abstract class AbstractHandler implements Handler {
    
    
    private Handler nextHandler;

    public Handler setNextHandler(Handler nextHandler) {
    
    
        this.nextHandler = nextHandler;
        return this.nextHandler;
    }

    public Handler getNextHandler() {
    
    
        return nextHandler;
    }
}

 // 创建具体的处理器类
public class ConcreteHandler1 extends AbstractHandler {
    
    
    public void handleRequest(Request request) {
    
    
        if (request.getType().equals("Type1")) {
    
    
            System.out.println("ConcreteHandler1 handles request " + request);
        } else {
    
    
            getNextHandler().handleRequest(request);
        }
    }
}
 public class ConcreteHandler2 extends AbstractHandler {
    
    
    public void handleRequest(Request request) {
    
    
        if (request.getType().equals("Type2")) {
    
    
            System.out.println("ConcreteHandler2 handles request " + request);
        } else {
    
    
            getNextHandler().handleRequest(request);
        }
    }
}
 public class ConcreteHandler3 extends AbstractHandler {
    
    
    public void handleRequest(Request request) {
    
    
        if (request.getType().equals("Type3")) {
    
    
            System.out.println("ConcreteHandler3 handles request " + request);
        } else {
    
    
            getNextHandler().handleRequest(request);
        }
    }
}
// 创建请求类
public class Request {
    
    
    private String type;

    public Request(String type) {
    
    
        this.type = type;
    }

    public String getType() {
    
    
        return type;
    }

    public String toString() {
    
    
        return "Request [type=" + type + "]";
    }
}
// 客户端代码
public class Client {
    
    
    public static void main(String[] args) {
    
    
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        Handler handler3 = new ConcreteHandler3();
        handler1.setNextHandler(handler2)
                .setNextHandler(handler3);
        handler1.handleRequest(new Request("Type1"));
        handler1.handleRequest(new Request("Type2"));
        handler1.handleRequest(new Request("Type3"));
    }
}

Enter the result:

ConcreteHandler1 handles request Request [type=Type1]
ConcreteHandler2 handles request Request [type=Type2]
ConcreteHandler3 handles request Request [type=Type3]

The above code demonstrates how to create a chain of handlers and pass the request along the chain until a handler handles it. In this example, ConcreteHandler1, ConcreteHandler2, and ConcreteHandler3 are concrete handler classes that inherit from the AbstractHandler class and implement the handleRequest method. The client code creates a chain of handlers and sends the request to the first handler in the chain. When a request arrives at a processor, it checks that the request type matches a type that the processor can handle. If yes, the handler will process the request. Otherwise, it passes the request to the next handler in the chain until a handler handles it.

2. Command Pattern

The command pattern is a behavioral design pattern that allows encapsulation of requests as an object so that different requests can be parameterized with different parameters, queue or log requests, and support undoable operations. In Java, the implementation of the command pattern usually involves a command interface and one or more concrete command classes, which implement the command interface and define the actual command logic in their execute() method. Additionally, the command pattern may involve an invoker class that associates one or more command objects with receiver objects and invokes their execute() methods when needed. Command pattern is a very flexible and extensible pattern that can be used in many different application scenarios.
command mode

interface Command {
    
    
    void execute(String[] args);
}
 // 定义具体命令
class CreateFileCommand implements Command {
    
    
    public void execute(String[] args) {
    
    
        // 根据给定的名称和内容创建文件的代码
        System.out.println("创建文件 : " + String.join(", ", args));
    }
}
 class DeleteFileCommand implements Command {
    
    
    public void execute(String[] args) {
    
    
        // 根据给定的名称删除文件的代码
        System.out.println("删除文件 : "+String.join(",",args) );
    }
}
 // 定义命令执行者
class CommandExecutor {
    
    
    private Map<String, Command> commands = new HashMap<>();
     public CommandExecutor() {
    
    
        // 将具体命令与命令名称关联起来
        commands.put("create", new CreateFileCommand());
        commands.put("delete", new DeleteFileCommand());
    }
     public void executeCommand(String commandName, String[] args) {
    
    
        // 查找对应的命令并执行
        Command command = commands.get(commandName);
        if (command != null) {
    
    
            command.execute(args);
        } else {
    
    
            System.out.println("Unknown command: " + commandName);
        }
    }
}
 // 使用命令执行者执行命令
public class Main {
    
    
    public static void main(String[] args) {
    
    
        CommandExecutor executor = new CommandExecutor();
        executor.executeCommand("create", new String[]{
    
    "file.txt", "Hello World!"});
        executor.executeCommand("delete", new String[]{
    
    "file.txt"});
        executor.executeCommand("unknown", new String[]{
    
    });
    }
}

Execution output:

创建文件 : file.txt, Hello World!
删除文件 : file.txt
Unknown command: unknown

3. Interpreter Pattern

The Java interpreter pattern is a behavioral design pattern that defines a language and an interpreter for that language so that some specific operations can be expressed using the language. This mode is suitable for scenarios that need to interpret some specific languages, such as compilers, expression calculators, etc.

In Java, language expressions can be represented using an Abstract Syntax Tree (AST), and an interpreter is used to execute those expressions. Interpreter patterns typically include the following components:

  • Abstract Expression: Defines an abstract interpreter interface that contains methods that the interpreter needs to implement.
  • Terminal Expression: A terminal that implements the abstract expression interface and is used to represent basic operations or values ​​in the language.
  • Non-Terminal Expression (Non-Terminal Expression): A non-terminal symbol that implements the abstract expression interface and is used to represent complex operations in the language.
  • Context: Contains some global information needed by the interpreter, such as variables, functions, etc.
  • Interpreter: Use the above components to interpret language expressions and perform corresponding operations.
    interpreter mode

The following is a sample code for a simple Java interpreter mode:

interface Expression {
    
    
    int interpret(Context context);
}
 // 终结符表达式
class NumberExpression implements Expression {
    
    
    private int value;
     public NumberExpression(int value) {
    
    
        this.value = value;
    }
     public int interpret(Context context) {
    
    
        return value;
    }
}
 // 非终结符表达式
class AddExpression implements Expression {
    
    
    private Expression left;
    private Expression right;
     public AddExpression(Expression left, Expression right) {
    
    
        this.left = left;
        this.right = right;
    }
     public int interpret(Context context) {
    
    
        return left.interpret(context) + right.interpret(context);
    }
}
 // 上下文
class Context {
    
    
    private Map<String, Integer> variables = new HashMap<>();
     public void setVariable(String name, int value) {
    
    
        variables.put(name, value);
    }
     public int getVariable(String name) {
    
    
        return variables.get(name);
    }
}
 // 解释器
class Interpreter {
    
    
    private Expression expression;
     public Interpreter(Expression expression) {
    
    
        this.expression = expression;
    }
     public int interpret(Context context) {
    
    
        return expression.interpret(context);
    }
}
 // 使用解释器执行表达式
public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 创建上下文
        Context context = new Context();
        context.setVariable("a", 10);
        context.setVariable("b", 20);
         // 创建表达式
        Expression expression = new AddExpression(
            new NumberExpression(context.getVariable("a")),
            new NumberExpression(context.getVariable("b"))
        );
         // 创建解释器并执行表达式
        Interpreter interpreter = new Interpreter(expression);
        int result = interpreter.interpret(context);
        System.out.println("Result: " + result);
    }
}

In the above sample code, we defined two terminal expressions (NumberExpression) and one non-terminal expression (AddExpression) to represent the addition operation. We also define a context (Context), used to store variables and functions, and an interpreter (Interpreter), used to execute expressions. Finally, we executed a simple addition expression using the interpreter and printed the result.

4. Iterator Pattern

The Java Iterator pattern is a behavioral design pattern that provides a way to access the elements of a collection object without exposing the internal representation of that object. This mode is suitable for scenarios that need to traverse collection objects, such as arrays, lists, trees, etc.
iterator pattern

The following is a simple sample code of the Java iterator pattern, which implements an array iterator using array storage:

interface Iterator<T> {
    
    
    boolean hasNext();
    T next();
}
 // 具体迭代器实现类
class ArrayIterator<T> implements Iterator<T> {
    
    
    private T[] array;
    private int currentIndex;
     public ArrayIterator(T[] array) {
    
    
        this.array = array;
        this.currentIndex = 0;
    }
     public boolean hasNext() {
    
    
        return currentIndex < array.length;
    }
     public T next() {
    
    
        if (!hasNext()) {
    
    
            throw new NoSuchElementException();
        }
        T element = array[currentIndex];
        currentIndex++;
        return element;
    }
}
 // 使用迭代器遍历数组
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Integer[] array = {
    
    1, 2, 3, 4, 5};
        Iterator<Integer> iterator = new ArrayIterator<>(array);
        while (iterator.hasNext()) {
    
    
            System.out.println(iterator.next());
        }
    }
}

In the sample code above, we defined an abstract iterator interface (Iterator) to represent an iterator object. We also define a specific iterator implementation class (ArrayIterator) for implementing array iterators. Finally, we iterate through an array of integers and print out the value of each element.

5. Observer Pattern

The Java observer pattern is a behavioral design pattern that defines a one-to-many dependency relationship. When the state of an object changes, all objects that depend on it will be notified and automatically updated. This mode is suitable for scenarios that need to establish dynamic and loosely coupled relationships between objects, such as event processing.

The Observer pattern is suitable for the following scenarios:

  1. When an object's state changes, it needs to notify other objects and update their state.
  2. When an object needs to notify other objects of its state changes, but does not want to have a tight coupling relationship with these objects.
  3. When the change of an object needs to change the state of other objects at the same time, and it is not known how many objects need to be changed.
  4. When an object's change requires the assistance of other objects, but you don't want these objects to be tightly coupled with themselves.
  5. When an object changes, it will cause a series of chain reactions, and the specific realization of these chain reactions does not want to be known by the object.
  6. When an object needs to provide a data update method to other objects, and this update method needs to notify different objects at different points in time.

In short, the observer pattern is suitable for scenarios that need to establish a dynamic, loosely coupled relationship between objects, such as event processing, GUI programming, message queues, etc.
Observer pattern

The following is a simple Java observer pattern sample code that implements a weather station that notifies all observer objects when the weather data changes:

// 抽象主题接口
interface Subject {
    
    
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}
// 具体主题实现类
class WeatherStation implements Subject {
    
    
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherStation() {
    
    
        this.observers = new ArrayList<>();
    }

    public void registerObserver(Observer observer) {
    
    
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
    
    
        observers.remove(observer);
    }

    public void notifyObservers() {
    
    
        System.out.println(">> 通知所有观察者 <<");
        for (Observer observer : observers) {
    
    
            System.out.println("------观察者:" + observer.name() + "-----------");
            observer.update(temperature, humidity, pressure);
        }
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
    
    
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
}
 // 抽象观察者接口
interface Observer {
    
    
    void update(float temperature, float humidity, float pressure);
    String name();
}
// 具体观察者实现类
class Display implements Observer {
    
    
    private float temperature;
    private float humidity;
    private float pressure;
    private String name;

    @Override
    public String name() {
    
    
        return this.name;
    }

    public Display(String name){
    
    
        this.name = name;
    }

    public void update(float temperature, float humidity, float pressure) {
    
    
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    public void display() {
    
    
        System.out.println("Temperature: " + temperature);
        System.out.println("Humidity: " + humidity);
        System.out.println("Pressure: " + pressure);
    }
}
// 使用观察者模式实现气象站
public class Main {
    
    
    public static void main(String[] args) {
    
    
        WeatherStation weatherStation = new WeatherStation();
        Display display1 = new Display("01");
        Display display2 = new Display("02");
        weatherStation.registerObserver(display1);
        weatherStation.registerObserver(display2);
        weatherStation.setMeasurements(25.0f, 60.0f, 1013.0f);
        weatherStation.removeObserver(display2);
        weatherStation.setMeasurements(26.0f, 65.0f, 1012.0f);
    }
}

Execute the input result:

>> 通知所有观察者 <<
------观察者:01-----------
Temperature: 25.0
Humidity: 60.0
Pressure: 1013.0
------观察者:02-----------
Temperature: 25.0
Humidity: 60.0
Pressure: 1013.0
>> 通知所有观察者 <<
------观察者:01-----------
Temperature: 26.0
Humidity: 65.0
Pressure: 1012.0

In the sample code above, we define an abstract subject interface (Subject) and an abstract observer interface (Observer), which are used to represent the subject and observer objects. We also define a concrete subject implementation class (WeatherStation) and a concrete observer implementation class (Display) for implementing weather station and display objects. Finally, we implemented a weather station using the Observer pattern. When the weather data changes, all observer objects are notified and the data on the display is updated.

Introduction to Java's own observer mode

A built-in observer pattern implementation provided by Java. It uses the Observable class and Observer interface in Java to implement the observer pattern.

The Observable class is an abstract class that represents an observable object with methods for adding, removing, and notifying observers. When the state of the Observable object changes, its notifyObservers() method is called to notify all observer objects and update their state. The Observable class also provides the setChanged() method and the clearChanged() method to mark whether the state of the Observable object has changed.

The Observer interface represents an observer object, which has a method update() to update the state. When the state of the Observable object changes, the update() method of the observer object will be called to pass the updated data.
JDK comes with observer mode

import java.util.Observable;
import java.util.Observer;
 
// 具体主题类
class WeatherStation extends Observable {
    
    
    private float temperature;
    private float humidity;
    private float pressure;
 
    public void setMeasurements(float temperature, float humidity, float pressure) {
    
    
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        setChanged();
        notifyObservers();
    }
 
    public float getTemperature() {
    
    
        return temperature;
    }
 
    public float getHumidity() {
    
    
        return humidity;
    }
 
    public float getPressure() {
    
    
        return pressure;
    }
}
 
// 具体观察者类
class Display implements Observer {
    
    
    private float temperature;
    private float humidity;
    private float pressure;
 
    public void update(Observable o, Object arg) {
    
    
        if (o instanceof WeatherStation) {
    
    
            WeatherStation weatherStation = (WeatherStation) o;
            this.temperature = weatherStation.getTemperature();
            this.humidity = weatherStation.getHumidity();
            this.pressure = weatherStation.getPressure();
            display();
        }
    }
 
    public void display() {
    
    
        System.out.println("Temperature: " + temperature);
        System.out.println("Humidity: " + humidity);
        System.out.println("Pressure: " + pressure);
    }
}
 
// 使用JDK自带观察者模式实现气象站
public class Main {
    
    
    public static void main(String[] args) {
    
    
        WeatherStation weatherStation = new WeatherStation();
        Display display1 = new Display();
        Display display2 = new Display();
        weatherStation.addObserver(display1);
        weatherStation.addObserver(display2);
        weatherStation.setMeasurements(25.0f, 60.0f, 1013.0f);
        weatherStation.deleteObserver(display2);
        weatherStation.setMeasurements(26.0f, 65.0f, 1012.0f);
    }
}

In the above sample code, we have used Observable class and Observer interface to implement weather station and display objects. When the weather data changes, the Observable object will call the notifyObservers() method to notify all observer objects and update their status. The observer object implements the update() method of the Observer interface to update its own state.

6. State Pattern

The Java state pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes. The state pattern encapsulates the state into an independent class, and delegates the request to the current state object, so as to realize the state switching and state behavior change.

Scenarios for using the state pattern include:

  1. When an object's behavior depends on its state, and it must change its behavior at runtime based on the state.
  2. When an object needs to change its data and methods based on state.
  3. When an object needs to switch between multiple states, each with different behavior.

Precautions:

  1. The state pattern can increase the number of classes, so it needs to be designed with the number and complexity of classes in mind.
  2. The state pattern needs to encapsulate the state, so the scalability and maintainability of the state need to be considered in the design.
    state mode

Here is a sample code implemented using the Java state pattern:

interface State {
    
    
    void handle();
}
 // 具体状态类1
class ConcreteState1 implements State {
    
    
    public void handle() {
    
    
        System.out.println("ConcreteState1 is handling.");
    }
}
 // 具体状态类2
class ConcreteState2 implements State {
    
    
    public void handle() {
    
    
        System.out.println("ConcreteState2 is handling.");
    }
}
 // 环境类
class Context {
    
    
    private State state;
     public void setState(State state) {
    
    
        this.state = state;
    }
     public void request() {
    
    
        state.handle();
    }
}
 // 使用状态模式实现的客户端代码
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Context context = new Context();
        State state1 = new ConcreteState1();
        State state2 = new ConcreteState2();
        context.setState(state1);
        context.request();
        context.setState(state2);
        context.request();
    }
}

In the sample code above, we define a state interface State and two concrete state classes ConcreteState1 and ConcreteState2. We also define an environment class Context, which contains a state object, and defines a request method request(), which is used to call the handle() method of the current state object. In the client code, we create a Context object, set its state to ConcreteState1, and then call the request() method to output "ConcreteState1 is handling.". Next, we set the state of Context to ConcreteState2, and call the request() method again, outputting "ConcreteState2 is handling.".

By using the state pattern, we can separate the state and behavior, so that the behavior of the object can change as the state changes, thereby achieving a more flexible design.

7. Template Pattern

The Java Template pattern is a behavioral design pattern that defines the program skeleton in an operation, deferring some steps to subclass implementation. This allows subclasses to redefine certain steps in the program without changing the structure of the program.

In the Java template pattern, there are two types of methods: abstract methods and concrete methods. Abstract methods are implemented by subclasses while concrete methods are implemented by superclasses. The template method is composed of concrete methods and abstract methods, which define the skeleton of the program, while the concrete methods implement some steps of the algorithm.

The Java template pattern is suitable for the following scenarios:

  1. Use the template pattern when you need to define a set of algorithms that are similar in structure but differ in implementation details.
  2. The template pattern can be used when you need to control the flow of an algorithm and you need different behaviors at different stages of the algorithm.
  3. When it is necessary to customize some steps of the algorithm without affecting the overall structure of the algorithm, the template mode can be used.
  4. When you need to use the same algorithm in multiple classes, you can use the template pattern to avoid code duplication.

In short, the Java template pattern is suitable for those who need to define the skeleton in the program and implement the specific steps in the subclass. It improves code reusability and maintainability, while also making code more flexible and extensible.
template pattern

The following is a simple Java template pattern sample code:

abstract class Game {
    
    
    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();
     // 模板方法
    public final void play() {
    
    
        // 初始化游戏
        initialize();
         // 开始游戏
        startPlay();
         // 结束游戏
        endPlay();
    }
}
 class Cricket extends Game {
    
    
    @Override
    void endPlay() {
    
    
        System.out.println("Cricket Game Finished!");
    }
     @Override
    void initialize() {
    
    
        System.out.println("Cricket Game Initialized! Start playing.");
    }
     @Override
    void startPlay() {
    
    
        System.out.println("Cricket Game Started. Enjoy the game!");
    }
}
 class Football extends Game {
    
    
    @Override
    void endPlay() {
    
    
        System.out.println("Football Game Finished!");
    }
     @Override
    void initialize() {
    
    
        System.out.println("Football Game Initialized! Start playing.");
    }
     @Override
    void startPlay() {
    
    
        System.out.println("Football Game Started. Enjoy the game!");
    }
}
 public class TemplatePatternDemo {
    
    
    public static void main(String[] args) {
    
    
        Game game = new Cricket();
        game.play();
         System.out.println();
         game = new Football();
        game.play();
    }
}

In the above code, the Game class is an abstract class that defines a play() method as a template method. The Cricket and Football classes are concrete implementation classes that implement the abstract methods defined in the abstract class. In the main() method, we create a Cricket object and a Football object and call their play() method. This way we can redefine some steps in the algorithm without changing the structure of the algorithm.

8. Mediator Pattern

The Java Mediator pattern is a behavioral design pattern that allows objects to communicate through a Mediator object, thereby avoiding direct coupling between objects. The mediator pattern transforms the complex relationship between objects into a simple relationship between mediators and objects, thus improving the flexibility and maintainability of the system.

In the Java intermediary pattern, the intermediary object is responsible for coordinating the communication between objects, and it usually contains some public methods for handling the interaction between objects. Communication between objects takes place through a mediator object, thus avoiding direct coupling between objects.
mediator pattern

The following is a simple Java Mediator pattern sample code:

// Mediator接口定义了send()方法,用于处理对象之间的交互。
interface Mediator {
    
    
    void send(String message, Colleague colleague);
}
 // Colleague抽象类表示对象,它包含一个Mediator对象,用于处理对象之间的通信。
abstract class Colleague {
    
    
    protected Mediator mediator;
     public Colleague(Mediator mediator) {
    
    
        this.mediator = mediator;
    }
     public abstract void receive(String message);
     public abstract void send(String message);
}
 // ConcreteColleague1和ConcreteColleague2是具体的对象实现类,它们实现了Colleague抽象类中的方法。
class ConcreteColleague1 extends Colleague {
    
    
    public ConcreteColleague1(Mediator mediator) {
    
    
        super(mediator);
    }
     @Override
    public void receive(String message) {
    
    
        System.out.println("Colleague1 received message: " + message);
    }
     @Override
    public void send(String message) {
    
    
        System.out.println("Colleague1 sends message: " + message);
        mediator.send(message, this);
    }
}
 class ConcreteColleague2 extends Colleague {
    
    
    public ConcreteColleague2(Mediator mediator) {
    
    
        super(mediator);
    }
     @Override
    public void receive(String message) {
    
    
        System.out.println("Colleague2 received message: " + message);
    }
     @Override
    public void send(String message) {
    
    
        System.out.println("Colleague2 sends message: " + message);
        mediator.send(message, this);
    }
}
 // ConcreteMediator是具体的中介者实现类,它负责协调对象之间的通信。
class ConcreteMediator implements Mediator {
    
    
    private ConcreteColleague1 colleague1;
    private ConcreteColleague2 colleague2;
     public void setColleague1(ConcreteColleague1 colleague1) {
    
    
        this.colleague1 = colleague1;
    }
     public void setColleague2(ConcreteColleague2 colleague2) {
    
    
        this.colleague2 = colleague2;
    }
     @Override
    public void send(String message, Colleague colleague) {
    
    
        if (colleague == colleague1) {
    
    
            colleague2.receive(message);
        } else {
    
    
            colleague1.receive(message);
        }
    }
}
 public class MediatorPatternDemo {
    
    
    public static void main(String[] args) {
    
    
        ConcreteMediator mediator = new ConcreteMediator();
         ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
        ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
         mediator.setColleague1(colleague1);
        mediator.setColleague2(colleague2);
         colleague1.send("Hello, Colleague2.");
        colleague2.send("Hello, Colleague1.");
    }
}

In the above code, the Mediator interface defines the send() method, which is used to handle the interaction between objects. The Colleague abstract class represents objects, and it contains a Mediator object for handling communication between objects. ConcreteColleague1 and ConcreteColleague2 are concrete object implementation classes that implement methods in the Colleague abstract class. ConcreteMediator is a specific intermediary implementation class, which is responsible for coordinating communication between objects.

In the main() method, we create a ConcreteMediator object and two ConcreteColleague objects, and call their send() method to communicate. Communication is carried out through intermediary objects, avoiding direct coupling between objects.

9. Memento Pattern

The Java Memento pattern is a behavioral design pattern that allows capturing and restoring an object's internal state without breaking encapsulation. The memento pattern is often used in situations where it is necessary to undo an action or restore a previous state. The pattern consists of three main components: primitive objects, memento objects, and objects responsible for managing memento objects.
memo mode

The following is a sample code for a simple Java memento pattern:

// Originator类表示原始对象,它包含需要保存的状态。
class Originator {
    
    
    private String state;
     public void setState(String state) {
    
    
        this.state = state;
    }
     public String getState() {
    
    
        return state;
    }
     // createMemento()方法创建备忘录对象,并将当前状态保存到备忘录对象中。
    public Memento createMemento() {
    
    
        return new Memento(state);
    }
     // restore()方法用于从备忘录对象中恢复先前的状态。
    public void restore(Memento memento) {
    
    
        state = memento.getState();
    }
}
 // Memento类表示备忘录对象,它包含需要保存的状态。
class Memento {
    
    
    private String state;
     public Memento(String state) {
    
    
        this.state = state;
    }
     public String getState() {
    
    
        return state;
    }
}
 // Caretaker类负责管理备忘录对象,它包含一个Memento对象。
class Caretaker {
    
    
    private Memento memento;
     public void setMemento(Memento memento) {
    
    
        this.memento = memento;
    }
     public Memento getMemento() {
    
    
        return memento;
    }
}
 public class MementoPatternDemo {
    
    
    public static void main(String[] args) {
    
    
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
         // 保存原始对象的状态到备忘录对象中
        originator.setState("State 1");
        caretaker.setMemento(originator.createMemento());
         // 修改原始对象的状态
        originator.setState("State 2");
         // 从备忘录对象中恢复先前的状态
        originator.restore(caretaker.getMemento());
         System.out.println("Current state: " + originator.getState());
    }
}

In the above code, the Originator class represents the original object, which contains the state that needs to be saved. The createMemento() method creates a memento object and saves the current state to the memento object. The restore() method is used to restore the previous state from the memento object.

The Memento class represents a Memento object, which contains the state that needs to be saved.

The Caretaker class is responsible for managing the Memento object, which contains a Memento object.

In the main() method, we create an Originator object and a Caretaker object, and call their methods for state preservation and restoration. With the Memento pattern, we can capture and restore an object's internal state without breaking encapsulation.

10. Visitor Pattern

The Java visitor pattern is a behavioral design pattern that allows you to define new operations without modifying the object structure. The visitor pattern separates object structure and operations so that operations can be applied independently to elements in the object structure. In the Visitor pattern, there are two main actors: the visitor and the element. Visitors define methods to operate on elements, and elements provide methods that accept visitors.

Below is a sample code of the visitor pattern, in which we will implement a simple calculator that can evaluate expressions. We will use the visitor pattern to walk the expression tree and perform the corresponding operation on each node.

The visitor pattern is suitable for the following scenarios:

  1. The object structure is relatively stable, but it is often necessary to define new operations on this object structure.
  2. You need to perform many different and unrelated operations on the objects in an object structure, and you don't want these operations to "pollute" the classes of these objects.
  3. The classes of objects in the object structure rarely change, but often new operations need to be defined on these objects.
  4. Different operations need to be performed at runtime depending on the type of the object.
  5. All objects in an object structure need to be processed in some way, but the way objects are processed varies depending on the type of object.

Common scenarios for using the visitor pattern include: compiler syntax analysis, XML document parsing, static analyzer, model validator, model converter, etc.

visitor pattern

interface Expression {
    
    
    void accept(Visitor visitor);
}
class NumberExpression implements Expression {
    
    
    private int value;
    public NumberExpression(int value) {
    
    
        this.value = value;
    }
    public int getValue() {
    
    
        return value;
    }
    public void accept(Visitor visitor) {
    
    
        visitor.visit(this);
    }
}
// 定义具体的元素类:加法表达式
class AdditionExpression implements Expression {
    
    
    private Expression left;
    private Expression right;
    public AdditionExpression(Expression left, Expression right) {
    
    
        this.left = left;
        this.right = right;
    }
    public Expression getLeft() {
    
    
        return left;
    }
    public Expression getRight() {
    
    
        return right;
    }
    public void accept(Visitor visitor) {
    
    
        visitor.visit(this);
    }
}
interface Visitor {
    
    
    void visit(NumberExpression expression);
    void visit(AdditionExpression expression);
}
// 定义具体的访问者类:打印访问者
class PrinterVisitor implements Visitor {
    
    
    public void visit(NumberExpression expression) {
    
    
        System.out.print(expression.getValue());
    }
    public void visit(AdditionExpression expression) {
    
    
        System.out.print("(");
        expression.getLeft().accept(this);
        System.out.print("+");
        expression.getRight().accept(this);
        System.out.print(")");
    }
}
class CalculatorVisitor implements Visitor {
    
    
    private int result;
    public void visit(NumberExpression expression) {
    
    
        result = expression.getValue();
    }
    public void visit(AdditionExpression expression) {
    
    
        expression.getLeft().accept(this);
        int leftValue = result;
        expression.getRight().accept(this);
        int rightValue = result;
        result = leftValue + rightValue;
    }
    public int getResult() {
    
    
        return result;
    }
}
public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 构建表达式树:1 + (2 + 3)
        Expression expression = new AdditionExpression(
                new NumberExpression(1),
                new AdditionExpression(
                        new NumberExpression(2),
                        new NumberExpression(3)
                )
        );
        // 计算表达式的值
        CalculatorVisitor calculator = new CalculatorVisitor();
        expression.accept(calculator);
        System.out.println("Result: " + calculator.getResult());
        // 打印表达式的字符串表示
        PrinterVisitor printer = new PrinterVisitor();
        expression.accept(printer);
    }
}

Output result:

Result: 6
(1+(2+3))

In the above example, we define an expression interface Expression, and provide two expression implementations, NumberExpression and AdditionExpression, and define a visitor interface Visitor, and two specific visitors, CalculatorVisitor and PrinterVisitor. The two visitors receive the expression object and implement specific operations on the expression in the visitor, namely expression operation and expression printing. The above example does not change the concrete expression class and defines new operations.

11. Strategy Pattern

The Strategy pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each algorithm, and make them interchangeable. This pattern enables algorithms to vary independently of the clients using them.

In Java, the strategy pattern usually consists of an interface and multiple classes that implement the interface. The client will use this interface to call the algorithm instead of calling the implementation class directly. In this way, the client can choose a different algorithm implementation at runtime without modifying the code.
strategy pattern

The following example shows how to use the strategy pattern to implement a simple payment system. We define an interface PaymentStrategy, and create multiple classes that implement this interface, and each class represents a different payment method. Clients can choose which payment method to use according to their needs.

interface PaymentStrategy {
    
    
    void pay(double amount);
}
 class CreditCardPayment implements PaymentStrategy {
    
    
    private String cardNumber;
    private String cvv;
    private String expiryDate;
     public CreditCardPayment(String cardNumber, String cvv, String expiryDate) {
    
    
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.expiryDate = expiryDate;
    }
     public void pay(double amount) {
    
    
        System.out.println("Paying " + amount + " using credit card.");
    }
}
 class PayPalPayment implements PaymentStrategy {
    
    
    private String email;
    private String password;
     public PayPalPayment(String email, String password) {
    
    
        this.email = email;
        this.password = password;
    }
     public void pay(double amount) {
    
    
        System.out.println("Paying " + amount + " using PayPal.");
    }
}
 class CashPayment implements PaymentStrategy {
    
    
    public void pay(double amount) {
    
    
        System.out.println("Paying " + amount + " using cash.");
    }
}
 class PaymentProcessor {
    
    
    private PaymentStrategy strategy;
     public PaymentProcessor(PaymentStrategy strategy) {
    
    
        this.strategy = strategy;
    }
     public void setStrategy(PaymentStrategy strategy) {
    
    
        this.strategy = strategy;
    }
     public void processPayment(double amount) {
    
    
        strategy.pay(amount);
    }
}
 public class PaymentSystem {
    
    
    public static void main(String[] args) {
    
    
        PaymentProcessor processor = new PaymentProcessor(new CreditCardPayment("1234 5678 9012 3456", "123", "12/23"));
        processor.processPayment(100.0);
         processor.setStrategy(new PayPalPayment("[email protected]", "password"));
        processor.processPayment(50.0);
         processor.setStrategy(new CashPayment());
        processor.processPayment(25.0);
    }
}

Execution output:

Paying 100.0 using credit card.
Paying 50.0 using PayPal.
Paying 25.0 using cash.

In the above example, the PaymentStrategy interface defines a payment method and contains a pay method that accepts an amount parameter. We created three classes that implement this interface, representing credit card payment, PayPal payment and cash payment respectively. The PaymentProcessor class takes a PaymentStrategy instance as a parameter and uses it to perform payment operations. In the main method, we create a PaymentProcessor instance and use different payment methods to make payments.

other

The following two commonly used design patterns are introduced

1. Filter Pattern

The Java filter design pattern is a commonly used design pattern for processing or filtering requests before or after they reach the target object. This mode can be used to implement different functions, such as authentication, authorization, logging, compression, etc., and treat different operations as a filter in the filter chain.
filter mode
Here is a sample code for a simple Java filter design pattern:

  • First, define an interface Filter, which contains a method doFilter for processing requests
  • Then, implement two filter classes, one for authenticating requests and one for logging
  • Finally, define a FilterChain class for chaining multiple filters together
public interface Filter {
    
    
    public void doFilter(String request);
}
// 定义授权过滤器
public class AuthenticationFilter implements Filter {
    
    
    public void doFilter(String request) {
    
    
        System.out.println("Authenticating request: " + request);
    }
}
// 定义日志过滤器
public class LoggingFilter implements Filter {
    
    
    public void doFilter(String request) {
    
    
        System.out.println("Logging request: " + request);
    }
}
// 定义过滤器链
public class FilterChain {
    
    
    private List<Filter> filters = new ArrayList<Filter>();
    private int index = 0;

    public void addFilter(Filter filter) {
    
    
        filters.add(filter);
    }

    public void doFilter(String request) {
    
    
        if (index == filters.size()) {
    
    
            return;
        }

        Filter filter = filters.get(index);
        index++;

        filter.doFilter(request);
        doFilter(request);
    }
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        FilterChain chain = new FilterChain();
        chain.addFilter(new AuthenticationFilter());
        chain.addFilter(new LoggingFilter());

        chain.doFilter("request");
    }
}

The above sample code shows the basic implementation of the Java filter design pattern. Different functions can be achieved by defining different filter classes to chain them.

2. Null Object Pattern

The Java Null Object Pattern is a behavioral design pattern that allows us to provide default behavior without returning null. This pattern is usually used in situations where null objects need to be handled to avoid NullPointerException exceptions. The pattern consists of two main components: abstract classes and concrete classes.

The following is a simple sample code for the Java Null Object pattern:
empty object pattern

interface User {
    
    
    String getName();
    boolean hasAccess();
}
 // 定义一个具体类,表示一个真实的用户
class RealUser implements User {
    
    
    private String name;
    private boolean hasAccess;
     public RealUser(String name, boolean hasAccess) {
    
    
        this.name = name;
        this.hasAccess = hasAccess;
    }
     public String getName() {
    
    
        return name;
    }
     public boolean hasAccess() {
    
    
        return hasAccess;
    }
}
 // 定义一个空对象,表示一个空的用户
class NullUser implements User {
    
    
    public String getName() {
    
    
        return "Guest";
    }
     public boolean hasAccess() {
    
    
        return false;
    }
}
 // 定义一个工厂类,用于创建用户
class UserFactory {
    
    
    // 根据名称和权限创建一个用户
    public static User getUser(String name, boolean hasAccess) {
    
    
        if (name == null) {
    
    
            return new NullUser();
        }
        return new RealUser(name, hasAccess);
    }
}
 public class NullObjectPatternDemo {
    
    
    public static void main(String[] args) {
    
    
        User user1 = UserFactory.getUser("Alice", true);
        User user2 = UserFactory.getUser(null, false);
         System.out.println("User 1: " + user1.getName() + ", has access: " + user1.hasAccess());
        System.out.println("User 2: " + user2.getName() + ", has access: " + user2.hasAccess());
    }
}

In the above code, we define an interface User, which represents a user and contains two methods: getName() and hasAccess(). Next, we define a concrete class RealUser, which represents a real user and implements two methods of the User interface. We also define an empty object NullUser, which represents an empty user, and implements two methods of the User interface. Finally, we define a factory class UserFactory for creating users. If the incoming name is null, return an empty object NullUser, otherwise return a real user RealUser.

In the main() method, we use UserFactory to create two users user1 and user2, and print their names and permissions. Since the name of user2 is null, it will return an empty object NullUser and print out the "Guest" string and the boolean value of false. Through the empty object pattern, we can provide default behavior without returning null, avoiding NullPointerException.

Guess you like

Origin blog.csdn.net/wlddhj/article/details/131071730