《设计模式之禅》笔记

《设计模式之禅》笔记

马草原 1,484 2023-07-01

《设计模式之禅》笔记

《设计模式之禅》是一本深入介绍软件设计模式的宝贵指南。通过23种经典设计模式的生动解释、实际案例和具体代码示例,本书帮助开发者从最佳实践的角度提升代码质量、加速开发过程、降低风险、适应变化,并培养职业素养。它为开发者构建坚实的设计基础,使其能够更富创造力地应对日益复杂的软件开发挑战。

设计模式之禅


一、6大设计原则解读

1. 单一职责原则

一个类应该只有一个引起它变化的原因,即只承担一个明确的职责。这有助于提高代码的可读性、可维护性和扩展性,并减少变化的传播和混淆。

2. 里氏替换原则

子类应该能够完全替代父类,而不会产生意外的行为或破坏原有的功能。提高低码的可维护性和灵活性。

3. 依赖倒置原则

高层模块不应依赖于低层模块,两者都应依赖于抽象接口。抽象不应该依赖于具体实现,而具体实现应该依赖于抽象。可以降低代码的耦合性。

4. 接口隔离原则

客户端不应依赖不需要的接口。将大接口分解成小而精确的接口,以满足各个客户端的需求,避免不必要的复杂性和依赖。可以提高系统的灵活性和可维护性。

5. 迪米特法则(最少知道原则)

强调一个模块不应该了解其他模块内部过多的细节。一个对象应该与其它对象保持最小的交互,只与其直接的成员或参数交互。可以降低代码耦合,提高系统的可维护性和扩展性。

6. 开闭原则

软件实体(类、模块、方法等)应该对扩展开放,对修改关闭。系统的设计应该允许新功能的添加,而不必修改现有的代码。这有助于减少变更的风险,提高代码的可维护性和可扩展性。



二、23种设计模式解读

1. 单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

应用场景:

  • 资源共享:当多个部分需要共享一个资源时,可以使用单例模式确保只有一个实例存在,避免资源冲突。
  • 配置管理:用于管理全局配置信息,例如数据库连接信息、日志设置等。
  • 日志记录器:确保在应用程序中只有一个日志记录器,以避免重复记录和不一致的日志。
  • 线程池:在需要线程池来管理线程时,可以使用单例模式确保全局共享的线程池实例。
  • 缓存管理:在需要全局共享的缓存管理器情况下,单例模式可以用于确保只有一个缓存管理实例。

优点:

  • 全局访问:单例模式允许在整个应用程序中访问相同的实例,避免了重复创建和管理多个实例的问题。
  • 资源节省:由于只有一个实例存在,可以减少内存和系统资源的浪费。

缺点:

  • 全局状态:单例模式可能导致全局状态的存在,使代码变得难以维护和测试。
  • 扩展困难:由于单例模式的全局性质,当需要扩展功能时可能会变得复杂。
  • 违背单一职责原则:有时候我们的单例类可能会有多个职责,这可能会导致违背单一职责原则。
  • 并发控制:在多线程环境下,需要额外的措施来确保单例实例的正确创建和访问,以避免并发问题。
基于Java的代码示例:
  1. 饿汉式单例(Eager Initialization):在类加载时就创建单例实例。线程安全,但可能会造成不必要的资源浪费。
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton() {
    }
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  1. 懒汉式单例(Lazy Initialization):在第一次调用获取实例方法时才创建单例实例。可能存在线程安全问题,需要使用同步控制或者双重检查锁来解决。
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
    }

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
  1. 双重检查锁(Double-Checked Locking):在懒汉式的基础上,使用双重检查来减小同步开销,保证线程安全。
public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {
    }

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}
  1. 静态内部类(Initialization-on-demand holder idiom):使用静态内部类来持有单例实例,在第一次调用获取实例方法时创建。线程安全且懒加载。
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {
    }

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

    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  1. 枚举(Enum)单例:使用枚举来实现单例,可以避免反射和序列化等问题,同时也能保证线程安全。
public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        // ...
    }
}

