跳到主要内容

解密设计模式:从理论到应用场景的探究

·39386 字·79 分钟

本文将详细介绍多种设计模式,包括它们的概念、类型、通用代码、注意事项、优缺点以及在 Java 体系中的使用场景,并通过丰富的代码示例加深理解。

1. 单例模式(Singleton Pattern) #

1.1 定义 #

Ensure a class has only one instance,and provide a global point of access to it. (确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)

1.2 自己的理解 #

就像 Windows 系统中的任务管理器,无论在系统的任何地方调用它,都是同一个实例。在一个应用程序中,某些类只需要一个实例,例如数据库连接池、配置信息类等,单例模式可以确保这些类只有一个对象被创建,避免资源的浪费和不一致性。

1.3 类型 #

创建型模式。

1.4 通用代码 #

通用代码:(是线程安全的)

public class Singleton{
	private static final Singleton singleton =  new Singleton();
	//限制产生多个对象
	private Singleton(){
    }
	//通过该方法获得实例对象
	public static Singleton getSingleton(){
    	return singleton;
    }

	//类中其他方法,尽量是static
	public static void doSomething(){
    }
}    

线程不安全实例:

public class Singleton{
	private static Singleton singleton = null;
	//限制产生多个对象
	private Singleton(){
    }
	//通过该方法获得实例对象
	public static Singleton getSingleton(){
		if(singleton == null){
			singleton = new Singleton();
        }
		return singleton:
    }
}    

1.5 注意事项 #

  • 多线程环境下确保线程安全,如上述代码中的实现方式。
  • 避免在单例类中使用过多的非静态方法,以免影响单例的唯一性和线程安全性。

1.6 优缺点 #

  • 优点:
    • 内存中只有一个实例,节省资源。
    • 全局访问点,方便在不同地方获取实例。
  • 缺点:
    • 违反单一职责原则,既负责创建对象又负责业务逻辑。
    • 对测试不友好,因为单例类的状态在全局是共享的,可能影响测试结果的独立性。

1.7 使用场景: #

  • 要求生成唯一序列号的环境:
  • 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的:
  • 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源:
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
  • 数据库连接池管理类,确保整个应用程序使用同一个连接池实例。
public class DatabaseConnectionPool {
    private static DatabaseConnectionPool instance;
    private List<Connection> connections;

