Java Design Patterns: In-depth Analysis and Application Examples

introduction

A design pattern is a reusable solution that recurs in a specific context to deal with common problems in software design. Mastering design patterns can not only help us write more elegant, understandable and maintainable code, but also a common knowledge point in Java interviews. In this article, we'll explore several common design patterns, including their definitions, usage scenarios, and Java implementations.

1. Singleton mode

The singleton pattern ensures that there is only one instance of a class and provides a global point of access. This design pattern is a creational pattern and it involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its only object, directly, without instantiating an object of this class.

Application scenarios: Objects that need to be instantiated frequently and then destroyed, database connections, thread pools and other objects that exist in the system for a long time.

Sample code:

public class Singleton {
    
    
    // 使用volatile关键字防止指令重排序
    private static volatile Singleton instance;

    private Singleton() {
    
    }

    // 提供全局访问点
    public static Singleton getInstance() {
    
    
        // 第一次检查
        if (instance == null) {
    
    
            // 加锁
            synchronized (Singleton.class) {
    
    
                // 第二次检查
                if (instance == null) {
    
    
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

We create a Singletonclass . SingletonThe class has a getInstance()method that returns Singletonan instance of the class. In this class we have a private constructor which prevents other classes from instantiating this class. getInstance()The method can provide a way for other classes to obtain a single instance of this class.

2. Factory mode

Factory pattern is a creational design pattern that provides an optimal way to create objects. In the factory mode, we do not expose the creation logic to the client when creating an object, and use a common interface to point to the newly created object.

Application scenario: When coding, it is impossible to foresee which type of instance needs to be created. The system needs to provide an interface to decouple the system from its various concrete implementation classes.

Sample code:

public interface Shape {
    
    
    void draw();
}

public class Rectangle implements Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("Inside Rectangle::draw() method.");
    }
}

public class ShapeFactory {
    
    
    // 使用getShape方法获取形状类型的对象
    public Shape getShape(String shapeType) {
    
    
        if (shapeType.equalsIgnoreCase("RECTANGLE")) {
    
    
            return new Rectangle();
        }
        // other shape types...
        return null;
    }
}

We first created an interface Shapeand Shapean entity class that implements the interface. Then we create a factory class ShapeFactory. ShapeFactoryThe class has a method getShapethat, depending on the type of input, returns an instance of the entity class. In this example, ShapeFactoryhow returns instances of different classes based on the information we provide.

3. Abstract factory pattern

The Abstract Factory pattern is a creational design pattern that provides a way to encapsulate a set of individual factories that share a common theme. In the abstract factory pattern, an abstract factory defines what a product is, providing an interface for creating a series of related or interdependent objects without specifying their concrete classes.

Application scenario: The products of the system have more than one product family, and the system only consumes the products of one of them.
Sample code:

public interface GUIFactory {
    
    
    Button createButton();
    Checkbox createCheckbox();
}

public class WinFactory implements GUIFactory {
    
    
    // 返回WinButton类的实例
    public Button createButton() {
    
    
        return new WinButton();
    }

    // 返回WinCheckbox类的实例
    public Checkbox createCheckbox() {
    
    
        return new WinCheckbox();
    }
}

Similar to the factory pattern, but this time we've added a new layer - the factory creator/generator class FactoryProducer. AbstractFactoryclass is the superclass of all factory classes and FactoryProducercan return a specific factory based on the information passed in.

4. Builder mode

The builder pattern is a creational design pattern, which can abstract the construction process of a complex object (abstracted as a conductor and a builder), so that different implementation methods of this abstraction process can construct complex objects with different performances (attributes). object. Specifically, decoupling a complex construction from its representation enables the same construction process to create different representations.

Application scenario: The product objects that need to be generated have complex internal structures, and these product objects usually contain multiple parts.

Sample code:

public class Pizza {
    
    
    private String dough = "";
    private String sauce = "";
    private String topping = "";

    // setters...
}

public class PizzaBuilder {
    
    
    private Pizza pizza;

    public PizzaBuilder() {
    
    
        pizza = new Pizza();
    }

    public PizzaBuilder setDough(String dough) {
    
    
        pizza.setDough(dough);
        return this;
    }

    public PizzaBuilder setSauce(String sauce) {
    
    
        pizza.setSauce(sauce);
        return this;
    }

    public PizzaBuilder setTopping(String topping) {
    
    
        pizza.setTopping(topping);
        return this;
    }

    // 最终构建复杂的Pizza对象并返回
    public Pizza build() {
    
    
        return pizza;
    }
}

PackingThe and Iteminterfaces represent food and food packaging. Then we have entity classes implementing these interfaces, Burgerand ColdDrinkimplementing Itemthe interface, Wrapperand Bottleimplementing Packingthe interface. MealA class is a composite class that contains Itemobjects . MealBuilderis the actual builder responsible for creating Mealthe object .

5. Prototype mode

The prototype pattern is a creational design pattern that returns a new instance by copying an existing instance instead of creating a new one. The copied instance is what we call a "prototype", and this prototype is customizable.

Application scenario: The cost of creating an object is relatively high (for example, initialization takes a lot of time and takes up too much CPU resources or network resources). New objects can be obtained by copying existing objects through the prototype mode. If they are similar objects, they can be copied Its member variables are slightly modified.
Sample code:

public class Prototype implements Cloneable {
    
    
    // 使用 clone() 方法来创建新的实例
    public Prototype clone() {
    
    
        try {
    
    
            return

 (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
}

In the Prototype pattern, we create new objects by duplicating an existing object. We create an abstract class Shapeand several Shapeconcrete classes that extend the class. ShapeCacheclass is a cache class that stores shape objects in a Hashtable and returns clones of them when requested.

6. Adapter mode

The adapter pattern is a structural design pattern that can help us solve incompatibility problems by transforming the interface of a class into another interface that the client expects. This mode is mainly used in scenarios where you want to reuse old components that are incompatible with the new system, so that those classes that could not work together due to interface incompatibility can work together.

Application scenario: the existing class, its method is different from our requirements, that is, the interface is different, or we have created a reusable class, which may not be very compatible with the existing class library. Use the Adapter pattern when neither side is easily modifiable.

Sample code:

// 目标接口,或称为标准接口
public interface MediaPlayer {
    
    
    void play(String audioType, String fileName);
}

// 适配器类,实现MediaPlayer接口
public class MediaAdapter implements MediaPlayer {
    
    
    // 适配器中包含了一个需要适配的对象
    AdvancedMediaPlayer advancedMusicPlayer;

    public MediaAdapter(String audioType) {
    
    
        if (audioType.equalsIgnoreCase("vlc")) {
    
    
            advancedMusicPlayer = new VlcPlayer();
        } else if (audioType.equalsIgnoreCase("mp4")) {
    
    
            advancedMusicPlayer = new Mp4Player();
        }
    }

    // 调用适配器中的方法
    public void play(String audioType, String fileName) {
    
    
        if (audioType.equalsIgnoreCase("vlc")) {
    
    
            advancedMusicPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
    
    
            advancedMusicPlayer.playMp4(fileName);
        }
    }
}

We have an MediaPlayerinterface and an MediaPlayerentity class that implements the interface. Then we create another interface AdvancedMediaPlayerand AdvancedMediaPlayerthe entity class that implements the interface. Then we create an adapter class MediaAdapterthat uses AdvancedMediaPlayerthe object to play the desired format.

7. Decorator mode

The decorator pattern is a structural design pattern that allows adding new functionality to an existing object without changing its structure. This type of design pattern is a structural pattern that dynamically adds new functionality to objects without using inheritance.

Application scenario: To extend a class without adding many subclasses, it is necessary to dynamically add and revoke the function of objects.

Sample code:

// 定义接口Shape
public interface Shape {
    
    
    void draw();
}

// 定义装饰器DecoratorShape
public class DecoratorShape implements Shape {
    
    
    protected Shape decoratedShape;

    public DecoratorShape(Shape decoratedShape) {
    
    
        this.decoratedShape = decoratedShape;
    }

    // 在装饰器中调用原始对象的方法,并添加新的功能
    public void draw() {
    
    
        decoratedShape.draw();
        System.out.println("Additional decoration function.");
    }
}

We have an Shapeinterface and Shapean entity class that implements the interface. Then we created an abstract decorator class ShapeDecoratorand extended Shapethe interface . This decorator class wraps the decorated class to add new functions.

8. Observer mode

The observer pattern is a behavioral design pattern that defines a one-to-many dependency relationship between objects, so that when the state of an object changes, its related dependent objects will be notified and automatically updated. The Observer pattern is a behavioral pattern.

Application scenario: When the change of an object needs to change other objects at the same time, and it does not know how many objects need to be changed, the observer mode can be considered.
Sample code:

// 定义Subject,持有观察者的列表,并提供attach和notifyAllObservers方法
public class Subject {
    
    
    private List<Observer> observers = new ArrayList<Observer>();

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

    public void notifyAllObservers() {
    
    
        for (Observer observer : observers) {
    
    
            observer.update();
        }
    }
}

// 定义Observer,声明更新自己的抽象方法
public abstract class Observer {
    
    
    protected Subject subject;
    public abstract void update();
}

We created Subjecta class , Observeran abstract class, and a concrete class that extends Observerthe class . SubjectWhen an object changes state, all objects that depend on it are notified and automatically updated.

9. Strategy Mode

The strategy pattern is a behavioral design pattern that defines a series of algorithms, encapsulates each algorithm, and makes them interchangeable. The Strategy pattern lets an algorithm vary independently of the clients that use it.

Application scenario: A system has many types of algorithms or business logic. These algorithms or business logic can be encapsulated in different implementation classes of the same interface to reduce the use of multiple transfer statements (if...else if...else).

Sample code:

// 定义策略接口,声明算法方法
public interface Strategy {
    
    
    public int doOperation(int num1, int num2);
}

// 定义具体策略类
public class OperationAdd implements Strategy {
    
    


    public int doOperation(int num1, int num2) {
    
    
        return num1 + num2;
    }
}

// 环境类,持有一个策略类的引用
public class StrategyContext {
    
    
    private Strategy strategy;

    public StrategyContext(Strategy strategy) {
    
    
        this.strategy = strategy;
    }

    // 使用策略的方法
    public int executeStrategy(int num1, int num2) {
    
    
        return strategy.doOperation(num1, num2);
    }
}

We define a strategy interface Strategyand Strategyentity strategy class that implements the interface. ContextIs a class that uses a certain strategy. ContextThe object uses some strategy objects, which change the execution algorithm of Contextthe object .

10. Command mode

The command pattern is a behavioral design pattern that simplifies the dependencies between objects and reduces the complexity of composition and invocation by introducing levels between objects. This pattern involves five components: Client, Invoker, Command, ConcreteCommand, Receiver.

Application scenario: When it is necessary to decouple the request caller from the request receiver, the command mode prevents the caller from directly interacting with the receiver, and the caller does not need to know the interface of the receiver. The command pattern can be used to have multiple triggerers or receivers and need to issue requests to multiple objects.

Sample code:

// 命令接口,声明执行方法
public interface Order {
    
    
    void execute();
}

// 具体命令类,实现Order接口的execute方法,调用接收者的方法
public class StockRequest implements Order {
    
    
    private Stock stock;

    public StockRequest(Stock stock) {
    
    
        this.stock = stock;
    }

    public void execute() {
    
    
        stock.buy();
    }
}

// 调用者类,接收命令并执行
public class Broker {
    
    
    private List<Order> orderList = new ArrayList<Order>();

    public void takeOrder(Order order) {
    
    
        orderList.add(order);
    }

    public void placeOrders() {
    
    
        for (Order order : orderList) {
    
    
            order.execute();
        }
        orderList.clear();
    }
}

We create a request class Stock, a command interface Orderand Orderentity command class that implements the interface. The command implementation class holds a reference to the request and executes the request. BrokerObjects use command objects and queues to execute requests.

epilogue

In this blog, we have studied 10 commonly used Java design patterns, hoping that this knowledge can help you better solve problems in daily development or interviews. Remember, design patterns are just tools. In actual projects, we should choose the appropriate design pattern according to the project requirements and specific conditions, rather than forcefully applying it.

Guess you like

Origin blog.csdn.net/weixin_46703995/article/details/131238732