2. 工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

应用场景:

  • 对象创建的抽象:当需要创建一组相关或相似的对象,并且希望通过一个公共接口进行对象的创建,可以使用工厂方法模式。
  • 解耦对象创建和使用:工厂方法模式可以将对象的创建与使用分离,使得客户端代码不依赖于具体的对象创建细节。
  • 提高扩展性:当需要新增一种产品类型时,只需要添加相应的具体工厂类和产品类,而无需修改现有代码,实现了开闭原则。
  • 增加可维护性:通过工厂方法模式,不同产品的创建逻辑都集中在各自的工厂类中,有助于代码的组织和维护。

优点:

  • 封装对象创建过程:将对象的创建封装在具体的工厂类中,使得客户端代码只需关注使用而不用关心创建过程(解耦)。
  • 可扩展性:通过添加新的具体工厂类和产品类,可以轻松地扩展系统的功能,符合开闭原则。
  • 隐藏实现细节:客户端不需要知道产品的具体创建细节,只需通过工厂接口获取所需的产品。

缺点:

  • 类的数量增加:引入了更多的类,包括抽象工厂、具体工厂和具体产品,增加了类的数量。
  • 复杂性增加:相对于直接实例化对象,工厂方法模式引入了更多的层级,可能会增加代码的复杂性。
  • 增加了系统抽象性:对于简单的情况,使用工厂方法模式可能会过于复杂,不一定是最佳选择。
基于Java的代码示例:
interface Document {
    void open();
    void save();
}
//  文本文档
class TextDocument implements Document {
    @Override
    public void open() {
        System.out.println("Text Document opened");
    }

    @Override
    public void save() {
        System.out.println("Text Document saved");
    }
}

//  电子表格文档
class SpreadsheetDocument implements Document {
    @Override
    public void open() {
        System.out.println("Spreadsheet Document opened");
    }

    @Override
    public void save() {
        System.out.println("Spreadsheet Document saved");
    }
}
abstract class DocumentFactory {
    public abstract Document createDocument();
}
// 创建文本文档
class TextDocumentFactory extends DocumentFactory {
    @Override
    public Document createDocument() {
        return new TextDocument();
    }
}

// 创建电子表格文档
class SpreadsheetDocumentFactory extends DocumentFactory {
    @Override
    public Document createDocument() {
        return new SpreadsheetDocument();
    }
}
public class Main {
    public static void main(String[] args) {
        DocumentFactory textFactory = new TextDocumentFactory();
        Document textDocument = textFactory.createDocument();
        textDocument.open();
        textDocument.save();

        DocumentFactory spreadsheetFactory = new SpreadsheetDocumentFactory();
        Document spreadsheetDocument = spreadsheetFactory.createDocument();
        spreadsheetDocument.open();
        spreadsheetDocument.save();
    }
}

3. 抽象工厂模式

为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。

应用场景:

  • 产品族创建:当需要创建一组具有相关性的产品(产品族),并且这些产品在使用时需要保持一致性,可以使用抽象工厂模式。
  • 跨系统交互:在需要将不同系统中的对象进行交互时,可以使用抽象工厂模式来确保对象之间的兼容性。
  • 解耦合:抽象工厂模式可以将客户端代码与具体产品的实现解耦,使得系统更加灵活和可维护。
  • 平台无关性:当希望在不同平台上使用不同的实现时,可以使用抽象工厂模式来创建适应不同平台的产品。

优点:

  • 产品一致性:抽象工厂模式确保了创建的产品在一组相关产品中保持一致性,因为它们由同一个工厂创建。
  • 解耦:客户端代码只需与抽象工厂和产品接口交互,不需要了解具体的产品实现,从而降低了耦合度。
  • 可扩展性:通过添加新的抽象工厂和具体产品类,可以轻松地扩展系统的功能,符合开闭原则。
  • 灵活性:抽象工厂模式允许在运行时切换不同的工厂,从而实现不同的产品组合。