    private DatabaseConnectionPool() {
        // 初始化连接池
        connections = new ArrayList<>();
        // 模拟创建连接并添加到连接池
        for (int i = 0; i < 10; i++) {
            try {
                Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
                connections.add(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

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

    public Connection getConnection() {
        if (connections.isEmpty() {
            System.out.println("连接池为空,无法获取连接!");
            return null;
        }
        return connections.remove(0);
    }

    public void releaseConnection(Connection connection) {
        connections.add(connection);
    }
}
  • 配置信息类,在整个应用程序中共享配置数据。
public class AppConfig {
    private static AppConfig instance;
    private String serverUrl;
    private int maxConnections;

    private AppConfig() {
        // 从配置文件或其他地方加载配置信息
        serverUrl = "http://localhost:8080";
        maxConnections = 100;
    }

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

    public String getServerUrl() {
        return serverUrl;
    }

    public int getMaxConnections() {
        return maxConnections;
    }
}

2. 工厂模式 #

2.1 定义 #

Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)

2.2 自己的理解 #

工厂模式就像是一个生产产品的工厂,不同的产品由不同的车间(子类)生产。例如,汽车工厂可以生产不同类型的汽车,如轿车、SUV 等,具体生产哪种汽车由具体的车间来决定。在软件中,当创建对象的过程比较复杂,或者需要根据不同条件创建不同类型对象时,可以使用工厂模式将对象的创建逻辑封装起来,使代码结构更清晰,易于维护和扩展。

2.3 类型 #

创建型模式。

2.4 通用代码 #

  • Product 为抽象产品类负责定义产品的共性,实现对事物最抽象的定义;
public abstract class Product {
    public abstract void use();
}
  • Creator 为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。
public abstract class Creator {
    public abstract <T extends Product> T createProduct(Class<T> c);
}
  • 具体工厂类代码:
public class ConcreteCreator extends Creator {
    public <T extends Product> T createProduct(Class<T> c){
    	Product product = null;
        try{
           product = (Product)Class.forName(c.getName().newInstance();
        }catch (Exception e){
            //异常处理
            return (T)product;
        }
	}
}

2.5 注意事项 #

  • 工厂类的职责要单一,只负责对象的创建,不涉及过多业务逻辑。
  • 当产品种类较多时,可能需要创建多个工厂类,避免工厂类过于复杂。

2.6 优缺点 #

  • 优点:
    • 解耦对象的创建和使用,使用者不需要了解对象的创建过程。
    • 增加新的产品类时,只需要添加具体的产品类和对应的工厂子类,符合开闭原则。
  • 缺点:
    • 工厂类增多,代码复杂度增加。
    • 不符合迪米特法则,因为工厂类需要知道产品类的具体实现。

2.7 使用场景 #

  • Jdbc 连接数据库时,根据不同的数据库类型(如 MySQL、Oracle 等)创建不同的数据库连接对象。
public class DatabaseConnectionFactory {
    public static Connection createConnection(String databaseType, String url, String username, String password) throws SQLException {
        if ("MySQL".equals(databaseType) {
            return DriverManager.getConnection(url, username, password);
        } else if ("Oracle".equals(databaseType) {
            // 创建Oracle连接的逻辑
            return null;
        } else {
            throw new IllegalArgumentException("不支持的数据库类型:" + databaseType);
        }
    }
}
  • 硬件访问,根据不同的硬件设备类型创建相应的驱动对象。
public class HardwareDriverFactory {
    public static HardwareDriver createDriver(String deviceType) {
        if ("Printer".equals(deviceType) {
            return new PrinterDriver();
        } else if ("Scanner".equals(deviceType) {
            return new ScannerDriver();
        } else {
            throw new IllegalArgumentException("不支持的硬件设备类型:" + deviceType);
        }
    }
}

tips:

  • **简单工厂模式:**一个模块仅需要一个工厂类,没有必要把它产生出来,使用静态的方法
  • **多个工厂类:**每个人种(具体的产品类)都对应了一个创建者,每个创建者独立负责创建对应的产品对象,非常符合单一职责原则
  • **代替单例模式:**单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象
  • **延迟初始化:**ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要再次被重用的对象保留

3. 抽象工厂模式(Abstract Factory Pattern) #

3.1 定义 #

****Provide an interface for creating families of related or dependent objects without specifying their concrete classes. (为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。)

抽象工厂模式通用类图:

![]({{ ‘/assets/img/DesignPattern/11.png’ | prepend: ’’ }})

抽象工厂模式通用源码类图:

3.2 自己的理解 #

抽象工厂模式就像是一个超级工厂,它可以生产一系列相关的产品家族。例如,一个家具工厂可以生产不同风格(如现代、欧式、中式)的家具,每种风格的家具都包括桌椅、沙发等产品。在软件中,当需要创建多个相关对象,且这些对象的创建过程相互关联时,可以使用抽象工厂模式,将对象的创建逻辑封装在一个工厂接口中,由具体的工厂子类来创建所需的对象家族,这样可以保证创建出的对象之间的兼容性和一致性。

3.3 类型 #

创建型模式。

3.4 通用代码 #

  • 抽象工厂类代码:
public abstract class AbstractCreator{
    //创建A产品家族
	public abstract AbstractProductA createProductA();
	//创建B产品家族
	public abstract AbstractProductB createProductB();
}
  • 抽象产品类 A
public abstract class AbstractProductA {
    public abstract void methodA();
}
  • 抽象产品类 B。
public abstract class AbstractProductB {
    public abstract void methodB();
}
  • 具体工厂类。
public class ConcreteCreator1 extends AbstractCreator {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB1();
    }
}
  • 具体产品类 A1。
public class ConcreteProductA1 extends AbstractProductA {
    @Override
    public void methodA() {
        System.out.println("ConcreteProductA1的methodA方法");
    }
}
  • 具体产品类 B1。
public class ConcreteProductB1 extends AbstractProductB {
    @Override
    public void methodB() {
        System.out.println("ConcreteProductB1的methodB方法");
    }
}

3.5 注意事项 #

  • 产品家族的扩展可能需要修改抽象工厂接口和所有具体工厂类,违反开闭原则。
  • 不适合创建对象差异较大的产品家族,否则工厂类会变得复杂。

3.6 优缺点 #

  • 优点:
    • 保证产品家族的一致性和兼容性,便于切换产品家族。
    • 符合迪米特法则,使用者只与抽象工厂类交互,不需要了解对象的创建细节。
  • 缺点:
    • 不便于扩展产品家族,新增产品家族可能需要修改大量代码。
    • 抽象工厂类的职责过重,一旦修改,影响面较大。

3.7 使用场景 #

  • 跨平台应用程序中,根据不同操作系统(如 Windows、Linux、Mac)创建不同风格的界面组件。
public abstract class AbstractWidgetFactory {
    public abstract Button createButton();
    public abstract TextField createTextField();
}

public class WindowsWidgetFactory extends AbstractWidgetFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public TextField createTextField() {
        return new WindowsTextField();
    }
}

public class LinuxWidgetFactory extends AbstractWidgetFactory {
    @Override
    public Button createButton() {
        return new LinuxButton();
    }

    @Override
    public TextField createTextField() {
        return new LinuxTextField();
    }
}

public class MacWidgetFactory extends AbstractWidgetFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public TextField createTextField() {
        return new MacTextField();
    }
}
  • 游戏开发中,根据不同游戏场景(如战斗场景、主菜单场景)创建相应的游戏对象(如敌人、道具、UI 元素等)。
public abstract class AbstractGameObjectFactory {
    public abstract Enemy createEnemy();
    public abstract Item createItem();
    public abstract UIElement createUIElement();
}

public class BattleSceneGameObjectFactory extends AbstractGameObjectFactory {
    @Override
    public Enemy createEnemy() {
        return new BattleEnemy();
    }

    @Override
    public Item createItem() {
        return new BattleItem();
    }

    @Override
    public UIElement createUIElement() {
        return new BattleUIElement();
    }
}

public class MainMenuSceneGameObjectFactory extends AbstractGameObjectFactory {
    @Override
    public Enemy createEnemy() {
        return null;
    }

    @Override
    public Item createItem() {
        return null;
    }

    @Override
    public UIElement createUIElement() {
        return new MainMenuUIElement();
    }
}
  • 一个对象族(或是一组没有任何关系的对象)都有相同的约束。
  • 涉及不同操作系统的时候,都可以考虑使用抽象工厂模式

4. 模板方法模式(Template Method Pattern) #

4.1 定义 #

Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)

4.2 自己的理解 #

模板方法模式就像是一个制作蛋糕的模板,它定义了制作蛋糕的基本步骤(如准备原料、搅拌、烘焙、装饰),但某些步骤(如装饰)的具体实现可以由不同的蛋糕师傅(子类)根据自己的创意来完成。在软件中,当多个子类有共同的算法框架,但某些步骤的实现细节不同时,可以使用模板方法模式,将通用的算法框架提取到父类中,将可变的步骤定义为抽象方法,由子类实现,这样可以提高代码的复用性,避免重复代码,同时保证算法的稳定性。

4.3 类型 #

行为型模式。

4.4 通用代码 #

AbstractClass叫做抽象模板,它的方法分为两类:

  • 基本方法

基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用

  • 模板方法

可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。

**注意:**为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。

  • AbstractClass 为抽象模板类。
public abstract class AbstractClass {
    // 模板方法,定义算法框架
    public final void templateMethod() {
        step1();
        step2();
        step3();
    }

    // 基本方法,由子类实现
    protected abstract void step1();
    protected abstract void step2();
    protected abstract void step3();
}

**具体模板:**ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。

  • ConcreteClass1 和 ConcreteClass2 为具体模板类。
public class ConcreteClass1 extends AbstractClass {
    @Override
    protected void step1() {
        System.out.println("ConcreteClass1的step1实现");
    }

    @Override
    protected void step2() {
        System.out.println("ConcreteClass1的step2实现");
    }

    @Override
    protected void step3() {
        System.out.println("ConcreteClass1的step3实现");
    }
}

public class ConcreteClass2 extends AbstractClass {
    @Override
    protected void step1() {
        System.out.println("ConcreteClass2的step1实现");
    }

    @Override
    protected void step2() {
        System.out.println("ConcreteClass2的step2实现");
    }

    @Override
    protected void step3() {
        System.out.println("ConcreteClass2的step3实现");
    }
}

4.5 注意事项 #

  • 为防止子类恶意修改模板方法,通常将模板方法声明为 final。
  • 模板方法中的步骤顺序应尽量固定,避免子类依赖特定的步骤顺序。

4.6 优缺点 #

  • 优点:
    • 提高代码复用性,将公共算法框架提取到父类。
    • 符合开闭原则,子类可以在不改变算法结构的情况下扩展功能。
  • 缺点:
    • 子类对父类的依赖可能导致代码可读性下降,尤其是当模板方法复杂时。
    • 不适合算法步骤经常变动的情况,因为这可能需要修改父类的模板方法。

4.7 使用场景 #

  • 开发框架中,定义一些通用的业务流程模板,如 Web 应用中的用户注册、登录流程,具体的验证规则和数据存储方式可以由子类实现。
public abstract class UserProcessTemplate {
    public final void process() {
        validateInput();
        saveUser();
        sendNotification();
    }

    protected abstract void validateInput();
    protected abstract void saveUser();
    protected abstract void sendNotification();
}

public class UserRegistrationProcess extends UserProcessTemplate {
    @Override
    protected void validateInput() {
        // 验证注册信息的逻辑
        System.out.println("验证注册信息");
    }

    @Override
    protected void saveUser() {
        // 将用户信息保存到数据库的逻辑
        System.out.println("保存用户信息到数据库");
    }

    @Override
    protected void sendNotification() {
        // 发送注册成功通知的逻辑
        System.out.println("发送注册成功通知");
    }
}

public class UserLoginProcess extends UserProcessTemplate {
    @Override
    protected void validateInput() {
        // 验证登录信息的逻辑
        System.out.println("验证登录信息");
    }

    @Override
    protected void saveUser() {}

    @Override
    protected void sendNotification() {}
}
  • 游戏开发中,定义游戏角色的升级算法框架,不同角色的升级属性加成方式可以由子类实现。
public abstract class CharacterLevelUpTemplate {
    public final void levelUp() {
        checkLevelRequirements();
        addAttributePoints();
        unlockSkills();
    }

    protected abstract void checkLevelRequirements();
    protected abstract void addAttributePoints();
    protected abstract void unlockSkills();
}

public class WarriorLevelUp extends CharacterLevelUpTemplate {
    @Override
    protected void checkLevelRequirements() {
        // 检查战士升级所需经验值等条件的逻辑
        System.out.println("检查战士升级条件");
    }

    @Override
    protected void addAttributePoints() {
        // 为战士增加力量、体力等属性点的逻辑
        System.out.println("为战士增加属性点");
    }

    @Override
    protected void unlockSkills() {
        // 解锁战士技能的逻辑
        System.out.println("解锁战士技能");
    }
}

public class MageLevelUp extends CharacterLevelUpTemplate {
    @Override
    protected void checkLevelRequirements() {
        // 检查法师升级所需经验值等条件的逻辑
        System.out.println("检查法师升级条件");
    }

    @Override
    protected void addAttributePoints
  • 数据处理流程框架:在数据处理应用中,常常需要对不同类型的数据进行相似的处理流程,如数据读取、清洗、转换、分析和存储。可以使用模板方法模式定义一个抽象的数据处理类,将数据读取、存储等通用步骤在父类中实现,而数据清洗、转换和分析等步骤定义为抽象方法由具体子类实现。
public abstract class DataProcessor {
    public final void processData() {
        readData();
        cleanData();
        transformData();
        analyzeData();
        saveData();
    }

    protected void readData() {
        System.out.println("从数据源读取数据");
    }

    protected void saveData() {
        System.out.println("将处理后的数据保存");
    }

    protected abstract void cleanData();
    protected abstract void transformData();
    protected abstract void analyzeData();
}

public class TextDataProcessor extends DataProcessor {
    @Override
    protected void cleanData() {
        System.out.println("清洗文本数据");
    }

    @Override
    protected void transformData() {
        System.out.println("转换文本数据格式");
    }

    @Override
    protected void analyzeData() {
        System.out.println("分析文本数据内容");
    }
}

public class NumericalDataProcessor extends DataProcessor {
    @Override
    protected void cleanData() {
        System.out.println("清洗数值数据");
    }

    @Override
    protected void transformData() {
        System.out.println("转换数值数据单位");
    }

    @Override
    protected void analyzeData() {
        System.out.println("分析数值数据统计特征");
    }
}
  • 多个子类有公有的方法,并且逻辑基本相同时。
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。

5. 建造者模式(Builder Pattern) #

5.1 定义 #

Separate the construction of a complex object from its representation so that the same construction process can create different representations.(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。)

5.2 自己的理解 #

建造者模式类似于组装一台电脑,电脑的各个组件(如 CPU、主板、内存、硬盘等)的组装过程是固定的,但可以根据用户的需求和预算选择不同品牌和型号的组件,最终组装出不同配置和性能的电脑。在软件中,当创建复杂对象时,将对象的构建过程和表示分离,由建造者负责构建对象的各个部分,导演类负责控制构建过程的顺序,这样可以灵活地创建出具有不同属性和配置的复杂对象,并且代码结构更加清晰,易于维护和扩展。

5.3 类型 #

创建型模式。

5.4 通用代码 #

  • Product 产品类:通常实现了模板方法模式,包含模板方法和基本方法。
public class Product {
    private String partA;
    private String partB;
    private String partC;

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }

    public void showProduct() {
        System.out.println("产品组成:PartA = " + partA + ", PartB = " + partB + ", PartC = " + partC);
    }
}
  • Builder 抽象建造者:规范产品的组建,一般由子类实现。
public abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();

    public Product getProduct() {
        return product;
    }
}
  • ConcreteBuilder 具体建造者:实现抽象类定义的所有方法,并返回组建好的对象。
public class ConcreteBuilder1 extends Builder {
    @Override
    public void buildPartA() {
        product.setPartA("ComponentA1");
    }

    @Override
    public void buildPartB() {
        product.setPartB("ComponentB1");
    }

    @Override
    public void buildPartC() {
        product.setPartC("ComponentC1");
    }
}

public class ConcreteBuilder2 extends Builder {
    @Override
    public void buildPartA() {
        product.setPartA("ComponentA2");
    }

    @Override
    public void buildPartB() {
        product.setPartB("ComponentB2");
    }

    @Override
    public void buildPartC() {
        product.setPartC("ComponentC2");
    }
}
  • Director 导演类:负责安排已有模块的顺序,然后告诉 Builder 开始建造。
public class Director {
    private Builder builder;

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

    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

5.5 注意事项 #

  • 建造者模式中,导演类和建造者类的职责要明确划分,导演类只负责控制构建过程的顺序,建造者类负责对象的具体构建,避免职责混乱。
  • 如果产品的组成部分变化频繁,可能需要频繁修改建造者类,因此在设计时要考虑产品结构的稳定性。

5.6 优缺点 #

  • 优点:
    • 可以创建复杂对象,并且将构建过程和表示分离,使得代码结构清晰,易于理解和维护。
    • 可以根据不同需求灵活创建不同表示的对象,提高了对象创建的灵活性。
  • 缺点:
    • 建造者模式需要创建多个类(导演类、建造者类及其子类、产品类),增加了代码量和系统复杂度。
    • 如果产品的属性或构建过程相似性较低,使用建造者模式可能会导致代码冗余。

5.7 使用场景 #

  • 创建数据库连接配置对象:数据库连接配置包含多个参数,如 URL、用户名、密码、驱动类等,根据不同的数据库类型和环境,可以使用建造者模式创建不同配置的连接对象。
public class DatabaseConnection {
    private String url;
    private String username;
    private String password;
    private String driverClass;

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setDriverClass(String driverClass) {
        this.driverClass = driverClass;
    }

    public void connect() {
        System.out.println("连接数据库:URL = " + url + ", 用户名 = " + username + ", 密码 = " + password + ", 驱动类 = " + driverClass);
    }
}

public abstract class DatabaseConnectionBuilder {
    protected DatabaseConnection connection = new DatabaseConnection();

    public abstract void buildUrl();
    public abstract void buildUsername();
    public abstract void buildPassword();
    public abstract void buildDriverClass();

    public DatabaseConnection getConnection() {
        return connection;
    }
}

public class MySQLConnectionBuilder extends DatabaseConnectionBuilder {
    @Override
    public void buildUrl() {
        connection.setUrl("jdbc:mysql://localhost:3306/mydb");
    }

    @Override
    public void buildUsername() {
        connection.setUsername("root");
    }

    @Override
    public void buildPassword() {
        connection.setPassword("password");
    }

    @Override
    public void buildDriverClass() {
        connection.setDriverClass("com.mysql.jdbc.Driver");
    }
}

public class OracleConnectionBuilder extends DatabaseConnectionBuilder {
    @Override
    public void buildUrl() {
        connection.setUrl("jdbc:oracle:thin:@localhost:1521:orcl");
    }

    @Override
    public void buildUsername() {
        connection.setUsername("system");
    }

    @Override
    public void buildPassword() {
        connection.setPassword("oracle");
    }

    @Override
    public void buildDriverClass() {
        connection.setDriverClass("oracle.jdbc.driver.OracleDriver");
    }
}

public class ConnectionDirector {
    private DatabaseConnectionBuilder builder;

    public ConnectionDirector(DatabaseConnectionBuilder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildUrl();
        builder.buildUsername();
        builder.buildPassword();
        builder.buildDriverClass();
    }
}
  • 创建复杂的 UI 界面组件:例如创建一个包含多个子组件(如按钮、文本框、标签等)的表单组件,根据不同的页面需求,可以使用建造者模式构建不同布局和样式的表单。
public class Form {
    private Button button;
    private TextField textField;
    private Label label;

    public void setButton(Button button) {
        this.button = button;
    }

    public void setTextField(TextField textField) {
        this.textField = textField;
    }

    public void setLabel(Label label) {
        this.label = label;
    }

    public void display() {
        System.out.println("表单组件:按钮 = " + button + ", 文本框 = " + textField + ", 标签 = " + label);
    }
}

public abstract class FormBuilder {
    protected Form form = new Form();

    public abstract void buildButton();
    public abstract void buildTextField();
    public abstract void buildLabel();

    public Form getForm() {
        return form;
    }
}

public class LoginFormBuilder extends FormBuilder {
    @Override
    public void buildButton() {
        form.setButton(new Button("登录");
    }

    @Override
    public void buildTextField() {
        form.setTextField(new TextField("用户名");
        form.setTextField(new TextField("密码");
    }

    @Override
    public void buildLabel() {
        form.setLabel(new Label("登录表单");
    }
}

public class RegistrationFormBuilder extends FormBuilder {
    @Override
    public void buildButton() {
        form.setButton(new Button("注册");
    }

    @Override
    public void buildTextField() {
        form.setTextField(new TextField("姓名");
        form.setTextField(new TextField("邮箱");
        form.setTextField(new TextField("密码");
        form.setTextField(new TextField("确认密码");
    }

    @Override
    public void buildLabel() {
        form.setLabel(new Label("注册表单");
    }
}

public class FormDirector {
    private FormBuilder builder;

    public FormDirector(FormBuilder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildLabel();
        builder.buildTextField();
        builder.buildButton();
    }
}
  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。

建造者模式与工厂模式的不同:

建造者模式最主要的功能是基本方法的调用顺序安排,这些基本方法已经实现了,顺序不同产生的对象也不同;

工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。

6. 代理模式(Proxy Pattern) #

6.1 定义 #

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

6.2 自己的理解 #

代理模式就像是一个中介,当你想要访问某个对象时,不是直接与该对象交互,而是通过代理对象来间接访问。例如,你想购买国外的商品,你可能不会直接与国外商家联系,而是通过代购(代理)来完成购买。在软件中,代理模式可以在不改变目标对象接口的前提下,对目标对象的访问进行控制,如权限控制、懒加载、远程访问等,同时还可以在目标对象方法执行前后添加额外的逻辑,如日志记录、性能监控等。

6.3 类型 #

结构型模式。

6.4 通用代码 #

  • Subject 抽象主题角色:可以是抽象类或接口,定义了目标对象和代理对象共同的业务方法。
public interface Subject {
    void request();
}
  • RealSubject 具体主题角色:实现了抽象主题角色的业务方法,是真正的业务逻辑执行者。
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject处理请求");
    }
}
  • Proxy 代理主题角色:实现了抽象主题角色,内部包含对真实主题角色的引用,并在调用真实主题角色方法前后添加额外逻辑。
public class Proxy implements Subject {
    private RealSubject realSubject;

    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        System.out.println("Proxy在请求前的预处理");
        realSubject.request();
        System.out.println("Proxy在请求后的后处理");
    }
}

6.5 注意事项 #

  • 代理模式可能会增加系统的复杂性,尤其是在动态代理的情况下,需要理解动态代理的机制和相关的反射知识。
  • 在使用代理模式时,要注意代理对象和目标对象之间的一致性,确保代理对象能够正确地代理目标对象的方法,并且不会破坏目标对象的原有功能。

普通代理和强制代理:

普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问:

强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。

普通代理:

在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。

强制代理:

强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理己经由真实角色自己完成。

动态代理:

根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称我已经实现该接口下的所有方法了。

两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。

动态代理调用过程示意图:

![]({{ ‘/assets/img/DesignPattern/33.png’ | prepend: ’’ }})

动态代理的意图:横切面编程,在不改变我们己有代码结构的情况下增强或控制对象的行为。

**首要条件:**被代理的类必须要实现一个接口。

6.6 优缺点 #

  • 优点:
    • 可以实现对目标对象的访问控制,保护目标对象的安全,例如通过权限代理限制某些用户对敏感资源的访问。
    • 能够在不修改目标对象的前提下,增强目标对象的功能,如添加日志记录、性能监控等横切关注点,符合开闭原则。
    • 对于一些创建开销较大的对象,可以使用代理模式进行懒加载,延迟对象的创建,提高系统性能。
  • 缺点:
    • 由于增加了代理层,可能会降低系统的性能,尤其是在简单的应用场景中,如果代理逻辑过于复杂,可能会带来额外的开销。
    • 代理模式的代码结构相对复杂,增加了代码的理解和维护难度,尤其是在动态代理的情况下,需要掌握更多的技术细节。

6.7 使用场景 #

  • 远程代理:在分布式系统中,当客户端需要访问远程服务器上的对象时,可以使用代理模式创建一个本地代理对象,客户端通过与本地代理对象交互,由代理对象负责与远程服务器进行通信,实现远程方法的调用,就像 RMI(Remote Method Invocation)技术。
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

// 远程接口
public interface RemoteService extends Remote {
    String getData() throws RemoteException;
}

// 远程服务的实现类
public class RemoteServiceImpl implements RemoteService {
    @Override
    public String getData() throws RemoteException {
        return "这是远程服务器上的数据";
    }
}

// 远程代理类
public class RemoteServiceProxy implements RemoteService {
    private RemoteService remoteService;

    public RemoteServiceProxy() throws RemoteException {
        // 在构造函数中初始化远程服务对象
        this.remoteService = (RemoteService) UnicastRemoteObject.exportObject(new RemoteServiceImpl(), 0);
    }

    @Override
    public String getData() throws RemoteException {
        System.out.println("远程代理在调用远程方法前的预处理");
        String data = remoteService.getData();
        System.out.println("远程代理在调用远程方法后的后处理");
        return data;
    }
}
  • 安全代理:在企业级应用中,对于一些敏感资源或操作,如数据库访问、文件操作等,可以使用安全代理进行权限控制,只有授权用户才能访问真实的资源。
public interface Resource {
    void access();
}

public class SecureResource implements Resource {
    @Override
    public void access() {
        System.out.println("访问敏感资源");
    }
}

public class SecureResourceProxy implements Resource {
    private SecureResource secureResource;
    private User user;

    public SecureResourceProxy(User user) {
        this.user = user;
        if (user.hasPermission() {
            this.secureResource = new SecureResource();
        }
    }

    @Override
    public void access() {
        if (secureResource!= null) {
            System.out.println("安全代理进行权限验证通过,允许访问");
            secureResource.access();
        } else {
            System.out.println("安全代理进行权限验证失败,禁止访问");
        }
    }
}

public class User {
    private boolean permission;

    public User(boolean permission) {
        this.permission = permission;
    }

    public boolean hasPermission() {
        return permission;
    }
}
  • 缓存代理:对于一些计算成本较高或获取数据开销较大的操作,可以使用缓存代理。缓存代理在第一次调用目标对象的方法时,将结果缓存起来,后续相同的调用直接返回缓存结果,提高系统性能。
public interface ExpensiveOperation {
    int calculate(int num);
}

public class ExpensiveOperationImpl implements ExpensiveOperation {
    @Override
    public int calculate(int num) {
        System.out.println("执行复杂计算");
        return num * 2;
    }
}

public class CachedProxy implements ExpensiveOperation {
    private ExpensiveOperationImpl expensiveOperation;
    private Map<Integer, Integer> cache = new HashMap<>();

    @Override
    public int calculate(int num) {
        if (cache.containsKey(num) {
            System.out.println("从缓存中获取结果");
            return cache.get(num);
        } else {
            int result = expensiveOperation.calculate(num);
            cache.put(num, result);
            return result;
        }
    }
}

7. 原型模式(Prototype Pattern) #

7.1 定义 #

Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)

7.2 自己的理解 #

原型模式类似于复印机,当你需要一份文档的副本时,只需将原始文档放入复印机(原型对象),就能快速得到一份内容相同的副本(新对象)。在软件中,当创建对象的过程较为复杂,或者创建一个新对象的成本较高(如需要大量初始化操作、资源消耗大等),且对象之间的差异较小时,可以使用原型模式。通过拷贝已有对象(原型)来创建新对象,避免了重复的初始化过程,提高了创建对象的效率。

7.3 类型 #

创建型模式。

7.4 通用代码 #

public class PrototypeClass implements Cloneable {
    // 覆写父类Object方法
    @Override
    public PrototypeClass clone() {
        PrototypeClass prototypeClass = null;
        try {
            prototypeClass = (PrototypeClass) super.clone();
        } catch (CloneNotSupportedException e) {
            // 异常处理
        }
        return prototypeClass;
    }
}

7.5 注意事项 #

  • 使用原型模式时,需要确保被拷贝的对象的成员变量满足一定条件,引用的成员变量必须是可变的引用对象,且不是方法内变量,否则可能导致拷贝后的对象共享相同的引用,引发意外的结果。
  • 如果对象的构造函数中有重要的初始化逻辑,而原型模式是通过拷贝来创建对象,可能会绕过这些初始化逻辑,需要谨慎处理。

原型模式实际上就是实现Cloneable接口,置写clone()方法。

浅拷贝和深拷贝:

浅拷贝:Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象****等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝,其他的原始类型比如int、long、char、string(当做是原始类型)等都会被拷贝。

**注意:**使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。

**深拷贝:**对私有的类变量进行独立的拷贝。

如:thing.arrayList=(ArrayList)this.arrayListclone();

7.6 优缺点 #

  • 优点:
    • 性能优良,尤其是在创建大量相似对象时,通过内存拷贝比直接使用 new 关键字创建对象效率更高,因为避免了重复的初始化过程。
    • 可以逃避构造函数的约束,直接在内存中拷贝对象,对于一些特殊情况(如对象初始化需要复杂的资源获取或权限验证等)可能会更加方便。
  • 缺点:
    • 深拷贝和浅拷贝的选择需要谨慎,如果处理不当,可能会导致对象状态的不一致或内存泄漏等问题。
    • 对于复杂对象的拷贝,尤其是包含循环引用的对象,实现深拷贝可能会比较复杂,容易出错。

7.7 使用场景 #

  • 对象池技术:在游戏开发或数据库连接管理中,经常需要创建和销毁大量相同类型的对象,如游戏中的子弹、敌人等对象,或者数据库连接对象。使用原型模式创建对象池,预先创建一定数量的对象(原型),当需要对象时从池中获取,使用完毕后放回池中,避免频繁创建和销毁对象带来的性能开销。
import java.util.ArrayList;
import java.util.List;

public class ObjectPool<T extends Cloneable> {
    private List<T> pool;
    private T prototype;

    public ObjectPool(T prototype) {
        this.prototype = prototype;
        pool = new ArrayList<>();
        // 预先创建一定数量的对象放入池中
        for (int i = 0; i < 10; i++) {
            pool.add(prototype.clone();
        }
    }

    public T acquire() {
        if (pool.isEmpty() {
            System.out.println("对象池为空,创建新对象");
            return prototype.clone();
        } else {
            return pool.remove(0);
        }
    }

    public void release(T object) {
        pool.add(object);
    }
}

public class Bullet implements Cloneable {
    private int damage;

    public Bullet(int damage) {
        this.damage = damage;
    }

    @Override
    public Bullet clone() {
        try {
            return (Bullet) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    public int getDamage() {
        return damage;
    }
}
  • 配置对象的复制:在应用程序中,对于一些配置对象,如果需要根据不同的场景创建略有不同的配置副本,可以使用原型模式。例如,一个应用程序有不同的运行模式(如开发模式、测试模式、生产模式),每个模式的配置大部分相同,只有少数参数不同,可以通过拷贝原型配置对象,然后修改部分参数来快速创建不同模式的配置。
public class AppConfig implements Cloneable {
    private String databaseUrl;
    private String serverAddress;
    private boolean debugMode;

    public AppConfig(String databaseUrl, String serverAddress, boolean debugMode) {
        this.databaseUrl = databaseUrl;
        this.serverAddress = serverAddress;
        this.debugMode = debugMode;
    }

    @Override
    public AppConfig clone() {
        try {
            return (AppConfig) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    public void setDatabaseUrl(String databaseUrl) {
        this.databaseUrl = databaseUrl;
    }

    public void setServerAddress(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    public void setDebugMode(boolean debugMode) {
        this.debugMode = debugMode;
    }

    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public String getServerAddress() {
        return serverAddress;
    }

    public boolean isDebugMode() {
        return debugMode;
    }
}
  • 资源优化场景
    • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  • 性能和安全要求的场景
    • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  • 一个对象多个修改者的场景
    • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

8. 中介者模式 #

8.1 定义 #

Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.(用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散。而且可以独立地改变它们之间的交互。)

8.2 自己的理解 #

中介者模式就像是一个社交聚会的组织者,各个参与者(同事对象)之间不需要直接相互交流所有信息,而是通过组织者(中介者)来传递信息和协调互动。例如,在一个多人在线游戏中,玩家之间的交易、组队、聊天等交互行为都可以通过游戏服务器(中介者)来管理,玩家只需与服务器交互,服务器负责将信息转发给其他相关玩家,这样可以降低玩家之间的耦合度,提高系统的灵活性和可维护性。

8.3 类型 #

行为型模式。

8.4 通用代码 #

  • Mediator 抽象中介者角色:定义统一的接口,用于各同事角色之间的通信。
public abstract class Mediator {
    // 定义同事类
    protected ConcreteColleague1 c1;
    protected ConcreteColleague2 c2;

    // 通过getter/setter方法把同事类注入进来
    public ConcreteColleague1 getC1() {
        return c1;
    }

    public void setC1(ConcreteColleague1 c1) {
        this.c1 = c1;
    }

    public ConcreteColleague2 getC2() {
        return c2;
    }

    public void setC2(ConcreteColleague2 c2) {
        this.c2 = c2;
    }

    // 中介者模式的业务逻辑
    public abstract void doSomething1();
    public abstract void doSomething2();
}
  • Concrete Mediator 具体中介者角色:通过协调各同事角色实现协作行为,依赖于各个同事角色。
public class ConcreteMediator extends Mediator {
    @Override
    public void doSomething1() {
        c1.selfMethod();
        c2.depMethod();
    }

    @Override
    public void doSomething2() {
        c2.selfMethod();
        c1.depMethod();
    }
}
  • Colleague 同事角色:每个同事角色都知道中介者角色,与其他同事通信时通过中介者协作。

每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作、每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖:第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)。

public class ConcreteColleague1 {
    private Mediator mediator;

    public ConcreteColleague1(Mediator mediator) {
        this.mediator = mediator;
        mediator.setC1(this);
    }

    public void selfMethod() {
        System.out.println("ConcreteColleague1的自发行为");
    }

    public void depMethod() {
        System.out.println("ConcreteColleague1的依赖方法");
        mediator.doSomething1();
    }
}

public class ConcreteColleague2 {
    private Mediator mediator;

    public ConcreteColleague2(Mediator mediator) {
        this.mediator = mediator;
        mediator.setC2(this);
    }

    public void selfMethod() {
        System.out.println("ConcreteColleague2的自发行为");
    }

    public void depMethod() {
        System.out.println("ConcreteColleague2的依赖方法");
        mediator.doSomething2();
    }
}
  • 通用抽象中介者代码:
public abstract class Mediator {
	// 定义同事类
	protected ConcreteColleague1 c1;
	protected ConcreteColleague2 c2;

	// 通过getter/setter方法把同事类注入进来
	public ConcreteColleague1 getC1() {
		return c1;
	}

	public void setC1(ConcreteColleague1 c1) {
		this.c1 = c1;
	}

	public ConcreteColleague2 getC2() {
		return c2;
	}

	public void setC2(ConcreteColleague2 c2) {
		this.c2 = c2;
	}
	// 中介者模式的业务逻辑 
	public abstract void doSomething1(); 
	public abstract void doSomething2();
}

**PS:**使用同事类注入而不使用抽象注入的原因是因为抽象类中不具有每个同事类必须要完成的方法。即每个同事类中的方法各不相同。

**问:**为什么同事类要使用构造函数注入中介者,而中介者使用getter/setter方式注入同事类呢?

这是因为同事类必须有中介者,而中介者却可以只有部分同事类。

8.5 注意事项 #

  • 中介者类可能会变得过于复杂,尤其是当同事类之间的交互逻辑较多时,中介者需要协调处理大量的信息,容易导致中介者类的职责过重,违背单一职责原则。
  • 中介者模式会使同事类之间的依赖关系转移到中介者类上,如果中介者类出现问题,可能会影响整个系统的正常运行,增加了系统的风险。

8.6 优缺点 #

  • 优点:
    • 降低了对象之间的耦合度,使得同事类之间的关系更加松散,易于维护和扩展。各同事类只需要与中介者交互,不需要了解其他同事类的具体实现,符合迪米特法则。
    • 可以将复杂的对象交互逻辑集中在中介者类中,提高了系统的可维护性,当交互逻辑发生变化时,只需要修改中介者类,而不需要修改各个同事类。
  • 缺点:
    • 中介者类的复杂性可能会增加,尤其是在处理大量同事类和复杂交互逻辑时,中介者类可能会变得庞大且难以理解和维护。
    • 中介者模式可能会导致系统的性能下降,因为所有的交互都需要通过中介者进行转发,增加了一定的间接性和开销。

8.7 使用场景 #

  • 聊天系统:在即时通讯软件中,多个用户之间的聊天消息传递可以通过中介者(服务器)来实现。用户发送消息时,将消息发送给服务器,服务器根据接收方的信息将消息转发给相应的用户,用户之间不需要直接建立连接,降低了用户之间的耦合度,便于系统的扩展和管理。
public abstract class ChatMediator {
    public abstract void sendMessage(User user, String message);
    public abstract void addUser(User user);
}

public class ChatServer implements ChatMediator {
    private List<User> users = new ArrayList<>();

    @Override
    public void sendMessage(User user, String message) {
        for (User u : users) {
            if (u!= user) {
                u.receiveMessage(message);
            }
        }
    }

    @Override
    public void addUser(User user) {
        users.add(user);
    }
}

public class User {
    private String name;
    private ChatMediator mediator;

    public User(String name, ChatMediator mediator) {
        this.name = name;
        this.mediator = mediator;
        mediator.addUser(this);
    }

    public void sendMessage(String message) {
        mediator.sendMessage(this, message);
    }

    public void receiveMessage(String message) {
        System.out.println(name + "收到消息:" + message);
    }
}
  • 交通控制系统:在交通路口,车辆、行人等交通参与者之间的交互(如信号灯控制、车辆让行等)可以通过交通控制器(中介者)来协调。交通控制器根据交通规则和各个方向的交通流量,控制信号灯的变化,指挥车辆和行人的通行,避免交通参与者之间直接复杂的交互,提高交通系统的安全性和效率。
public abstract class TrafficMediator {
    public abstract void setTrafficLight(TrafficLight light);
    public abstract void vehicleArrives(Vehicle vehicle);
    public abstract void pedestrianArrives(Pedestrian pedestrian);
}

public class TrafficController implements TrafficMediator {
    private TrafficLight trafficLight;

    @Override
    public void setTrafficLight(TrafficLight light) {
        this.trafficLight = light;
    }

    @Override
    public void vehicleArrives(Vehicle vehicle) {
        if (trafficLight.isGreen() {
            vehicle.go();
        } else {
            vehicle.stop();
        }
    }

    @Override
    public void pedestrianArrives(Pedestrian pedestrian) {
        if (trafficLight.isGreenForPedestrian() {
            pedestrian.cross();
        } else {
            pedestrian.wait();
        }
    }
}

public class Vehicle {
    private String licensePlate;
    private TrafficMediator mediator;

    public Vehicle(String licensePlate, TrafficMediator mediator) {
        this.licensePlate = licensePlate;
        this.mediator = mediator;
    }

    public void go() {
        System.out.println(licensePlate + "车辆通行");
    }

    public void stop() {
        System.out.println(licensePlate + "车辆停止");
    }

    public void arrive() {
        mediator.vehicleArrives(this);
    }
}

public class Pedestrian {
    private String name;
    private TrafficMediator mediator;

    public Pedestrian(String name, TrafficMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public void cross() {
        System.out.println(name + "行人过马路");
    }

    public void wait() {
        System.out.println(name + "行人等待");
    }

    public void arrive() {
        mediator.pedestrianArrives(this);
    }
}

public class TrafficLight {
    private boolean green;
    private boolean greenForPedestrian;

    public TrafficLight(boolean green, boolean greenForPedestrian) {
        this.green = green;
        this.greenForPedestrian = greenForPedestrian;
    }

    public boolean isGreen() {
        return green;
    }

    public boolean isGreenForPedestrian() {
        return greenForPedestrian;
    }
}
  • 中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构,即每个类都与其他的类有直接的联系。

9. 命令模式 #

9.1 定义 #

Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。)

9.2 自己的理解 #

命令模式就像是一个遥控器,遥控器上的每个按钮(命令对象)都对应着一个具体的操作(如打开电视、切换频道、调节音量等),当你按下按钮时,遥控器就会发送相应的命令给电视(接收者)执行。在软件中,将请求封装成命令对象,可以将请求的发送者和接收者解耦,使得请求可以被参数化、排队、记录日志或撤销等,增加了系统的灵活性和可扩展性。

9.3 类型 #

行为型模式。

9.4 通用代码 #

  • Receive 接收者角色:执行实际操作的角色,命令传递到这里被执行。
public class Receiver {
    public void action() {
        System.out.println("执行接收者的操作");
    }
}
  • Command 命令角色:声明执行命令的接口,包含执行命令的方法。
public interface Command {
    void execute();
}
  • ConcreteCommand 具体命令角色:实现命令接口,关联接收者,在执行方法中调用接收者的相应方法。
public class ConcreteCommand implements Command {
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.action();
    }
}
  • Invoker 调用者角色:负责接收命令并执行命令。
public class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }
}

9.5 注意事项 #

  • 在设计命令模式时,要注意命令对象和接收者对象之间的解耦程度,确保命令对象可以独立于接收者对象进行扩展和维护,同时接收者对象的变化不会影响到命令对象的接口。
  • 如果系统中的命令种类繁多,可能会导致命令类的数量过多,增加系统的复杂性。可以考虑使用命令模式的扩展,如命令队列、命令组合等方式来优化设计。

9.6 优缺点 #

  • 优点:
    • 实现了请求发送者和接收者之间的解耦,发送者只需要知道如何发送命令,而不需要了解接收者的具体操作,接收者也不需要了解命令的发送者,提高了系统的灵活性和可维护性。
    • 可以方便地对命令进行扩展、组合和复用,例如可以创建新的命令类来实现新的功能,或者将多个命令组合成一个复合命令来执行一系列操作。
    • 支持命令的排队、记录日志和撤销恢复等功能,为系统提供了更多的控制和管理能力。
  • 缺点:
    • 每一个命令都需要一个具体的命令类,可能会导致类的数量过多,尤其是在复杂的系统中,过多的类会增加代码的理解和维护难度。
    • 命令模式的实现可能会引入一定的性能开销,例如创建命令对象、存储命令对象等操作都需要消耗一定的资源。

9.7 使用场景 #

  • 图形界面操作:在图形用户界面(GUI)中,用户的各种操作(如点击按钮、选择菜单等)都可以看作是命令。例如,一个绘图软件中,“绘制直线”“绘制矩形”“填充颜色” 等操作都可以封装成相应的命令类,当用户点击相应的工具按钮时,就会创建并执行对应的命令,实现图形的绘制和编辑。
import java.awt.Graphics;

public class DrawLineCommand implements Command {
    private Graphics graphics;
    private int x1, y1, x2, y2;

    public DrawLineCommand(Graphics graphics, int x1, int y1, int x2, int y2) {
        this.graphics = graphics;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    @Override
    public void execute() {
        graphics.drawLine(x1, y1, x2, y2);
    }
}

public class DrawRectangleCommand implements Command {
    private Graphics graphics;
    private int x, y, width, height;

    public DrawRectangleCommand(Graphics graphics, int x, int y, int width, int height) {
        this.graphics = graphics;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    @Override
    public void execute() {
        graphics.drawRect(x, y, width, height);
    }
}

public class GraphicsEditor {
    private Invoker invoker;

    public GraphicsEditor() {
        invoker = new Invoker();
    }

    public void drawLine(Graphics graphics, int x1, int y1, int x2, int y2) {
        Command command = new DrawLineCommand(graphics, x1, y1, x2, y2);
        invoker.setCommand(command);
        invoker.executeCommand();
    }

    public void drawRectangle(Graphics graphics, int x, int y, int width, int height) {
        Command command = new DrawRectangleCommand(graphics, x, y, width, height);
        invoker.setCommand(command);
        invoker.executeCommand();
    }
}
  • 游戏操作控制:在游戏中,玩家的各种操作指令(如移动角色、攻击敌人、使用道具等)也可以使用命令模式来实现。每个操作对应一个命令类,游戏引擎作为调用者,接收玩家的输入并执行相应的命令,控制游戏角色的行为。
public class MoveCharacterCommand implements Command {
    private Character character;
    private int x, y;

    public MoveCharacterCommand(Character character, int x, int y) {
        this.character = character;
        this.x = x;
        this.y = y;
    }

    @Override
    public void execute() {
        character.move(x, y);
    }
}

public class AttackCommand implements Command {
    private Character character;
    private Enemy enemy;

    public AttackCommand(Character character, Enemy enemy) {
        this.character = character;
        this.enemy = enemy;
    }

    @Override
    public void execute() {
        character.attack(enemy);
    }
}

public class GameEngine {
    private Invoker invoker;

    public GameEngine() {
        invoker = new Invoker();
    }

    public void handleInput(String input, Character character, Enemy enemy) {
        if ("move".equals(input) {
            Command command = new MoveCharacterCommand(character, 1, 0);
            invoker.setCommand(command);
            invoker.executeCommand();
        } else if ("attack".equals(input) {
            Command command = new AttackCommand(character, enemy);
            invoker.setCommand(command);
            invoker.executeCommand();
        }
    }
}
  • 认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发一反馈机制的处理等。

10. 责任链模式 #

10.1 定义 #

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the requestChain the receiving objects and pass the request along the chain until an object handles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系,将这些对象连成一条链并沿着这条链传递该请求,直到有对象处理它为止。)

10.2 自己的理解 #

责任链模式就像是一个工作流程中的审批链,一个请求(如请假申请、报销申请等)从链的起始端开始传递,每个环节的审批人(处理者对象)都有机会处理该请求,如果当前审批人能够处理,则处理完毕;如果不能处理,则将请求传递给下一个审批人,直到有合适的审批人处理该请求或者请求到达链的末尾。在软件中,这种模式可以将请求的处理逻辑分散到多个对象中,提高系统的灵活性和可扩展性,同时降低了请求发送者和处理者之间的耦合度。

10.3 类型 #

行为型模式。

10.4 通用代码 #

  • Handler 抽象处理者:定义处理请求的接口,包含处理请求的方法和设置下一个处理者的方法,同时定义处理者的级别。
public abstract class Handler {
    private Handler nextHandler;

    public final Response handleMessage(Request request) {
        Response response = null;
        // 判断是否是自己的处理级别
        if (this.getHandlerLevel().equals(request.getRequestLevel()) {
            response = this.echo(request);
        } else {
            // 判断是否有下一个处理者
            if (this.nextHandler!= null) {
                response = this.nextHandler.handleMessage(request);
            } else {
                // 没有适当的处理者,业务自行处理
            }
        }
        return response;
    }

    // 设置下一个处理者是谁
    public void setNext(Handler _handler) {
        this.nextHandler = _handler;
    }

    // 每个处理者都有一个处理级别
    protected abstract Level getHandlerLevel();

    // 每个处理者都必须实现处理任务
    protected abstract Response echo(Request request);
}
  • ConcreteHandler 具体处理者:实现抽象处理者的接口,根据自身的处理级别决定是否处理请求。
public class ConcreteHandler1 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        return Level.LEVEL1;
    }

    @Override
    protected Response echo(Request request) {
        System.out.println("ConcreteHandler1处理请求");
        return new Response("ConcreteHandler1处理结果");
    }
}

public class ConcreteHandler2 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        return Level.LEVEL2;
    }

    @Override
    protected Response echo(Request request) {
        System.out.println("ConcreteHandler2处理请求");
        return new Response("ConcreteHandler2处理结果");
    }
}
  • Request 请求类:包含请求的相关信息和请求级别。
public class Request {
    private Level requestLevel;
    private String requestData;

    public Request(Level requestLevel, String requestData) {
        this.requestLevel = requestLevel;
        this.requestData = requestData;
    }

    public Level getRequestLevel() {
        return requestLevel;
    }

    public String getRequestData() {
        return requestData;
    }
}
  • Response 响应类:用于返回处理结果。
public class Response {
    private String result;

    public Response(String result) {
        this.result = result;
    }

    public String getResult() {
        return result;
    }
}
  • Level 处理级别枚举:定义不同的处理级别。
public enum Level {
    LEVEL1,
    LEVEL2
}

10.5 注意事项 #

  • 在构建责任链时,要注意处理者的顺序和处理级别设置,确保请求能够按照预期的顺序被正确处理,避免出现请求无法被处理或者处理顺序混乱的情况。
  • 责任链过长可能会影响系统性能,因为每个请求都需要依次遍历处理者链,直到找到合适的处理者。可以考虑设置链的最大长度或者采用其他优化策略,如缓存处理结果等。
    • 链中节点数量需要控制,通免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否己经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。

抽象的处理者实现三个职责:

一是定义一个请求的处理方法handleMessage,唯一对外开放的方法:

二是定义一个链的编排方法setNext,设置下一个处理者:

三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo

10.6 优缺点 #

  • 优点:
    • 降低了请求发送者和处理者之间的耦合度,发送者不需要知道请求的具体处理者,只需要将请求发送到责任链的起始端即可,符合迪米特法则。
    • 可以动态地增加或修改处理者,灵活地调整处理流程,增强了系统的灵活性和可扩展性。
    • 每个处理者只关注自己职责范围内的请求处理,符合单一职责原则,提高了代码的可维护性。
  • 缺点:
    • 责任链过长可能导致性能下降,尤其是在处理复杂请求时,遍历链的开销可能会较大。
    • 调试和排查问题可能会比较困难,因为请求在链中的传递过程可能比较复杂,不易追踪和定位问题所在。

10.7 使用场景 #

  • 日志记录级别处理:在日志系统中,可以根据日志的级别(如 DEBUG、INFO、WARN、ERROR 等)设置不同的处理者。例如,DEBUG 级别的日志可以由开发环境中的详细日志处理器处理,INFO 级别的日志可以由普通日志处理器处理,WARN 和 ERROR 级别的日志可以由更高级别的错误日志处理器处理,并且可以将日志记录到不同的目标(如文件、控制台、数据库等)。
public class LogLevel {
    public static final LogLevel DEBUG = new LogLevel("DEBUG");
    public static final LogLevel INFO = new LogLevel("INFO");
    public static final LogLevel WARN = new LogLevel("WARN");
    public static final LogLevel ERROR = new LogLevel("ERROR");

    private String level;

    private LogLevel(String level) {
        this.level = level;
    }

    public String getLevel() {
        return level;
    }
}

public class LogRecord {
    private LogLevel level;
    private String message;

    public LogRecord(LogLevel level, String message) {
        this.level = level;
        this.message = message;
    }

    public LogLevel getLevel() {
        return level;
    }

    public String getMessage() {
        return message;
    }
}

public abstract class LogHandler {
    private LogHandler nextHandler;

    public final void handleLog(LogRecord record) {
        if (this.getLogLevel().equals(record.getLevel()) {
            this.log(record);
        } else {
            if (this.nextHandler!= null) {
                this.nextHandler.handleLog(record);
            } else {
                System.out.println("未找到合适的日志处理器,日志级别:" + record.getLevel();
            }
        }
    }

    public void setNext(LogHandler handler) {
        this.nextHandler = handler;
    }

    protected abstract LogLevel getLogLevel();
    protected abstract void log(LogRecord record);
}

public class DebugLogHandler extends LogHandler {
    @Override
    protected LogLevel getLogLevel() {
        return LogLevel.DEBUG;
    }

    @Override
    protected void log(LogRecord record) {
        System.out.println("DEBUG日志:" + record.getMessage();
    }
}

public class InfoLogHandler extends LogHandler {
    @Override
    protected LogLevel getLogLevel() {
        return LogLevel.INFO;
    }

    @Override
    protected void log(LogRecord record) {
        System.out.println("INFO日志:" + record.getMessage();
    }
}

public class WarnLogHandler extends LogHandler {
    @Override
    protected LogLevel getLogLevel() {
        return LogLevel.WARN;
    }

    @Override
    protected void log(LogRecord record) {
        System.out.println("WARN日志:" + record.getMessage();
    }
}

public class ErrorLogHandler extends LogHandler {
    @Override
    protected LogLevel getLogLevel() {
        return LogLevel.ERROR;
    }

    @Override
    protected void log(LogRecord record) {
        System.out.println("ERROR日志:" + record.getMessage();
    }
}
  • 员工请假审批流程:在企业管理系统中,员工请假申请可以按照不同的请假天数和员工级别设置审批流程。例如,普通员工请假 1 天以内由直属上级审批,请假 1 - 3 天由部门经理审批,请假 3 天以上由人力资源部审批;而经理级别的员工请假则可能需要更高层级的领导审批。
public class LeaveRequest {
    private int days;
    private Employee employee;

    public LeaveRequest(int days, Employee employee) {
        this.days = days;
        this.employee = employee;
    }

    public int getDays() {
        return days;
    }

    public Employee getEmployee() {
        return employee;
    }
}

public class Employee {
    private String name;
    private String position;

    public Employee(String name, String position) {
        this.name = name;
        this.position = position;
    }

    public String getName() {
        return name;
    }

    public String getPosition() {
        return position;
    }
}

public abstract class LeaveApprover {
    private LeaveApprover nextApprover;

    public final void processLeaveRequest(LeaveRequest request) {
        if (this.canApprove(request) {
            this.approve(request);
        } else {
            if (this.nextApprover!= null) {
                this.nextApprover.processLeaveRequest(request);
            } else {
                System.out.println("请假申请未被批准,审批流程结束");
            }
        }
    }

    public void setNextApprover(LeaveApprover approver) {
        this.nextApprover = approver;
    }

    protected abstract boolean canApprove(LeaveRequest request);
    protected abstract void approve(LeaveRequest request);
}

public class DirectSupervisorApprover extends LeaveApprover {
    @Override
    protected boolean canApprove(LeaveRequest request) {
        return request.getEmployee().getPosition().equals("普通员工") && request.getDays() <= 1;
    }

    @Override
    protected void approve(LeaveRequest request) {
        System.out.println(request.getEmployee().getName() + "的请假申请已被直属上级批准");
    }
}

public class DepartmentManagerApprover extends LeaveApprover {
    @Override
    protected boolean canApprove(LeaveRequest request) {
        return request.getEmployee().getPosition().equals("普通员工") && request.getDays() > 1 && request.getDays() <= 3;
    }

    @Override
    protected void approve(LeaveRequest request) {
        System.out.println(request.getEmployee().getName() + "的请假申请已被部门经理批准");
    }
}

public class HRApprover extends LeaveApprover {
    @Override
    protected boolean canApprove(LeaveRequest request) {
        return request.getEmployee().getPosition().equals("普通员工") && request.getDays() > 3;
    }

    @Override
    protected void approve(LeaveRequest request) {
        System.out.println(request.getEmployee().getName() + "的请假申请已被人力资源部批准");
    }
}

11. 装饰模式(Decorator Pattern) #

11.1 定义 #

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)

11.2 自己的理解 #

装饰模式就像是给一个基础物品(如手机)添加各种配件(如手机壳、贴膜、耳机等)来增强其功能或改变其外观。在软件中,当需要在不改变现有对象结构的前提下,为对象添加新的功能或行为时,可以使用装饰模式。通过创建装饰类来包装原始对象,装饰类可以在原始对象的基础上添加额外的功能,并且可以根据需要动态地组合多个装饰类,实现不同的功能组合,比使用继承来扩展功能更加灵活。

11.3 类型 #

结构型模式。

11.4 通用代码 #

  • Component 抽象构件:定义对象的基本接口,可以是抽象类或接口,是被装饰对象和装饰类共同的父类或父接口。
public interface Component {
    void operation();
}
  • ConcreteComponent 具体构件:实现抽象构件接口,是被装饰的原始对象。
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("执行具体构件的操作");
    }
}
  • Decorator 装饰角色:一般是抽象类,实现抽象构件接口,内部包含一个指向抽象构件的引用,用于装饰原始对象。
public abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}
  • ConcreteDecorator 具体装饰角色:继承装饰角色,在原始对象的基础上添加额外的功能。
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addedFunctionA();
    }

    private void addedFunctionA() {
        System.out.println("添加功能A");
    }
}

public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        addedFunctionB();
    }

    private void addedFunctionB() {
        System.out.println("添加功能B");
    }
}

11.5 注意事项 #

  • 装饰类和被装饰类应该具有相同的接口或父类,这样才能保证装饰后的对象可以替代原始对象使用,否则可能会导致类型不匹配的问题。
  • 在使用多个装饰类时,要注意装饰的顺序,不同的顺序可能会产生不同的效果,需要根据具体需求进行合理的组合。

11.6 优缺点 #

  • 优点:
    • 动态地扩展对象的功能,比继承更加灵活,因为可以在运行时根据需要选择不同的装饰类来组合功能,而继承在编译时就确定了类的层次结构。
    • 遵循开闭原则,在不修改原始类代码的基础上,可以通过添加装饰类来扩展功能,对现有系统的影响较小。
    • 可以使用多个装饰类对一个对象进行多次装饰,实现复杂的功能组合,提高了代码的复用性和灵活性。
  • 缺点:
    • 会增加系统中类的数量,如果过度使用装饰模式,可能会导致系统变得复杂,增加代码的理解和维护难度。
    • 装饰模式的动态特性可能会使代码的调试和理解变得相对困难,因为装饰类的嵌套可能会隐藏一些对象的真实行为。

11.7 使用场景 #

  • 图形界面组件增强:在 GUI 开发中,对于基本的按钮、文本框等组件,可以使用装饰模式添加额外的功能,如添加边框样式、背景颜色、鼠标悬停效果等。
import javax.swing.JButton;

public class StyledButtonDecorator extends Decorator {
    public StyledButtonDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        JButton button = (JButton) component;
        button.setBorder(BorderFactory.createLineBorder(Color.RED);
        button.setBackground(Color.YELLOW);
    }
}

public class HoverEffectButtonDecorator extends Decorator {
    public HoverEffectButtonDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        JButton button = (JButton) component;
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                button.setBackground(Color.GREEN);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                button.setBackground(null);
            }
        });
    }
}
  • 数据加密与压缩:在数据处理中,对于原始数据,可以使用装饰模式先进行加密,再进行压缩,或者反之,根据具体需求灵活组合加密和压缩功能。
import java.io.ByteArrayOutputStream;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class EncryptionDecorator extends Decorator {
    public EncryptionDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        byte[] data = ((DataComponent) component).getData();
        // 简单的异或加密示例
        for (int i = 0; i < data.length; i++) {
            data[i] ^= 0x55;
        }
        ((DataComponent) component).setData(data);
    }
}

public class CompressionDecorator extends Decorator {
    public CompressionDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        byte[] data = ((DataComponent) component).getData();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Deflater deflater = new Deflater();
        deflater.setInput(data);
        deflater.finish();
        byte[] buffer = new byte[1024];
        while (!deflater.finished() {
            int count = deflater.deflate(buffer);
            outputStream.write(buffer, 0, count);
        }
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] compressedData = outputStream.toByteArray();
        ((DataComponent) component).setData(compressedData);
    }

    public byte[] decompress(byte[] compressedData) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Inflater inflater = new Inflater();
        inflater.setInput(compressedData);
        byte[] buffer = new byte[1024];
        try {
            while (!inflater.finished() {
                int count = inflater.inflate(buffer);
                outputStream.write(buffer, 0, count);
            }
            outputStream.close();
        } catch (IOException | DataFormatException e) {
            e.printStackTrace();
        }
        return outputStream.toByteArray();
    }
}

public class DataComponent implements Component {
    private byte[] data;

    public DataComponent(byte[] data) {
        this.data = data;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    @Override
    public void operation() {
        // 原始数据处理逻辑(可以为空)
    }
}
  • 需要扩展一个类的功能,或给一个类增加附加功能。
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  • 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式

12. 策略模式(Strategy Pattern) #

12.1 定义 #

Define a family of algorithms,.encapsulate each one.and make them interchangeable.(定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。)

12.2 自己的理解 #

策略模式就像是一个工具箱,里面有各种不同用途的工具(算法),当需要完成某项任务时,可以根据具体情况选择合适的工具来使用。在软件中,当一个类有多种不同的行为或算法,并且这些行为在运行时可以相互替换时,可以使用策略模式。将每个算法封装成一个独立的策略类,使得算法的变化不会影响到使用算法的类,提高了系统的灵活性和可维护性。

12.3 类型 #

行为型模式。

12.4 通用代码 #

  • Context 封装角色:也称为上下文角色,它持有一个策略对象的引用,并负责调用策略对象的算法方法,屏蔽高层模块对策略算法的直接访问。
public class Context {
    private Strategy strategy;

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

    public void executeStrategy() {
        strategy.algorithm();
    }
}
  • Strategy 抽象策略角色:定义算法家族的抽象接口,所有具体策略类都必须实现这个接口。
public interface Strategy {
    void algorithm();
}
  • ConcreteStrategy 具体策略角色(多个):实现抽象策略接口,包含具体的算法实现。
public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithm() {
        System.out.println("执行策略A的算法");
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithm() {
        System.out.println("执行策略B的算法");
    }
}

12.5 注意事项 #

  • 策略类的数量不宜过多,如果策略类过多,可能会导致系统变得复杂,增加代码的理解和维护难度。可以考虑使用策略枚举等方式来简化策略类的定义。(具体策略数量超过4个,则需要考虑使用混合模式)
  • 策略类之间应该具有良好的独立性,避免策略类之间相互依赖或耦合度过高,否则可能会影响策略模式的灵活性和可扩展性。

12.6 优缺点 #

  • 优点:
    • 实现了算法的独立封装和可替换性,使得算法的变化不会影响到使用算法的类,符合开闭原则,提高了系统的灵活性和可维护性。
    • 可以方便地切换不同的算法策略,根据不同的需求选择合适的算法,提高了系统的适应性。
    • 策略模式将算法的实现细节与使用算法的类分离,使得代码结构更加清晰,易于理解和调试。
  • 缺点:
    • 客户端需要知道所有的策略类,并根据具体情况选择合适的策略类,增加了客户端的复杂性。
    • 策略类的增加可能会导致类的数量增多,尤其是在复杂的系统中,过多的类会增加代码的管理成本。

12.7 使用场景 #

  • 排序算法切换:在数据处理中,对于数组或列表的排序,可以使用策略模式实现不同排序算法(如冒泡排序、快速排序、归并排序等)的切换。
import java.util.ArrayList;
import java.util.List;

public class SortContext {
    private SortStrategy strategy;

    public SortContext(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(List<Integer> data) {
        strategy.sort(data);
    }
}

public interface SortStrategy {
    void sort(List<Integer> data);
}

public class BubbleSortStrategy implements SortStrategy {
    @Override
    public void sort(List<Integer> data) {
        for (int i = 0; i < data.size() - 1; i++) {
            for (int j = 0; j < data.size() - i - 1; j++) {
                if (data.get(j) > data.get(j + 1) {
                    int temp = data.get(j);
                    data.set(j, data.get(j + 1);
                    data.set(j + 1, temp);
                }
            }
        }
    }
}

public class QuickSortStrategy implements SortStrategy {
    @Override
    public void sort(List<Integer> data) {
        quickSort(data, 0, data.size() - 1);
    }

    private void quickSort(List<Integer> data, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(data, low, high);
            quickSort(data, low, pivotIndex - 1);
            quickSort(data, pivotIndex + 1, high);
        }
    }

    private int partition(List<Integer> data, int low, int high) {
        int pivot = data.get(high);
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (data.get(j) <= pivot) {
                i++;
                int temp = data.get(i);
                data.set(i, data.get(j);
                data.set(j, temp);
            }
        }
        int temp = data.get(i + 1);
        data.set(i + 1, data.get(high);
        data.set(high, temp);
        return i + 1;
    }
}
  • 游戏角色移动策略:在游戏开发中,不同类型的游戏角色(如步兵、骑兵、飞行单位等)可能具有不同的移动策略,可以使用策略模式来实现不同角色的移动逻辑。
public class CharacterMovementContext {
    private MovementStrategy strategy;

    public CharacterMovementContext(MovementStrategy strategy) {
        this.strategy = strategy;
    }

    public void move() {
        strategy.move();
    }
}

public interface MovementStrategy {
    void move();
}

public class InfantryMovementStrategy implements MovementStrategy {
    @Override
    public void move() {
        System.out.println("步兵缓慢移动");
    }
}

public class CavalryMovementStrategy implements MovementStrategy {
    @Override
    public void move() {
        System.out.println("骑兵快速移动");
    }
}

public class FlyingUnitMovementStrategy implements MovementStrategy {
    @Override
    public void move() {
        System.out.println("飞行单位自由飞行移动");
    }
}
  • 多个类只有在算法或行为上稍有不同的场景
  • 算法需要自由切换的场景
  • 需要屏蔽算法规则的场景

**注意事项:**具体策略数量超过4个,则需要考虑使用混合模式

策略模式扩展:策略枚举

public enum Calculator{
    //1加法运算
    ADD("+"){
        public int exec(int a,int b){
          return a+b;
        }
    },
	//减法运算
    SUB("-"){
        public int exec(int a,int b){
            return a b;
        }
    };
    String value = "";
    //定义成员值类型
    private Calculator(String _value){
        this.value = _value;
    }
    //获得枚举成员的值
    public String getValue(){
       return this.value; 
    }
    //声明一个抽象函数
    public abstract int exec(int a,int b);
}

定义:

  • 它是一个枚举
  • 它是一个浓缩了的策略模式的枚举

注意:

受枚举类型的限制,每个枚举项都是public、final、static的,扩展性受到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。

致命缺陷:

所有的策略都需要暴露出去,由客户端决定使用哪一个策略。

13. 适配器模式(Adapter Pattern) #

13.1 定义 #

****Convert the interface of a class into another interface clients expectAdapter lets classes work together that couldn’t otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)

13.2 自己的理解 #

适配器模式就像是一个万能插头转换器,当你在国外旅行时,当地的电源插座接口(目标接口)与你带来的电器插头(源接口)不匹配,插头转换器(适配器)可以将电器插头的接口转换为当地插座的接口,使电器能够正常使用。在软件中,当需要使用一个已经存在但接口不兼容的类时,可以创建一个适配器类来将其接口转换为目标接口,使得原本不兼容的类能够在新的环境中正常工作。

13.3 类型 #

结构型模式。

13.4 通用代码 #

  • 类适配器:
    • Target 目标角色:定义客户端所期望的接口。
public interface Target {
    void request();
}
  • Adaptee 源角色:需要适配的现有类,它具有与目标接口不兼容的接口。
public class Adaptee {
    public void specificRequest() {
        System.out.println("执行源类的特定请求");
    }
}
  • Adapter 适配器角色:通过继承源类并实现目标接口,将源类的接口转换为目标接口。
public class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}
  • 对象适配器:与类适配器的区别在于,对象适配器使用对象的合成关系(关联关系)来实现适配,而不是继承源类。
public class ObjectAdapter implements Target {
    private Adaptee adaptee;

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

    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

13.5 注意事项 #

  • 在使用适配器模式时,要确保适配器类能够正确地转换源类的接口,使其符合目标接口的规范,避免出现转换错误或功能异常。
  • 适配器模式主要用于解决现有类的接口不兼容问题,在设计新系统时,应尽量避免出现接口不匹配的情况,以减少对适配器模式的依赖。

13.6 优缺点 #

  • 优点:
    • 可以使原本不兼容的类能够协同工作,提高了代码的复用性,避免了重复开发。
    • 遵循开闭原则,在不修改现有类的基础上,通过适配器类实现了对现有类的适配,对现有系统的影响较小。
  • 缺点:
    • 过多地使用适配器模式可能会导致系统变得复杂,尤其是当存在多个适配器类时,代码的可读性和维护性可能会受到影响。
    • 适配器类的存在可能会隐藏系统的一些复杂性,使得代码的理解和调试难度增加,尤其是在处理复杂的接口转换时。

13.7 使用场景 #

  • 数据库驱动适配:在不同的数据库系统(如 MySQL、Oracle、SQL Server 等)中,它们的驱动接口可能存在差异。当开发一个应用程序需要切换数据库时,可以使用适配器模式创建一个统一的数据库访问接口,然后针对不同的数据库驱动编写适配器类,使得应用程序可以无缝切换数据库。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public interface DatabaseDriverAdapter {
    Connection getConnection() throws SQLException;
}

public class MySQLDriverAdapter implements DatabaseDriverAdapter {
    private String url;
    private String username;
    private String password;

    public MySQLDriverAdapter(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
}

public class OracleDriverAdapter implements DatabaseDriverAdapter {
    private String url;
    private String username;
    private String password;

    public OracleDriverAdapter(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }
}
  • 第三方库接口适配
// 假设第三方库提供的接口
public class ThirdPartyLibrary {
    public void thirdPartyMethod() {
        System.out.println("第三方库的原始方法");
    }
}

// 目标接口
public interface TargetInterface {
    void targetMethod();
}

// 适配器类
public class ThirdPartyAdapter implements TargetInterface {
    private ThirdPartyLibrary thirdPartyLibrary;

    public ThirdPartyAdapter(ThirdPartyLibrary thirdPartyLibrary) {
        this.thirdPartyLibrary = thirdPartyLibrary;
    }

    @Override
    public void targetMethod() {
        thirdPartyLibrary.thirdPartyMethod();
    }
}
  • 你有动机修改一个己经投产中的接口时,适配器模式可能是最适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式,这也是我们例子中提到的。

注意事项:

详细设计阶段不要考虑使用适配器模式,使用主要场景为扩展应用中。

对象适配器:

对象适配器和类适配器的区别:

类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系,这是两者的根本区别。(实际项目中对象适配器使用到的场景相对比较多)。

14. 迭代器模式(Iterator Pattern) #

14.1 定义 #

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。)

14.2 自己的理解 #

迭代器模式就像一个导游,带领游客依次参观旅游景点,而游客不需要了解景点的内部布局和管理方式。在软件中,当需要遍历一个集合或容器对象,但又不想暴露其内部结构时,可以使用迭代器模式,将遍历的逻辑封装在迭代器中,使客户端可以方便地访问元素,而不影响集合对象的内部结构。

14.3 类型 #

行为型模式。

14.4 通用代码 #

  • Aggregate 聚合类:定义创建迭代器的接口。
public interface Aggregate {
    Iterator createIterator();
}
  • ConcreteAggregate 具体聚合类:实现聚合类接口,返回具体的迭代器。
import java.util.ArrayList;
import java.util.List;

public class ConcreteAggregate implements Aggregate {
    private List<Object> items = new ArrayList<>();

    public void add(Object item) {
        items.add(item);
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }

    public Object getItem(int index) {
        return items.get(index);
    }

    public int getCount() {
        return items.size();
    }
}
  • Iterator 迭代器:定义访问和遍历元素的接口。
public interface Iterator {
    boolean hasNext();
    Object next();
}
  • ConcreteIterator 具体迭代器:实现迭代器接口,完成元素的遍历。
public class ConcreteIterator implements Iterator {
    private ConcreteAggregate aggregate;
    private int index;

    public ConcreteIterator(ConcreteAggregate aggregate) {
        this.aggregate = aggregate;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < aggregate.getCount();
    }

    @Override
    public Object next() {
        if (hasNext() {
            return aggregate.getItem(index++);
        }
        return null;
    }
}

14.5 注意事项 #

  • 迭代器模式要注意迭代器的边界条件,避免越界访问,确保迭代器在遍历过程中不会出现异常。
  • 迭代器的实现要考虑集合对象的修改操作,例如在迭代过程中添加或删除元素可能会导致迭代结果不一致,需要采取相应的处理措施。
  • 迭代器模式已经被淘汰,java中已经把选代器运用到各个聚集类(collection)中了,使用java自带的选代器就已经满足我们的需求了.

14.6 优缺点 #

  • 优点:
    • 分离了集合对象的遍历行为和集合对象本身,符合单一职责原则,提高了集合对象的内聚性。
    • 迭代器提供了统一的遍历接口,使得客户端可以以一种标准的方式遍历不同类型的集合,提高了代码的复用性和可维护性。
    • 可以为不同的集合对象提供不同的迭代器,满足不同的遍历需求,提高了系统的灵活性。
  • 缺点:
    • 对于简单的集合对象,使用迭代器模式可能会增加系统的复杂性,增加了代码量。
    • 迭代器模式可能会影响系统性能,尤其是在处理大量元素时,迭代器的创建和遍历操作可能会有一定的开销。

14.7 使用场景 #

  • 自定义集合类的遍历:当开发一个自定义的集合类时,可以使用迭代器模式提供标准的遍历方式。
public class CustomList implements Aggregate {
    private Object[] elements;
    private int size;
    private int capacity;

    public CustomList() {
        capacity = 10;
        elements = new Object[capacity];
        size = 0;
    }

    public void add(Object element) {
        if (size == capacity) {
            Object[] newElements = new Object[capacity * 2];
            System.arraycopy(elements, 0, newElements, 0, size);
            elements = newElements;
            capacity *= 2;
        }
        elements[size++] = element;
    }

    @Override
    public Iterator createIterator() {
        return new CustomListIterator(this);
    }

    public Object get(int index) {
        if (index < size) {
            return elements[index];
        }
        return null;
    }

    public int size() {
        return size;
    }
}

public class CustomListIterator implements Iterator {
    private CustomList list;
    private int index;

    public CustomListIterator(CustomList list) {
        this.list = list;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < list.size();
    }

    @Override
    public Object next() {
        if (hasNext() {
            return list.get(index++);
        }
        return null;
    }
}
  • 文件系统遍历:在文件系统中,遍历文件夹中的文件和子文件夹,可以使用迭代器模式将遍历逻辑封装起来,使客户端可以方便地遍历文件系统,而不影响文件系统的内部结构。
import java.io.File;

public class FileSystem implements Aggregate {
    private File root;

    public FileSystem(File root) {
        this.root = root;
    }

    @Override
    public Iterator createIterator() {
        return new FileSystemIterator(root);
    }
}

public class FileSystemIterator implements Iterator {
    private File[] files;
    private int index;

    public FileSystemIterator(File root) {
        files = root.listFiles();
        index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < files.length;
    }

    @Override
    public Object next() {
        if (hasNext() {
            return files[index++];
        }
        return null;
    }
}

15. 组合模式(Composite Pattern) #

15.1 定义 #

Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.(将对象组合成树形结构以表示“部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。)

15.2 自己的理解 #

组合模式类似于文件系统,文件系统中既有文件(叶子节点)又有文件夹(树枝节点),文件夹可以包含文件和其他文件夹。用户可以像操作文件一样操作文件夹,例如复制、删除等操作,而不必区分是文件还是文件夹。在软件中,当需要表示具有层次结构的对象,并希望对单个对象和组合对象进行统一操作时,可以使用组合模式,它将对象组合成树形结构,让用户可以以统一的方式处理这些对象,简化了代码逻辑。

15.3 类型 #

结构型模式。

15.4 通用代码 #

  • Component 抽象构件:为组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。
public abstract class Component {
    protected String name;

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

    public abstract void add(Component component);
    public abstract void remove(Component component);
    public abstract void display(int depth);
}
  • Leaf 叶子构件:表示叶子节点,没有子节点,实现抽象构件的接口。
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {
        System.out.println("叶子节点不能添加子节点");
    }

    @Override
    public void remove(Component component) {
        System.out.println("叶子节点不能移除子节点");
    }

    @Override
    public void display(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("-");
        }
        System.out.println(sb.toString() + name);
    }
}
  • Composite 组合构件:表示树枝节点,可以包含子节点,实现抽象构件的接口,并维护子节点的集合。
import java.util.ArrayList;
import java.util.List;

public class Composite extends Component {
    private List<Component> children = new ArrayList<>();

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

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

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

    @Override
    public void display(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("-");
        }
        System.out.println(sb.toString() + name);
        for (Component child : children) {
            child.display(depth + 2);
        }
    }
}

15.5 注意事项 #

  • 要注意组件的层次结构和操作的一致性,确保叶子节点和组合节点对客户端操作的处理符合预期,避免出现异常情况。
  • 组合模式中,如果层次结构过深或节点过多,可能会影响系统性能,需要考虑性能优化,如采用懒加载等方式。

15.6 优缺点 #

  • 优点:
    • 定义了包含基本对象和组合对象的类层次结构,使客户端可以统一处理基本对象和组合对象,简化了客户端代码。
    • 可以方便地增加新的组件,符合开闭原则,易于扩展。
    • 可以方便地对整个树形结构进行递归操作,如遍历、计算等。
  • 缺点:
    • 对于复杂的树形结构,可能会导致系统的复杂性增加,尤其是在维护层次结构和处理递归操作时。
    • 由于使用了递归操作,可能会影响系统性能,尤其是在处理大量节点时。

15.7 使用场景 #

  • 图形绘制系统:在图形绘制软件中,图形可以是基本图形(如圆形、矩形等),也可以是组合图形(如多个图形组合成的复杂图形)。可以使用组合模式对图形对象进行组合,实现统一的绘制、移动、缩放等操作。
public class Circle extends Leaf {
    public Circle(String name) {
        super(name);
    }
}

public class Rectangle extends Leaf {
    public Rectangle(String name) {
        super(name);
    }
}

public class ComplexShape extends Composite {
    public ComplexShape(String name) {
        super(name);
    }
}
  • 组织结构:在企业管理系统中,可以使用组合模式表示公司的组织结构,公司由部门组成,部门又可以包含子部门或员工,对公司进行统一的操作(如统计人数、计算成本等)。
public class Employee extends Leaf {
    public Employee(String name) {
        super(name);
    }
}

public class Department extends Composite {
    public Department(String name) {
        super(name);
    }
}
  • 维护和展示部分整体关系的场景,如树形菜单、文件和文件夹管理。
  • 从一个整体中能够独立出部分模块或功能的场景。

注意:

只要是树形结构,就考虑使用组合模式。

16. 观察者模式(Observer Pattern) #

16.1 定义 #

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)

16.2 自己的理解 #

观察者模式就像一个订阅系统,当一个频道(主题)发布新消息时,所有订阅该频道的用户(观察者)都会收到通知。在软件中,当一个对象的状态变化需要通知其他多个对象时,可以使用观察者模式,将对象间的依赖关系解耦,提高系统的灵活性和可维护性。

16.3 类型 #

行为型模式。

16.4 通用代码 #

  • Subject 主题:管理观察者,提供注册、移除和通知观察者的方法。
    • 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
import java.util.ArrayList;
import java.util.List;

public class Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        notifyAllObservers();
    }

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

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

    private void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
  • Observer 观察者:定义一个更新接口,当主题状态改变时,主题会调用此接口更新观察者。
    • 观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理
public interface Observer {
    void update();
}
  • ConcreteObserver 具体观察者:实现更新接口,当收到主题通知时,执行相应的操作。
    • 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑
public class ConcreteObserverA implements Observer {
    private Subject subject;

    public ConcreteObserverA(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("ConcreteObserverA收到更新通知,新状态:" + subject.getState();
    }
}

public class ConcreteObserverB implements Observer {
    private Subject subject;

    public ConcreteObserverB(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("ConcreteObserverB收到更新通知,新状态:" + subject.getState();
    }
}

被观察者通用代码:

public abstract class Subject{
    //定义一个观察者数组
	private Vector<Observer> obsVector = new Vector<Observer>();
	//增加一个观察者
    public void addobserver(Observer o){
        this.obsVector.add(o);
    }
	//删除一个观察者
    public void delObserver(Observer o){
        this.obsVector.remove(o);
    }
    //通知所有观察者
    public void notifyobserversO{
        for(Observer o:this.obsVector){
        	o.update():
        }
    }
}

16.5 注意事项 #

  • 要注意观察者的注册和移除,避免出现内存泄漏,例如忘记移除不再需要的观察者。
  • 在多线程环境下,需要考虑线程安全,尤其是在注册、移除和通知观察者的操作中,避免出现并发问题。

16.6 优缺点 #

  • 优点:
    • 实现了对象之间的解耦,主题对象和观察者对象之间的依赖关系松散,符合迪米特法则,提高了系统的灵活性和可维护性。
    • 可以方便地添加和移除观察者,符合开闭原则,对于系统的扩展非常方便。
    • 可以实现广播式的通知,一个主题的变化可以通知多个观察者,提高了系统的协同性。
  • 缺点:
    • 如果观察者过多,通知的性能可能会受到影响,尤其是在通知逻辑复杂时。
    • 当观察者和主题之间的依赖关系复杂时,可能会导致系统的调试和维护困难,因为通知链可能比较长。

16.7 使用场景 #

  • 股票价格更新通知:在股票交易系统中,当股票价格发生变化时,需要通知所有关注该股票的用户(观察者)。
public class Stock {
    private String name;
    private double price;
    private List<StockObserver> observers = new ArrayList<>();

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

    public void setPrice(double price) {
        this.price = price;
        notifyObservers();
    }

    public double getPrice() {
        return price;
    }

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

    public void detach(StockObserver observer) {
        observers.remove(observer);
    }

    private void notifyObservers() {
        for (StockObserver observer : observers) {
            observer.update(this);
        }
    }
}

public interface StockObserver {
    void update(Stock stock);
}

public class UserObserver implements StockObserver {
    @Override
    public void update(Stock stock) {
        System.out.println("用户收到股票 " + stock.getName() + " 的价格更新通知,新价格:" + stock.getPrice();
    }
}
  • 消息推送服务:在消息推送系统中,当有新消息时,需要将消息推送给所有订阅该消息类型的用户。
public class Message {
    private String content;
    private List<MessageObserver> observers = new ArrayList<>();

    public Message(String content) {
        this.content = content;
    }

    public void setContent(String content) {
        this.content = content;
        notifyObservers();
    }

    public String getContent() {
        return content;
    }

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

    public void detach(MessageObserver observer) {
        observers.remove(observer);
    }

    private void notifyObservers() {
        for (MessageObserver observer : observers) {
            observer.update(this);
        }
    }
}

public interface MessageObserver {
    void update(Message message);
}

public class UserMessageObserver implements MessageObserver {
    @Override
    public void update(Message message) {
        System.out.println("用户收到消息更新通知,消息内容:" + message.getContent();
    }
}
  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系
  • 事件多级触发场景,
  • 跨系统的消息交换场景,如消息队列的处理机制

注意:

  • 广播链的问题

在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次)

  • 异步处理问题

观察者比较多,而且处理时间比较长,采用异步处理来考虑线程安全和队列的问题

17. 门面模式(Facade Pattern) #

17.1 定义 #

**定义:**Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.(要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。)

17.2 自己的理解 #

可以将门面模式理解为一个统一的服务窗口,这个窗口隐藏了背后多个服务部门(子系统)的复杂流程和操作。就像在银行办理业务,用户只需要在一个综合服务窗口(门面)提交需求,该窗口会自动协调多个内部部门(如储蓄部门、贷款部门、理财部门等)来完成各种业务操作,而用户无需直接与这些内部部门打交道。在软件系统中,它可以将多个子系统的操作整合起来,以一种简洁的方式呈现给客户端,降低客户端与子系统的耦合度,同时提高系统的易用性和可维护性。

17.3 类型 #

结构型模式。

17.4 通用代码 #

  • 子系统类:代表复杂子系统中的不同部分,每个类具有各自独立的操作。
class SubSystemOne {
    public void operationOne() {
        System.out.println("子系统一执行操作一");
    }
}

class SubSystemTwo {
    public void operationTwo() {
        System.out.println("子系统二执行操作二");
    }
}

class SubSystemThree {
    public void operationThree() {
        System.out.println("子系统三执行操作三");
    }
}
  • 门面类:将子系统的多个操作进行封装,为客户端提供一个统一的操作接口。
class Facade {
    private SubSystemOne subSystemOne = new SubSystemOne();
    private SubSystemTwo subSystemTwo = new SubSystemTwo();
    private SubSystemThree subSystemThree = new SubSystemThree();

    public void performComplexOperation() {
        subSystemOne.operationOne();
        subSystemTwo.operationTwo();
        subSystemThree.operationThree();
    }
}
  • 客户端代码示例:展示如何使用门面类来操作子系统。
public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.performComplexOperation();
    }
}

17.5 注意事项 #

  • 避免臃肿:门面类的设计应该简洁明了,主要负责子系统操作的组合和调用,不应包含过多的业务逻辑细节,否则会违背单一职责原则,导致代码的可维护性降低。
  • 子系统的独立性保留:虽然使用了门面类,但子系统应该仍然可以被单独使用,因为在某些特殊情况下,可能需要调用子系统的特定功能,而不是通过门面类的统一接口。
  • 异常处理:在门面类的方法中要考虑对可能出现的异常进行处理,确保子系统操作出现异常时,能给客户端合理的反馈,避免程序崩溃或出现难以排查的错误。

17.6 优缺点 #

  • 优点
    • 简化客户端使用:将复杂的子系统操作封装在一个接口后面,为客户端提供了简洁的使用方式,减少了客户端与子系统之间的耦合,使得客户端代码更简洁、易于理解和维护。
    • 提高系统可维护性:通过将子系统的复杂性隐藏在门面类后面,当子系统发生变化时,只需要修改门面类中相关的操作,而不会影响到客户端代码,提高了系统的可维护性。
    • 提高系统安全性:通过限制客户端对子系统的直接访问,只暴露门面类,可以更好地保护子系统内部的敏感信息和操作,防止客户端对系统的不当操作。
  • 缺点
    • 单点故障风险:如果门面类出现问题,可能会影响到整个系统的正常运行,因为客户端依赖门面类来完成操作,需要确保门面类的高可用性。
    • 功能扩展性限制:过度依赖门面类可能会限制子系统的功能扩展,因为客户端通常只使用门面类提供的操作,可能会忽略子系统本身的其他功能,对于一些高级用户或特殊需求,可能会造成不便。

17.7 使用场景 #

  • 集成多个子系统的操作:当一个系统由多个子系统构成,并且客户端需要同时操作多个子系统的功能时,使用门面模式可以将这些操作集成到一个接口中,方便客户端使用。例如,在一个电商系统中,涉及商品管理子系统、订单管理子系统、用户管理子系统,可使用门面类提供诸如 “用户下单” 这样的操作,它会涉及到用户身份验证、商品库存检查、订单创建等多个子系统操作。
class ProductManagementSystem {
    public void checkStock() {
        System.out.println("检查商品库存");
    }
}

class OrderManagementSystem {
    public void createOrder() {
        System.out.println("创建订单");
    }
}

class UserManagementSystem {
    public void authenticateUser() {
        System.out.println("验证用户身份");
    }
}

class EcommerceFacade {
    private ProductManagementSystem productSystem = new ProductManagementSystem();
    private OrderManagementSystem orderSystem = new OrderManagementSystem();
    private UserManagementSystem userSystem = new UserManagementSystem();

    public void userPlaceOrder() {
        userSystem.authenticateUser();
        productSystem.checkStock();
        orderSystem.createOrder();
    }
}
  • 简化第三方库或遗留系统的使用:当使用复杂的第三方库或遗留系统时,这些系统可能具有复杂的接口和操作,可以使用门面模式将其封装,为开发人员提供更简洁的使用接口。
class LegacySystemA {
    public void legacyOperationA() {
        System.out.println("遗留系统A的操作A");
    }
}

class LegacySystemB {
    public void legacyOperationB() {
        System.out.println("遗留系统B的操作B");
    }
}

class LegacySystemFacade {
    private LegacySystemA legacySystemA = new LegacySystemA();
    private LegacySystemB legacySystemB = new LegacySystemB();

    public void performLegacyTask() {
        legacySystemA.legacyOperationA();
        legacySystemB.legacyOperationB();
    }
}
  • 分层架构中的服务层:在分层架构中,门面模式可以作为服务层的实现方式,为表现层提供简单的服务接口,将业务逻辑层和数据访问层的操作封装起来。
class BusinessLogicLayer {
    public void processBusinessLogic() {
        System.out.println("处理业务逻辑");
    }
}

class DataAccessLayer {
    public void accessData() {
        System.out.println("访问数据");
    }
}

class ServiceFacade {
    private BusinessLogicLayer businessLogicLayer = new BusinessLogicLayer();
    private DataAccessLayer dataAccessLayer = new DataAccessLayer();

    public void performService() {
        businessLogicLayer.processBusinessLogic();
        dataAccessLayer.accessData();
    }
}
  • 为一个复杂的模块或子系统提供一个供外界访问的接口
  • 子系统相对独立—-外界对子系统的访问只要黑箱操作即可
  • 预防低水平人员带来的风险扩散

注意:

  • 一个子系统可以有多个门面
  • 门面不参与子系统内的业务逻辑

18. 备忘录模式(Memento Pattern) #

18.1 定义 #

Without violating encapsulation.capture and externalize an object’s internal state so that the object can be restored to this state later.(在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。)

18.2 自己的理解 #

备忘录模式就像一个游戏的存档功能,玩家可以在游戏的某个时刻保存游戏状态(备忘录),当需要时可以将游戏恢复到保存的状态。在软件中,当需要保存对象的某个状态,并且在之后能够将对象恢复到该状态时,可以使用备忘录模式,它可以在不破坏对象封装性的前提下实现状态的保存和恢复。

18.3 类型 #

行为型模式。

18.4 通用代码 #

  • Originator 发起人:负责创建一个备忘录,用于记录当前状态,并可以使用备忘录恢复状态。
public class Originator {
    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public Memento createMemento() {
        return new Memento(state);
    }

    public void restoreFromMemento(Memento memento) {
        state = memento.getState();
    }
}
  • Memento 备忘录:存储发起人的内部状态。
public class Memento {
    private final String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}
  • Caretaker 管理者:负责保存备忘录,但不能对备忘录的内容进行操作或检查。
public class Caretaker {
    private Memento memento;

    public void saveMemento(Memento memento) {
        this.memento = memento;
    }

    public Memento retrieveMemento() {
        return memento;
    }
}

18.5 注意事项 #

  • 备忘录模式需要注意备忘录对象的存储和管理,确保备忘录的安全性,避免备忘录被外部修改,破坏对象的封装性。
  • 在使用备忘录恢复状态时,要确保发起人的状态能够正确恢复,避免出现状态不一致的情况。

18.6 优缺点 #

  • 优点:
    • 可以在不破坏对象封装性的前提下,实现对象状态的保存和恢复,符合对象的封装原则。
    • 可以方便地实现撤销操作,对于需要撤销功能的系统(如文本编辑器、图形编辑器等)非常有用。
  • 缺点:
    • 备忘录对象的存储可能会占用较多的资源,尤其是当保存大量的状态时。
    • 备忘录模式会增加系统的复杂性,尤其是在多状态的情况下,需要管理多个备忘录对象。

18.7 使用场景 #

  • 文本编辑器的撤销操作:在文本编辑器中,用户输入的文本内容可以看作是一个状态,使用备忘录模式可以保存用户输入的历史状态,实现撤销操作。
public class TextEditor {
    private StringBuilder text;
    private TextEditorMemento memento;

    public TextEditor() {
        this.text = new StringBuilder();
    }

    public void appendText(String newText) {
        text.append(newText);
    }

    public String getText() {
        return text.toString();
    }

    public TextEditorMemento createMemento() {
        return new TextEditorMemento(text.toString();
    }

    public void restoreFromMemento(TextEditorMemento memento) {
        this.text = new StringBuilder(memento.getSavedText();
    }
}

public class TextEditorMemento {
    private final String savedText;

    public TextEditorMemento(String savedText) {
        this.savedText = savedText;
    }

    public String getSavedText() {
        return savedText;
    }
}

public class TextEditorHistory {
    private List<TextEditorMemento> mementos = new ArrayList<>();

    public void save(TextEditorMemento memento) {
        mementos.add(memento);
    }

    public TextEditorMemento undo() {
        if (mementos.size() > 0) {
            return mementos.remove(mementos.size() - 1);
        }
        return null;
    }
}
  • 游戏存档和读档:在游戏开发中,玩家的游戏进度(如角色的位置、等级、道具等)可以使用备忘录模式保存,玩家可以随时读取存档,恢复游戏进度。
public class GameCharacter {
    private int level;
    private int health;
    private int mana;

    public void setLevel(int level) {
        this.level = level;
    }

    public void setHealth(int health) {
        this.health = health;
    }

    public void setMana(int mana) {
        this.mana = mana;
    }

    public int getLevel() {
        return level;
    }

    public int getHealth() {
        return health;
    }

    public int getMana() {
        return mana;
    }

    public GameCharacterMemento createMemento() {
        return new GameCharacterMemento(level, health, mana);
    }

    public void restoreFromMemento(GameCharacterMemento memento) {
        this.level = memento.getLevel();
        this.health = memento.getHealth();
        this.mana = memento.getMana();
    }
}

public class GameCharacterMemento {
    private final int level;
    private final int health;
    private final int mana;

    public GameCharacterMemento(int level, int health, int mana) {
        this.level = level;
        this.health = health;
        this.mana = mana;
    }

    public int getLevel() {
        return level;
    }

    public int getHealth() {
        return health;
    }

    public int getMana() {
        return mana;
    }
}

public class GameSaveManager {
    private List<GameCharacterMemento> mementos = new ArrayList<>();

    public void save(GameCharacterMemento memento) {
        mementos.add(memento);
    }

    public GameCharacterMemento loadLastSave() {
        if (mementos.size() > 0) {
            return mementos.remove(mementos.size() - 1);
        }
        return null;
    }
}
  • 需要保存和恢复数据的相关状态场景
  • 提供一个可回滚(rollback)的操作
  • 需要监控的副本场景中
  • 数据库连接的事务管理就是用的备忘录模式。

注意:

  • 各忘录的生命期
  • 备忘录的性能
    • 不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)。

clone方式备忘录:

  • 发起人角色融合了发起人角色和备忘录角色,具有双重功效

多状态的备忘录模式

  • 增加了一个BeanUtils类,其中backupProp是把发起人的所有属性值转换到HashMap中,方便备忘录角色存储.restoreProp方法则是把HashMap中的值返回到发起人角色中。

BeanUtil工具类代码:

public class BeanUtils{
    //把bean的所有属性及数值放入到Hashmap中
    public static HashMap<String,Object> backupProp(Object bean){
    	HashMap<String,Object> result = new HashMap<String,Object>();
    	try{
            //获得Bean描述
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass();
            //获得属性描述
    		PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            //遍历所有属性
            for(PropertyDescriptor des:descriptors){
                //属性名称
    			String fieldName des.getName();
                //读取属性的方法
    			Method getter = des.getReadMethod();
                //读取属性值
    			Object fieldValue = getter.invoke(bean.new Object[]();
                if(!fieldName.equalsIgnoreCase("class"){
                	result.put(fieldName,fieldValue);   
                }
            }
        }catch(Exception e){
            //处理异常
        }
    	return result:
	}
}

19. 访问者模式(Visitor Pattern) #

19.1 定义 #

Represent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)

19.2 自己的理解 #

访问者模式就像一个访客,它可以访问不同的元素,并对不同元素执行不同的操作,而元素对象不需要改变自身的结构和行为。在软件中,当需要对一个对象结构中的元素添加新的操作,而不修改元素类的结构时,可以使用访问者模式,将操作封装在访问者类中,通过访问者类访问元素,实现操作的添加。

19.3 类型 #

行为型模式。

19.4 通用代码 #

  • Element 元素接口:定义一个接受访问者访问的接口。
    • 接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
public interface Element {
    void accept(Visitor visitor);
}
  • ConcreteElement 具体元素:实现元素接口,接受访问者访问。
    • 实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。
public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void operationA() {
        System.out.println("具体元素A的操作");
    }
}

public class ConcreteElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void operationB() {
        System.out.println("具体元素B的操作");
    }
}
  • Visitor 访问者接口:声明对元素的访问操作。
    • 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的
public interface Visitor {
    void visit(ConcreteElementA element);
    void visit(ConcreteElementB element);
}
  • ConcreteVisitor 具体访问者:实现访问者接口,实现对元素的具体操作。
    • 它影响访问者访问到一个类后该怎么干,要做什么事情。
public class ConcreteVisitor1 implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("具体访问者1访问具体元素A");
        element.operationA();
    }

    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("具体访问者1访问具体元素B");
        element.operationB();
    }
}

public class ConcreteVisitor2 implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("具体访问者2访问具体元素A");
        element.operationA();
    }

    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("具体访问者2访问具体元素B");
        element.operationB();
    }
}
  • ObjectStructure 对象结构:包含元素集合,并提供遍历元素的方法。
    • 元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
import java.util.ArrayList;
import java.util.List;

public class ObjectStructure {
    private List<Element> elements = new ArrayList<>();

    public void add(Element element) {
        elements.add(element);
    }

    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}

19.5 注意事项 #

  • 访问者模式会使元素和访问者之间产生依赖,当元素结构发生变化时,可能需要修改访问者的代码,需要考虑两者之间的耦合度。
  • 访问者模式适用于对象结构相对稳定,而操作经常变化的情况,如果对象结构频繁变化,可能会导致大量的修改工作。

19.6 优缺点 #

  • 优点:
    • 可以在不修改元素类的情况下添加新的操作,符合开闭原则,提高了系统的可扩展性。
    • 将元素的操作分离到访问者类中,使元素的操作更具灵活性,并且可以集中处理不同元素的操作。
  • 缺点:
    • 增加了系统的复杂性,尤其是元素和访问者之间的依赖关系,增加了代码的理解和维护难度。
    • 访问者模式可能会破坏封装性,因为访问者需要访问元素的内部细节。

19.7 使用场景 #

  • 报表生成:在报表系统中,报表由不同的元素(如表格、图表、文本等)组成,不同的访问者(如 HTML 报表生成器、PDF 报表生成器)可以访问这些元素并生成不同格式的报表。
public class Table implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void displayTable() {
        System.out.println("显示表格");
    }
}

public class Chart implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void displayChart() {
        System.out.println("显示图表");
    }
}

public class HTMLVisitor implements Visitor {
    @Override
    public void visit(Table table) {
        System.out.println("生成 HTML 表格");
        table.displayTable();
    }

    @Override
    public void visit(Chart chart) {
        System.out.println("生成 HTML 图表");
        chart.displayChart();
    }
}

public class PDFVisitor implements Visitor {
    @Override
    public void visit(Table table) {
        System.out.println("生成 PDF 表格");
        table.displayTable();
    }

    @Override
    public void visit(Chart chart) {
        System.out.println("生成 PDF 图表");
        chart.displayChart();
    }
}
  • 编译器设计:在编译器中,对抽象语法树的元素(如表达式、语句等)进行不同的操作,如代码生成、类型检查等,可以使用访问者模式。
public class Expression implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void evaluate() {
        System.out.println("计算表达式");
    }
}

public class Statement implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void execute() {
        System.out.println("执行语句");
    }
}

public class CodeGeneratorVisitor implements Visitor {
    @Override
    public void visit(Expression expression) {
        System.out.println("为表达式生成代码");
        expression.evaluate();
    }

    @Override
    public void visit(Statement statement) {
        System.out.println("为语句生成代码");
        statement.execute();
    }
}

public class TypeCheckerVisitor implements Visitor {
    @Override
    public void visit(Expression expression) {
        System.out.println("对表达式进行类型检查");
        expression.evaluate();
    }

    @Override
    public void visit(Statement statement) {
        System.out.println("对语句进行类型检查");
        statement.execute();
    }
}
  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式己经不能胜任的情景。
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。

20. 状态模式(State Pattern)(复杂) #

20.1 定义 #

Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。)

20.2 自己的理解 #

状态模式就像一个自动售货机,它有不同的状态(如准备、出货、找零等),当处于不同状态时,它的行为也会相应改变,例如在准备状态可以接收硬币,在出货状态会输出商品。在软件中,当一个对象的行为取决于其内部状态,并且状态变化频繁时,可以使用状态模式,将状态封装成独立的状态类,使得对象可以根据状态的改变而改变行为,避免了大量的条件判断语句。

20.3 类型 #

行为型模式。

20.4 通用代码 #

  • Context 上下文:维护一个具体状态对象,并将请求委托给状态对象处理。
    • 定义客户端需要的接口,并且负责具体状态的切换。
public class Context {
    private State state;

    public Context(State state) {
        this.state = state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void request() {
        state.handle(this);
    }
}
  • State 状态接口:定义一个接口,用于封装与上下文相关的操作。
    • 接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
public interface State {
    void handle(Context context);
}
  • ConcreteState 具体状态类:实现状态接口,处理与状态相关的操作。
    • 每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
public class ConcreteStateA implements State {
    @Override
    public void handle(Context context) {
        System.out.println("处理状态A的操作");
        context.setState(new ConcreteStateB();
    }
}

public class ConcreteStateB implements State {
    @Override
    public void handle(Context context) {
        System.out.println("处理状态B的操作");
        context.setState(new ConcreteStateA();
    }
}

20.5 注意事项 #

  • 状态模式需要准确地划分状态和状态之间的转换条件,避免状态转换混乱,确保状态的转换逻辑清晰明确。
  • 状态类的设计要考虑状态之间的独立性,避免状态类之间相互依赖或耦合度过高,影响系统的可维护性。

20.6 优缺点 #

  • 优点:
    • 实现了状态和行为的封装,将状态相关的行为封装在状态类中,避免了大量的条件判断语句,提高了代码的可读性和可维护性。
    • 符合开闭原则,当添加新的状态时,只需要添加新的状态类,而不需要修改现有代码,便于系统的扩展。
    • 可以方便地实现状态的切换和状态行为的变化,提高了系统的灵活性。
  • 缺点:
    • 会增加系统中类的数量,尤其是在状态较多时,会增加代码的复杂性和管理成本。
    • 状态模式的设计需要对状态和状态转换有清晰的理解,否则可能会导致状态管理混乱,影响系统的性能和可维护性。

20.7 使用场景 #

  • 订单状态管理:在电商系统中,订单有不同的状态(如待支付、已支付、已发货、已完成等),根据订单的不同状态,可以执行不同的操作。
public class Order {
    private OrderState state;

    public Order() {
        this.state = new OrderStatePending();
    }

    public void setState(OrderState state) {
        this.state = state;
    }

    public void process() {
        state.process(this);
    }
}

public interface OrderState {
    void process(Order order);
}

public class OrderStatePending implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("订单处于待支付状态,等待用户支付");
        order.setState(new OrderStatePaid();
    }
}

public class OrderStatePaid implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("订单已支付,准备发货");
        order.setState(new OrderStateShipped();
    }
}

public class OrderStateShipped implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("订单已发货,等待用户确认收货");
        order.setState(new OrderStateCompleted();
    }
}

public class OrderStateCompleted implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("订单已完成");
    }
}
  • 游戏角色状态控制:在游戏中,游戏角色有不同的状态(如正常、受伤、死亡等),根据角色的状态,其行为会有所不同,如移动速度、攻击能力等。