缺点:

  • 复杂性增加:引入了更多的抽象类和接口,可能增加系统的复杂性。
  • 类的数量增加:相对于其他创建型模式,抽象工厂模式引入了更多的类,包括抽象工厂、具体工厂和产品族的抽象产品。
  • 灵活性有限:由于每个抽象工厂和产品族都需要事先定义,因此可能在运行时无法动态地添加新的产品。
基于Java的代码示例:
// 按钮
interface Button {
    void render();
}

// 文本输入框
interface TextField {
    void render();
}
// Windows按钮
class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Render Windows Button");
    }
}

// Windows文本输入框
class WindowsTextField implements TextField {
    @Override
    public void render() {
        System.out.println("Render Windows TextField");
    }
}
//  Mac按钮
class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("Render Mac Button");
    }
}

// Mac文本输入框
class MacTextField implements TextField {
    @Override
    public void render() {
        System.out.println("Render Mac TextField");
    }
}
interface UIFactory {
    Button createButton();
    TextField createTextField();
}
// Windows UI工厂
class WindowsUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

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

// Mac UI工厂
class MacUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public TextField createTextField() {
        return new MacTextField();
    }
}
public class Main {
    public static void main(String[] args) {
    	// 使用抽象工厂模式创建不同风格的UI控件,而不必关心具体的实现。
        UIFactory windowsFactory = new WindowsUIFactory();
        Button windowsButton = windowsFactory.createButton();
        TextField windowsTextField = windowsFactory.createTextField();
        windowsButton.render();
        windowsTextField.render();

        UIFactory macFactory = new MacUIFactory();
        Button macButton = macFactory.createButton();
        TextField macTextField = macFactory.createTextField();
        macButton.render();
        macTextField.render();
    }
}

4. 模板方法模式

定义了一个流程的模版,将流程的具体步骤延迟到子类中实现。

应用场景:

  • 抽象模版:当一个具有固定的执行步骤和顺序的产品,但每个步骤的具体实现可能因子类而异时,可以使用模板方法模式。
  • 代码重用:当多个类共享相似的流程,但其中某些步骤需要定制化时,可以使用模板方法模式,避免代码重复。
  • 固定的流程:当需要强制实现一系列特定的步骤,确保每个子类都遵循相同的执行流程时,可以使用模板方法模式。

优点:

  • 代码复用:将通用的步骤抽象到父类中,避免了重复编写相似的代码。
  • 易于维护:主要逻辑位于父类中,一旦需要修改算法,只需在父类中进行修改,所有子类都会受到影响。
  • 遵循开闭原则:新增步骤只需要创建新的子类实现,不需要修改现有的流程,符合开闭原则。
  • 约束子类行为:通过定义抽象的流程方法,强制子类实现特定的步骤,确保一致的执行流程。

缺点:

  • 固定的模版结构:模板方法模式限制了流程内容,可能会让修改和扩展变得复杂。
  • 增加类的数量:为每个不同的实现都需要创建一个子类,可能会导致类的数量增加。
  • 灵活性有限:由于流程步骤已经固定,可能无法灵活地适应各种变化。
基于Java的代码示例:
abstract class Beverage {
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();
    abstract void addCondiments();

    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }
}
// 咖啡
class Coffee extends Beverage {
    @Override
    void brew() {
        System.out.println("Dripping coffee through filter");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

// 茶
class Tea extends Beverage {
    @Override
    void brew() {
        System.out.println("Steeping the tea");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding lemon");
    }
}
public class Main {
    public static void main(String[] args) {
    	// 使用模板方法模式来制作咖啡和茶
        System.out.println("Making coffee...");
        Beverage coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println("\nMaking tea...");
        Beverage tea = new Tea();
        tea.prepareRecipe();
    }
}

5. 建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

应用场景:

  • 创建复杂对象:当需要创建具有多个组成部分和步骤的复杂对象时,建造者模式可以帮助将构建过程模块化并分步进行。
  • 不同表示:当一个对象可以有不同的表示形式,但构建过程相同或相似时,可以使用建造者模式来构建不同的表示。
  • 避免过多的构造函数参数:当一个类的构造函数参数过多,难以管理时,可以通过建造者模式将参数分解到不同的构建方法中。
  • 创建不可变对象:建造者模式可以用于创建不可变对象,通过在构建过程中设置值,确保对象不可修改。

优点:

  • 分步构建:建造者模式将复杂对象的构建分解成一系列简单的步骤,使得构建过程更清晰和可控。
  • 代码复用:通过共享相同的建造者类,可以重复使用相同的构建逻辑,减少代码重复。
  • 隔离构建细节:客户端不需要知道对象的具体构建细节,只需要关注创建的步骤和顺序。
  • 扩展性:可以定义不同的具体建造者类来创建不同的产品表示,方便扩展和变化。

缺点:

  • 增加类的数量:引入了更多的类,包括抽象建造者、具体建造者和指挥者,可能增加了类的数量。
  • 构建过程复杂:对于简单对象的构建,建造者模式可能会使构建过程变得更复杂。
  • 不适合一些简单情况:在对象构建过程简单且不涉及多个组成部分时,建造者模式可能过于繁琐。
基于Java的代码示例:
class Car {
    private String engine;
    private int seats;
    private boolean hasGPS;

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

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

    public void setHasGPS(boolean hasGPS) {
        this.hasGPS = hasGPS;
    }

    @Override
    public String toString() {
        return "Car{" +
                "engine='" + engine + '\'' +
                ", seats=" + seats +
                ", hasGPS=" + hasGPS +
                '}';
    }
}
interface CarBuilder {
    void setEngine(String engine);
    void setSeats(int seats);
    void setHasGPS(boolean hasGPS);
    Car build();
}
class ConcreteCarBuilder implements CarBuilder {
    private Car car = new Car();

    @Override
    public void setEngine(String engine) {
        car.setEngine(engine);
    }

    @Override
    public void setSeats(int seats) {
        car.setSeats(seats);
    }

    @Override
    public void setHasGPS(boolean hasGPS) {
        car.setHasGPS(hasGPS);
    }

    @Override
    public Car build() {
        return car;
    }
}
class Director {
    private CarBuilder builder;

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

    public Car construct() {
        builder.setEngine("V6");
        builder.setSeats(4);
        builder.setHasGPS(true);
        return builder.build();
    }
}
public class Main {
    public static void main(String[] args) {
    	// 使用建造者模式构建不同配置的汽车对象
        CarBuilder builder = new ConcreteCarBuilder();
        Director director = new Director(builder);
        Car car = director.construct();

        System.out.println(car);
    }
}

6. 代理模式

为其他对象提供一种代理以控制对这个对象的访问。

应用场景:

  • 远程代理:用于在不同地址空间中代表对象,如远程服务、Web服务等。
  • 虚拟代理:用于在需要创建昂贵对象时,代替实际对象,延迟对象的实际创建。
  • 保护代理:用于控制对象的访问权限,根据用户的权限限制对对象的访问。
  • 缓存代理:在需要频繁访问资源时,使用代理来缓存结果,提高访问速度。
  • 智能代理:用于添加额外的行为,如记录日志、服务监控等。

优点:

  • 控制访问:代理模式可以控制客户端对真实对象的访问,限制其直接访问。
  • 增加功能:代理对象可以在不修改真实对象的情况下,扩展或添加功能。
  • 延迟加载:虚拟代理可以延迟真实对象的创建,提高系统性能。
  • 解耦:客户端只需知道代理对象,无需了解真实对象的实现细节。
  • 保护真实对象:代理可以充当真实对象的保护层,防止不合法操作。

缺点:

  • 代码复杂性增加:引入代理对象可能会增加代码的复杂性。
  • 性能下降:可能会因为添加了额外的层次而导致性能下降。
  • 代理过多:在某些情况下,系统中可能会因为代理而增加过多的类。
基于Java的代码示例:
  1. 静态代理
interface ImageLoader {
    void displayImage(String imageUrl);
}
class RealImageLoader implements ImageLoader {
    @Override
    public void displayImage(String imageUrl) {
        System.out.println("Displaying image from " + imageUrl);
    }
}
class ProxyImageLoader implements ImageLoader {
    private RealImageLoader realImageLoader = new RealImageLoader();

    @Override
    public void displayImage(String imageUrl) {
        System.out.println("Before displaying image");
        realImageLoader.displayImage(imageUrl);
        System.out.println("After displaying image");
    }
}
public class Main {
    public static void main(String[] args) {
    	// 使用静态代理类来加载图片
        ImageLoader proxyImageLoader = new ProxyImageLoader();
        proxyImageLoader.displayImage("example.com/image.jpg");
    }
}
  1. 动态代理
interface ImageLoader {
    void displayImage(String imageUrl);
}
class RealImageLoader implements ImageLoader {
    @Override
    public void displayImage(String imageUrl) {
        System.out.println("Displaying image from " + imageUrl);
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class DynamicImageProxy implements InvocationHandler {
    private Object realObject;

    public DynamicImageProxy(Object realObject) {
        this.realObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before displaying image");
        Object result = method.invoke(realObject, args);
        System.out.println("After displaying image");
        return result;
    }

    public static ImageLoader createProxyImageLoader(ImageLoader realImageLoader) {
        return (ImageLoader) Proxy.newProxyInstance(
                realImageLoader.getClass().getClassLoader(),
                realImageLoader.getClass().getInterfaces(),
                new DynamicImageProxy(realImageLoader)
        );
    }
}
public class Main {
    public static void main(String[] args) {
    	// 使用动态代理来加载图片
        ImageLoader realImageLoader = new RealImageLoader();
        ImageLoader proxyImageLoader = DynamicImageProxy.createProxyImageLoader(realImageLoader);
        proxyImageLoader.displayImage("example.com/image.jpg");
    }
}

7. 原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

应用场景:

  • 对象的创建成本较大:当创建一个对象的成本较高,或者涉及到大量的初始化步骤时,可以使用原型模式来复制一个已有对象,从而节省创建成本。
  • 对象的创建过程复杂:当对象的创建过程涉及到许多复杂的步骤和依赖关系时,可以使用原型模式来避免重新构建这些步骤。
  • 需要保存对象的状态历史:在某些情况下,可能需要保留对象的不同历史状态,可以使用原型模式通过克隆来保存状态快照。

优点:

  • 节省资源:克隆已有对象比创建新对象的成本更低,可以节省系统资源。
  • 避免重复初始化:可以避免重复的初始化步骤,提高了对象创建的效率。
  • 动态添加和删除对象:可以通过克隆来动态添加和删除对象,而无需修改原始代码。
  • 保护对象的不可变性:通过克隆创建对象,可以避免其他代码对原始对象的修改。
  • 更好的性能:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多

缺点:

  • 深克隆的复杂性:如果对象内部包含了引用类型的属性,进行深克隆可能需要处理引用对象的克隆。
  • 需要实现Cloneable接口:为了使用原型模式,需要在类中实现Cloneable接口,这可能会影响类的设计。
  • 破坏封装性:直接访问对象的克隆方法可能会破坏对象的封装性(直接在内存中拷贝不会运行构造方法)。
基于Java的代码示例:
interface Prototype extends Cloneable {
    Prototype clone();
}
class ConcretePrototype implements Prototype {
    private int value;

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

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

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}
public class Main {
    public static void main(String[] args) {
        ConcretePrototype original = new ConcretePrototype(10);
        // 使用原型模式来高效克隆对象
        ConcretePrototype copy = (ConcretePrototype) original.clone();

        System.out.println("Original value: " + original.getValue());
        System.out.println("Copy value: " + copy.getValue());

        copy.setValue(20);
        System.out.println("Original value after copy modification: " + original.getValue());
        System.out.println("Copy value after modification: " + copy.getValue());
    }
}

8. 中介者模式

用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

应用场景:

  • 复杂交互场景:当多个对象之间的交互变得复杂而杂乱时,可以使用中介者模式将交互逻辑集中在中介者中,减少对象之间的耦合。
  • GUI系统:在图形用户界面(GUI)中,各个界面组件(如按钮、文本框、复选框等)之间需要交互,中介者模式可以用于简化组件之间的通信。
  • 多人协作系统:在多人协作的系统中,不同角色之间需要相互协作,中介者模式可以用于管理和协调角色之间的通信和操作。

优点:

  • 解耦对象间关系:中介者模式将对象之间的交互逻辑集中在中介者中,使得各个对象之间不需要直接相互引用,从而减少耦合。
  • 集中控制:通过中介者,可以集中控制对象之间的交互逻辑,简化系统的维护和修改。
  • 降低复杂性:将复杂的交互逻辑抽象到中介者中,使各个对象的逻辑更加清晰简单。

缺点:

  • 中介者复杂性:随着系统中对象的增多,中介者可能变得复杂,管理多个对象之间的交互关系可能变得困难。
  • 单点故障:中介者本身可能成为系统的单点故障,一旦中介者出现问题,整个系统的交互也可能受到影响。
  • 过度集中化:如果过度使用中介者模式,可能导致交互逻辑过于集中,使得部分对象的逻辑被淹没在中介者中。
基于Java的代码示例:
interface ChatMediator {
    void addUser(User user);
    void sendMessage(String message, User user);
}
import java.util.ArrayList;
import java.util.List;

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

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

    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            if (user != sender) {
                user.receiveMessage(message);
            }
        }
    }
}
interface User {
    void sendMessage(String message);
    void receiveMessage(String message);
}
class ChatUser implements User {
    private String name;
    private ChatMediator mediator;

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