public class GameCharacter {
    private CharacterState state;

    public GameCharacter() {
        this.state = new CharacterStateNormal();
    }

    public void setState(CharacterState state) {
        this.state = state;
    }

    public void performAction() {
        state.performAction(this);
    }
}

public interface CharacterState {
    void performAction(GameCharacter character);
}

public class CharacterStateNormal implements CharacterState {
    @Override
    public void performAction(GameCharacter character) {
        System.out.println("角色正常,执行正常行动");
        // 受到攻击等情况,可能切换状态
        character.setState(new CharacterStateInjured();
    }
}

public class CharacterStateInjured implements CharacterState {
    @Override
    public void performAction(GameCharacter character) {
        System.out.println("角色受伤,行动受限");
        // 进一步受伤或恢复,可能切换状态
    }
}

public class CharacterStateDead implements CharacterState {
    @Override
    public void performAction(GameCharacter character) {
        System.out.println("角色死亡,无法行动");
    }
}
  • 行为随状态改变而改变的场景
    • 这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
  • 条件、分支判断语句的替代者

注意:

状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。

21. 解释器模式(Interpreter Pattern)(少用) #

21.1 定义 #

Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。)

21.2 自己的理解 #

解释器模式就像一个翻译器,将一种语言的语句翻译成另一种语言或操作。在软件中,当需要实现一个简单的语言或规则系统,并且需要解释执行该语言或规则时,可以使用解释器模式,将语言的解释逻辑封装在解释器中,将语言的元素和解释规则进行抽象和组合,实现对语言的解释执行。