    @Override
    public void sendMessage(String message) {
        System.out.println(name + " sends: " + message);
        mediator.sendMessage(message, this);
    }

    @Override
    public void receiveMessage(String message) {
        System.out.println(name + " receives: " + message);
    }
}
public class Main {
    public static void main(String[] args) {
        ChatMediator chatMediator = new ChatRoom();

        User user1 = new ChatUser("User1", chatMediator);
        User user2 = new ChatUser("User2", chatMediator);
        User user3 = new ChatUser("User3", chatMediator);
		// 通过中介者进行聊天
        user1.sendMessage("Hello, everyone!");
        user2.sendMessage("Hi there!");
    }
}

9. 命令模式

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

应用场景:

  • 菜单与按钮操作:在图形用户界面(GUI)中,菜单项和按钮的点击操作可以通过命令模式实现,每个菜单项或按钮都可以被封装成一个命令对象。
  • 多级撤销:命令模式可以实现撤销和恢复操作,特别适用于需要多级撤销的场景,如文档编辑器的撤销操作。
  • 日志记录:可以通过命令模式记录系统的操作日志,以便后续审计或故障排查。
  • 任务调度:命令模式可以用于实现任务的异步调度,将任务封装成命令对象,交由调度器执行。
  • 遥控器与设备控制:在遥控器控制家电等设备时,可以使用命令模式来将不同的操作封装成命令对象,从而实现控制的灵活性和扩展性。

优点:

  • 解耦调用者和接收者:命令模式将请求的发送者(调用者)与接收者(执行者)解耦,使得调用者不需要知道具体的执行细节。
  • 容易扩展:可以很容易地添加新的命令类,而无需修改现有的代码,满足开闭原则。
  • 支持撤销和恢复:命令模式支持撤销和恢复操作,可以在需要的情况下进行回滚。
  • 支持日志和任务队列:命令模式可以用于实现操作日志的记录和任务队列的调度。

缺点:

  • 类数量增加:在使用命令模式时,可能会增加大量的命令类,增加了类的数量。
  • 复杂性增加:对于简单的操作,使用命令模式可能会增加不必要的复杂性。
  • 内存占用:由于需要创建大量的命令对象,可能会增加内存占用。
基于Java的代码示例:
interface Command {
    void execute();
}
// 剪切
class CutCommand implements Command {
    private TextEditor textEditor;

    public CutCommand(TextEditor textEditor) {
        this.textEditor = textEditor;
    }

    @Override
    public void execute() {
        textEditor.cut();
    }
}

// 复制
class CopyCommand implements Command {
    private TextEditor textEditor;

    public CopyCommand(TextEditor textEditor) {
        this.textEditor = textEditor;
    }

    @Override
    public void execute() {
        textEditor.copy();
    }
}

// 粘贴
class PasteCommand implements Command {
    private TextEditor textEditor;