21.3 类型 #

行为型模式。

21.4 通用代码 #

  • AbstractExpression 抽象表达式:声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
public abstract class AbstractExpression {
    public abstract void interpret(Context context);
}
  • TerminalExpression 终结符表达式:实现与文法中的终结符相关联的解释操作。
public class TerminalExpression extends AbstractExpression {
    @Override
    public void interpret(Context context) {
        // 终结符的解释逻辑
    }
}
  • NonterminalExpression 非终结符表达式:为文法中的非终结符实现解释操作,通常包含一个或多个子表达式。
public class NonterminalExpression extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public void interpret(Context context) {
        // 非终结符的解释逻辑,通常需要调用子表达式的解释操作
        left.interpret(context);
        right.interpret(context);
    }
}
  • Context 上下文:包含解释器之外的一些全局信息。
public class Context {
    // 上下文信息,如变量存储、环境设置等
}

21.5 注意事项 #

  • 解释器模式对于简单的语言或规则系统比较适用,对于复杂的语言,使用解释器模式可能会导致系统过于复杂,难以维护和扩展。
  • 在设计解释器模式时,要考虑表达式的组合和递归调用,确保解释器能够正确处理各种表达式的嵌套和组合。

21.6 优缺点 #

  • 优点:
    • 可以方便地实现一个简单的语言或规则系统,将语言的解释逻辑封装在解释器中,提高了代码的可维护性和可扩展性。
    • 可以通过组合和扩展解释器类,实现复杂的语言结构,符合开闭原则。
  • 缺点:
    • 对于复杂的语言,解释器模式会导致类的数量增加,系统变得复杂,性能可能会受到影响。
    • 解释器模式的设计需要对语言的语法和语义有深入的理解,对于开发人员的要求较高。

21.7 使用场景 #

  • 简单的表达式求值:对于简单的数学表达式(如加减乘除运算),可以使用解释器模式进行解释和求值。
public class NumberExpression extends AbstractExpression {
    private int value;

    public NumberExpression(int value) {
        this.value = value;
    }

    @Override
    public void interpret(Context context) {
        context.setResult(value);
    }
}

public class AddExpression extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public AddExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public void interpret(Context context) {
        left.interpret(context);
        int leftValue = context.getResult();
        right.interpret(context);
        int rightValue = context.getResult();
        context.setResult(leftValue + rightValue);
    }
}

public class SubtractExpression extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public SubtractExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public void interpret(Context context) {
        left.interpret(context);
        int leftValue = context.getResult();
        right.interpret(context);
        int rightValue = context.getResult();
        context.setResult(leftValue - rightValue);
    }
}

public class Context {
    private int result;

    public int getResult() {
        return result;
    }

    public void setResult(int result) {
        this.result = result;
    }
}
  • 自定义规则引擎:在一些业务系统中,需要根据自定义的规则(如优惠规则、权限规则等)进行判断和处理,可以使用解释器模式实现规则的解释和执行。
public RuleExpression(String rule) {
    this.rule = rule;
}

@Override
public void interpret(Context context) {
    // 这里可以根据规则字符串进行解析和处理,例如根据规则字符串的内容判断权限、计算优惠等
    // 以下是一个简单示例,根据规则字符串进行权限判断
    if (rule.contains("admin") {
        context.setHasPermission(true);
    } else {
        context.setHasPermission(false);
    }
}
}