    public PasteCommand(TextEditor textEditor) {
        this.textEditor = textEditor;
    }

    @Override
    public void execute() {
        textEditor.paste();
    }
}
// 文本编辑器
class TextEditor {
    public void cut() {
        System.out.println("Cut the selected text");
    }

    public void copy() {
        System.out.println("Copy the selected text");
    }

    public void paste() {
        System.out.println("Paste the copied text");
    }
}
class Invoker {
    private Command command;

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

    public void executeCommand() {
        command.execute();
    }
}
public class Main {
    public static void main(String[] args) {
        TextEditor textEditor = new TextEditor();

        Command cutCommand = new CutCommand(textEditor);
        Command copyCommand = new CopyCommand(textEditor);
        Command pasteCommand = new PasteCommand(textEditor);

        Invoker invoker = new Invoker();

		// 使用命令模式来执行编辑操作
        invoker.setCommand(cutCommand);
        invoker.executeCommand();

        invoker.setCommand(copyCommand);
        invoker.executeCommand();

        invoker.setCommand(pasteCommand);
        invoker.executeCommand();
    }
}

10. 责任链模式

使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

应用场景:

  • 请求处理链: 当一个请求需要经过多个处理阶段,每个阶段有不同的处理者时,责任链模式非常适用。例如,工作流程中的多个审批环节,每个环节可以由不同的人员来审批。
  • 日志记录: 在日志记录中,可以构建一个日志记录链,每个记录器可以根据日志级别决定是否处理该日志,或者将其传递给下一个记录器。这样可以灵活地控制日志的输出方式和级别。
  • 用户权限验证: 在一个应用中,可以通过责任链模式来验证用户的权限。每个处理器可以检查用户的不同权限级别,如果权限足够,则处理请求,否则将请求传递给下一个处理器。
  • 事件处理系统: 责任链模式也可以用于事件处理系统中。不同的事件处理器可以根据事件的类型和优先级来决定是否处理事件,或者将事件传递给下一个处理器。
  • HTTP 请求处理: 在 Web 开发中,可以使用责任链模式来处理 HTTP 请求。每个处理器可以负责不同的请求处理逻辑,例如身份验证、参数验证、缓存等。

优点:

  • 解耦和灵活性: 责任链模式将请求发送者和接收者解耦,使得可以灵活地添加、移除或者改变处理者,而不影响其他部分的代码。
  • 单一职责原则: 每个处理者只需要关注自己负责的处理逻辑,符合单一职责原则,代码结构更清晰。
  • 动态组合: 可以动态地组合不同的处理器形成处理链,根据实际情况进行配置。
  • 可扩展性: 新的处理者可以很容易地添加到现有的链条中,扩展性很好。

缺点:

  • 请求保证: 由于请求可能在链条中被丢弃或者无法被处理,可能会出现请求得不到处理的情况。
  • 性能影响: 如果责任链较长,请求需要依次传递给每个处理者,可能会影响性能。需要注意设计合理的链条长度。
  • 调试复杂性: 由于请求的流程可能会被多个处理者处理,当出现问题时,调试可能会变得复杂。
基于Java的代码示例:
abstract class Logger {
    protected Logger nextLogger;

    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public abstract void logMessage(int level, String message);
}
class InfoLogger extends Logger {
    @Override
    public void logMessage(int level, String message) {
        if (level == 1) {
            System.out.println("INFO: " + message);
        } else if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
}

class DebugLogger extends Logger {
    @Override
    public void logMessage(int level, String message) {
        if (level == 2) {
            System.out.println("DEB