public class Context {
    private boolean hasPermission;

    public boolean hasPermission() {
        return hasPermission;
    }

    public void setHasPermission(boolean hasPermission) {
        this.hasPermission = hasPermission;
    }
}
  • 重复发生的问题可以使用解释器模式
  • 一个简单语法需要解释的场景

注意:

尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。

22. 享元模式(Flyweight Pattern) #

22.1 定义 #

Use sharing to support large numbers of fine-grained objects efficiently.(使用共享对象可有效地支持大量的细粒度的对象。)

22.2 自己的理解 #

享元模式就像一个共享资源池,当需要创建大量相似的对象时,将这些对象存储在池中,当再次需要相同对象时,直接从池中获取,而不是重新创建,从而节省内存和资源。例如,在一个文档编辑软件中,字母、数字、标点符号等字符对象可能会大量出现,使用享元模式可以将这些字符对象存储在一个池子里,需要时复用,而不是为每个字符创建新对象。

对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)。

  • 内部状态

内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变.

  • 外部状态

外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态

22.3 类型 #

结构型模式。

22.4 通用代码 #

  • Flyweight 抽象享元角色:定义享元对象的接口或抽象类。
public interface Flyweight {
    void operation(String extrinsicState);
}
  • ConcreteFlyweight 具体享元角色:实现抽象享元角色,存储内部状态,外部状态通过方法参数传递。
public class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String extrinsicState) {
        System.out.println("内部状态:" + intrinsicState + ",外部状态:" + extrinsicState);
    }
}
  • FlyweightFactory 享元工厂角色:负责创建和管理享元对象,使用一个池存储享元对象。
import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    private Map<String, Flyweight> flyweightPool = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        if (flyweightPool.containsKey(key) {
            return flyweightPool.get(key);
        } else {
            Flyweight flyweight = new ConcreteFlyweight(key);
            flyweightPool.put(key, flyweight);
            return flyweight;
        }
    }
}

22.5 注意事项 #

  • 享元模式的关键是区分内部状态和外部状态,内部状态存储在享元对象中,外部状态通过方法参数传递,避免将过多的状态存储在享元对象中,导致对象不能共享。
  • 享元工厂的设计要考虑线程安全,在多线程环境下,对享元池的操作可能会引发并发问题。
  • 享元模式是线程不安全的,只有依靠经验,在需要的地方考虑一下线程安全,在大部分场景下不用考虑。对象池中的享元对象尽量多,多到足够满足为止,
  • 性能安全:外部状态最好以java的基本类型作为标志,如String,int,可以提高效率。

22.6 优缺点 #

  • 优点:
    • 可以大幅度减少内存中对象的数量,节省系统资源,提高系统性能,尤其是在处理大量相似对象时。
    • 享元模式将对象的共享部分提取出来,提高了对象的复用性,符合内存优化的原则。
  • 缺点:
    • 享元模式会使系统更加复杂,尤其是在区分内部状态和外部状态时,需要精心设计。
    • 享元工厂的管理可能会带来额外的复杂性,如对象的创建、存储和检索等操作。

22.7 使用场景 #

  • 文本编辑器:在文本编辑器中,对于重复出现的字符,可以使用享元模式共享字符对象,减少内存占用。
public class CharacterFlyweightFactory {
    private Map<Character, Flyweight> characterPool = new HashMap<>();

    public Flyweight getCharacter(char c) {
        if (characterPool.containsKey(c) {
            return characterPool.get(c);
        } else {
            Flyweight flyweight = new ConcreteFlyweight(String.valueOf(c);
            characterPool.put(c, flyweight);
            return flyweight;
        }
    }
}
  • 游戏中的粒子系统:在游戏开发中,大量的粒子(如雪花、雨滴等)具有相似的属性和行为,可以使用享元模式共享粒子对象,提高性能。
public class ParticleFlyweightFactory {
    private Map<String, Flyweight> particlePool = new HashMap<>();

    public Flyweight getParticle(String particleType) {
        if (particlePool.containsKey(particleType) {
            return particlePool.get(particleType);
        } else {
            Flyweight flyweight = new ConcreteFlyweight(particleType);
            particlePool.put(particleType, flyweight);
            return flyweight;
        }
    }
}
  • 系统中存在大量的相似对象。
  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份.
  • 需要缓冲池的场景

23. 桥接模式(Bridge Pattern) #

23.1 定义 #

Decouple an abstraction from its implementation so that the two can vary independent心y.(将抽象和实现解耦,使得两者可以独立地变化。)

23.2 自己的理解 #

桥接模式就像一个桥梁,将抽象部分(如形状)和实现部分(如颜色)连接起来,使得抽象部分和实现部分可以独立地扩展和变化,而不会相互影响。例如,在绘制不同颜色的形状时,形状可以有圆形、矩形等,颜色可以有红色、蓝色等,使用桥接模式可以将形状和颜色分离,使得可以方便地添加新的形状或颜色,而不会对已有的部分产生影响。

23.3 类型 #

结构型模式。

23.4 通用代码 #

  • Abstraction 抽象类:定义抽象部分的接口,包含一个对实现部分的引用。
public abstract class Abstraction {
    protected Implementor implementor;

    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }

    public abstract void operation();
}
  • RefinedAbstraction 扩充抽象类:扩充抽象类的功能,实现更复杂的操作。
public class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    @Override
    public void operation() {
        implementor.operationImpl();
        System.out.println("RefinedAbstraction的额外操作");
    }
}
  • Implementor 实现类接口:定义实现部分的接口。
public interface Implementor {
    void operationImpl();
}
  • ConcreteImplementorA 具体实现类 A:实现实现类接口。
public class ConcreteImplementorA implements Implementor {
    @Override
    public void operationImpl() {
        System.out.println("具体实现类A的实现操作");
    }
}
  • ConcreteImplementorB 具体实现类 B:实现实现类接口。
public class ConcreteImplementorB implements Implementor {
    @Override
    public void operationImpl() {
        System.out.println("具体实现类B的实现操作");
    }
}

23.5 注意事项 #

  • 桥接模式的关键在于将抽象部分和实现部分分离,并且通过组合的方式将它们连接起来,这样可以使抽象和实现部分独立地扩展,避免了使用继承导致的类爆炸问题。
  • 在设计时,要准确地划分抽象部分和实现部分,确保它们之间的关联是合理的,并且要注意它们之间的交互逻辑,避免出现不匹配的情况。

23.6 优缺点 #

  • 优点:
    • 分离了抽象部分和实现部分,使它们可以独立地变化,符合开闭原则,提高了系统的可扩展性。
    • 实现了抽象和实现的解耦,抽象部分和实现部分可以分别扩展,不会相互影响,提高了代码的灵活性。
    • 对于多维度的变化,桥接模式可以很好地应对,避免了使用继承带来的复杂的类层次结构。
  • 缺点:
    • 增加了系统的复杂度,需要理解抽象部分和实现部分的分离和组合关系,对于开发人员的设计能力要求较高。
    • 由于分离和组合的设计,代码的理解和维护成本可能会有所增加,尤其是在大型系统中,需要更多的设计和架构知识。

23.7 使用场景 #

  • 不同平台的图形绘制:在跨平台开发中,图形绘制的抽象部分(如绘制形状)可以与具体的实现部分(如在 Windows 平台绘制或在 Linux 平台绘制)分离,使用桥接模式可以方便地切换平台,而不影响图形绘制的抽象逻辑。
public interface DrawingAPI {
    void drawCircle(double x, double y, double radius);
    void drawRectangle(double x, double y, double width, double height);
}

public class WindowsDrawingAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.println("在Windows平台上绘制圆形,圆心坐标:(" + x + ", " + y + "),半径:" + radius);
    }

    @Override
    public void drawRectangle(double x, double y, double width, double height) {
        System.out.println("在Windows平台上绘制矩形,左上角坐标:(" + x + ", " + y + "),宽:" + width + ",高:" + height);
    }
}

public class LinuxDrawingAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.println("在Linux平台上绘制圆形,圆心坐标:(" + x + ", " + y + "),半径:" + radius);
    }

    @Override
    public void drawRectangle(double x, double y, double width, double height) {
        System.out.println("在Linux平台上绘制矩形,左上角坐标:(" + x + ", " + y + "),宽:" + width + ",高:" + height);
    }
}

public abstract class Shape {
    protected DrawingAPI drawingAPI;

    public Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }

    public abstract void draw();
}

public class Circle extends Shape {
    private double x;
    private double y;
    private double radius;

    public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public void draw() {
        drawingAPI.drawCircle(x, y, radius);
    }
}

public class Rectangle extends Shape {
    private double x;
    private double y;
    private double width;
    private double height;

    public Rectangle(double x, double y, double width, double height, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        drawingAPI.drawRectangle(x, y, width, height);
    }
}
  • 设备驱动程序开发:对于不同类型的设备(如打印机、扫描仪等),设备的抽象操作(如打印、扫描)可以与具体的设备驱动实现分离,使用桥接模式可以方便地添加新的设备类型或驱动实现。
public interface DeviceDriver {
    void performOperation();
}

public class PrinterDriver implements DeviceDriver {
    @Override
    public void performOperation() {
        System.out.println("打印机驱动执行操作");
    }
}

public class ScannerDriver implements DeviceDriver {
    @Override
    public void performOperation() {
        System.out.println("扫描仪驱动执行操作");
    }
}

public abstract class Device {
    protected DeviceDriver deviceDriver;

    public Device(DeviceDriver deviceDriver) {
        this.deviceDriver = deviceDriver;
    }

    public abstract void operate();
}

public class Printer extends Device {
    public Printer(DeviceDriver deviceDriver) {
        super(deviceDriver);
    }

    @Override
    public void operate() {
        deviceDriver.performOperation();
        System.out.println("打印机进行打印操作");
    }
}

public class Scanner extends Device {
    public Scanner(DeviceDriver deviceDriver) {
        super(deviceDriver);
    }

    @Override
    public void operate() {
        deviceDriver.performOperation();
        System.out.println("扫描仪进行扫描操作");
    }
}
  • 不希望或不适用使用继承的场景
  • 接口或抽象类不稳定的场景
  • 重用性要求较高的场景

注意:

发现类的继承有N层时,可以考虑使用桥梁模式。桥梁模式主要考虑如何拆分抽象和实现。