《设计模式之禅》笔记

《设计模式之禅》笔记

马草原 2,282 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("DEBUG: " + message);
        } else if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
}

class ErrorLogger extends Logger {
    @Override
    public void logMessage(int level, String message) {
        if (level == 3) {
            System.out.println("ERROR: " + message);
        } else if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
}
public class ChainOfResponsibilityExample {
    public static void main(String[] args) {
        Logger infoLogger = new InfoLogger();
        Logger debugLogger = new DebugLogger();
        Logger errorLogger = new ErrorLogger();

        infoLogger.setNextLogger(debugLogger);
        debugLogger.setNextLogger(errorLogger);

        infoLogger.logMessage(1, "This is an information.");
        infoLogger.logMessage(2, "This is a debug message.");
        infoLogger.logMessage(3, "This is an error.");

        // 输出:
        // INFO: This is an information.
        // DEBUG: This is a debug message.
        // ERROR: This is an error.
    }
}

11. 装饰模式

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

应用场景:

  • 动态扩展功能: 当你希望在不修改现有代码的情况下,通过添加新功能来扩展对象的行为时,装饰模式非常适用。这使得代码更具灵活性和可扩展性。
  • 避免类爆炸: 当有许多类似但略有不同的对象时,使用继承来为每个变体创建新类可能会导致类的爆炸。使用装饰模式,可以将这些变体的功能分离出来,避免创建大量类。
  • 逐层添加功能: 装饰模式允许你逐层地添加多个装饰器来增加对象的功能,形成一个功能递进的链条。
  • 符合开闭原则: 装饰模式符合开闭原则,即对扩展开放,对修改关闭。你可以添加新的装饰器来引入新功能,而无需修改现有代码。
  • 组合不同功能: 可以将不同的装饰器组合在一起,以创建具有多个功能组合的对象。

优点:

  • 灵活性和可扩展性: 装饰模式使得在不改变现有代码的情况下添加新功能变得简单,提供了更大的灵活性和可扩展性。
  • 避免类爆炸: 装饰模式避免了由于类似但略有不同的变体导致的类爆炸问题。
  • 单一职责原则: 装饰模式允许将功能分离到不同的装饰器中,符合单一职责原则。
  • 组合功能: 可以通过组合不同的装饰器来构建具有复杂功能的对象。

缺点:

  • 复杂性增加: 装饰模式会引入许多小的装饰器类,可能会使代码结构变得复杂,增加阅读和维护的难度。
  • 运行时开销: 由于可能存在多个装饰器,对象的创建和调用过程可能会增加一些运行时开销。
  • 顺序关系: 装饰器的顺序可能影响最终结果,需要小心管理装饰器的添加顺序。
基于Java的代码示例:
interface Coffee {
    double cost();
    String getDescription();
}

class SimpleCoffee implements Coffee {
    @Override
    public double cost() {
        return 2.0;
    }

    @Override
    public String getDescription() {
        return "Simple Coffee";
    }
}
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 1.0;
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Milk";
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 0.5;
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Sugar";
    }
}
public class DecoratorPatternExample {
    public static void main(String[] args) {
        Coffee simpleCoffee = new SimpleCoffee();
        System.out.println("Cost: " + simpleCoffee.cost() + ", Description: " + simpleCoffee.getDescription());

        Coffee milkCoffee = new MilkDecorator(simpleCoffee);
        System.out.println("Cost: " + milkCoffee.cost() + ", Description: " + milkCoffee.getDescription());

        Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
        System.out.println("Cost: " + sugarMilkCoffee.cost() + ", Description: " + sugarMilkCoffee.getDescription());
    }
}

12. 策略模式

定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。

应用场景:

  • 多算法选择: 当一个问题可以有多种解决策略,而且客户端需要在运行时选择其中一种策略时,策略模式非常适用。
  • 消除大量的条件语句: 如果客户端代码中存在大量的条件语句,每个条件对应一个不同的算法或行为,使用策略模式可以将这些条件语句替换为不同的策略类,使代码更清晰和可维护。
  • 算法的变化与扩展: 当算法可能会经常变化或需要不断扩展时,使用策略模式可以避免频繁修改客户端代码。
  • 遵循开闭原则: 策略模式遵循开闭原则,允许新增策略而不影响现有代码。

优点:

  • 可维护性: 策略模式将算法封装成独立的策略类,使得每个策略都有自己的类,有利于代码的组织和维护。
  • 可扩展性: 可以随时新增新的策略类,实现算法的扩展,无需修改现有代码。
  • 消除条件语句: 策略模式可以消除大量的条件语句,使代码更加清晰,易于理解。
  • 复用性: 不同的上下文可以共享相同的策略,提高代码的复用性。

缺点:

  • 增加类的数量: 每个策略都需要一个独立的类,可能会增加类的数量,使得代码结构变得更加复杂。
  • 客户端知识增加: 客户端需要了解不同的策略类,可能会增加客户端的知识负担。
  • 上下文和策略之间的耦合: 上下文需要了解不同的策略类,可能会导致上下文和策略之间的耦合增加。
基于Java的代码示例:
interface PaymentStrategy {
    double calculatePaymentAmount(double orderAmount);
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public double calculatePaymentAmount(double orderAmount) {
        // 假设信用卡支付有 5% 的手续费
        return orderAmount * 1.05;
    }
}

class PayPalPayment implements PaymentStrategy {
    @Override
    public double calculatePaymentAmount(double orderAmount) {
        // 假设 PayPal 支付有固定的手续费 $2
        return orderAmount + 2.0;
    }
}

class BankTransferPayment implements PaymentStrategy {
    @Override
    public double calculatePaymentAmount(double orderAmount) {
        // 假设银行转账没有额外手续费
        return orderAmount;
    }
}
class Order {
    private double amount;
    private PaymentStrategy paymentStrategy;

    public Order(double amount, PaymentStrategy paymentStrategy) {
        this.amount = amount;
        this.paymentStrategy = paymentStrategy;
    }

    public double calculateTotalPayment() {
        return paymentStrategy.calculatePaymentAmount(amount);
    }
}
public class StrategyPatternExample {
    public static void main(String[] args) {
        PaymentStrategy creditCardPayment = new CreditCardPayment();
        PaymentStrategy paypalPayment = new PayPalPayment();
        PaymentStrategy bankTransferPayment = new BankTransferPayment();
		
        // 不同订单使用不同策略
        Order order1 = new Order(100.0, creditCardPayment);
        System.out.println("Total payment for order 1: $" + order1.calculateTotalPayment());

        Order order2 = new Order(50.0, paypalPayment);
        System.out.println("Total payment for order 2: $" + order2.calculateTotalPayment());

        Order order3 = new Order(200.0, bankTransferPayment);
        System.out.println("Total payment for order 3: $" + order3.calculateTotalPayment());
    }
}

13. 适配器模式

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

应用场景:

  • 接口不匹配: 当两个已有的类或组件之间的接口不匹配,无法直接协同工作时,可以使用适配器模式来实现它们之间的协调。
  • 旧代码和新代码集成: 在现有的系统中,可能有一些旧的组件或类,它们的接口已经过时,需要与新的代码进行集成。适配器模式可以用于将旧代码接口转换为新代码接口。
  • 类库的复用: 在使用第三方类库时,如果其接口不符合你的需求,可以通过适配器模式来封装类库,使其适配你的系统。
  • 多态性支持: 在需要使用多态的场景下,适配器可以帮助不同类型的对象以相同的接口进行交互。

优点:

  • 解决不匹配问题: 适配器模式可以解决不同接口之间的不匹配问题,使得原本不兼容的类能够协同工作。
  • 复用现有类: 可以复用现有的类,不需要修改原有代码,通过适配器进行包装和转换。
  • 灵活性和扩展性: 可以动态地添加适配器来支持新的类和接口,具有较高的灵活性和扩展性。

缺点:

  • 增加复杂性: 引入适配器会增加代码的复杂性,尤其是当涉及多层适配时,可能会导致代码难以理解和维护。
  • 过多的适配器: 如果系统中存在大量的适配器,可能会导致类的过多,使得代码结构变得混乱。
  • 可能会隐藏问题: 适配器模式可以解决不匹配问题,但有时也可能会掩盖系统设计上的问题,使得不合理的接口得以存在。
基于Java的代码示例:
interface EmailSender {
    void sendEmail(String to, String subject, String body);
}
class NewMessageSender {
    void send(String recipient, String message) {
        System.out.println("Sending message to " + recipient + ": " + message);
    }
}
class MessageSenderAdapter implements EmailSender {
    private NewMessageSender newMessageSender;

    public MessageSenderAdapter(NewMessageSender newMessageSender) {
        this.newMessageSender = newMessageSender;
    }

    @Override
    public void sendEmail(String to, String subject, String body) {
        String message = "Subject: " + subject + "\n" + body;
        newMessageSender.send(to, message);
    }
}
public class AdapterPatternExample {
    public static void main(String[] args) {
        NewMessageSender newMessageSender = new NewMessageSender();
        EmailSender emailSender = new MessageSenderAdapter(newMessageSender);
		
        // 使用适配器适配标准消息发送接口完成邮件消息发送
        emailSender.sendEmail("recipient@example.com", "Hello", "This is the email body.");
    }
}

14. 迭代器模式

它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。

应用场景:

  • 遍历集合: 当需要遍历一个集合或数据结构中的元素,而又不希望直接暴露其内部结构时,迭代器模式非常适用。
  • 隐藏集合实现: 迭代器模式可以隐藏集合的内部实现细节,使得集合的实现可以更改而不影响客户端代码。
  • 支持不同遍历方式: 有时候可能需要对同一个集合进行不同的遍历方式,迭代器模式可以支持多个不同的迭代器,每个迭代器可以有不同的遍历逻辑。
  • 解耦遍历和集合: 迭代器模式将遍历逻辑从集合中抽离出来,使得遍历逻辑可以独立于集合进行变化。

优点:

  • 封装集合: 迭代器模式封装了集合的遍历逻辑,隐藏了内部实现,提供了统一的接口。
  • 分离遍历和集合: 迭代器模式将遍历逻辑与集合本身分离,使得集合的变化不会影响遍历的方式。
  • 支持多种遍历方式: 可以针对同一个集合实现多个不同的迭代器,支持多种遍历方式。
  • 增加可扩展性: 新的集合类可以很容易地实现自己的迭代器,增加了可扩展性。

缺点:

  • 增加复杂性: 在某些简单的情况下,使用迭代器模式可能会增加代码的复杂性。
  • 不适用于简单集合: 对于简单的集合,使用迭代器模式可能会显得过于繁琐,不切实际。
基于Java的代码示例:
interface Iterator<T> {
    boolean hasNext();
    T next();
}
import java.util.ArrayList;
import java.util.List;

class MyList<T> {
    private List<T> items = new ArrayList<>();

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

    public Iterator<T> createIterator() {
        return new MyListIterator();
    }

    private class MyListIterator implements Iterator<T> {
        private int index = 0;

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

        @Override
        public T next() {
            if (hasNext()) {
                return items.get(index++);
            }
            return null;
        }
    }
}
public class IteratorPatternExample {
    public static void main(String[] args) {
        MyList<String> myList = new MyList<>();
        myList.add("Item 1");
        myList.add("Item 2");
        myList.add("Item 3");

        Iterator<String> iterator = myList.createIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

15. 组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

应用场景:

  • 对象组织关系: 当你的问题领域可以被表示为“整体-部分”的层次关系,且你希望以统一的方式处理单个对象和对象组合时,组合模式非常适用。
  • 嵌套结构: 如果对象可以以嵌套的方式来构建,形成层次结构,例如树状结构,那么组合模式可以用来表示这种关系。
  • 统一处理: 如果你希望能够以相同的方式对单个对象和对象组合进行操作,例如对整体进行递归处理,组合模式可以实现这一点。
  • 递归结构: 当问题可以递归地分解成子问题,并且每个子问题都可以使用相同的解决方案时,组合模式可以派上用场。

优点:

  • 统一处理: 组合模式使得客户端可以一致地处理单个对象和对象组合,无需区分不同的层次。
  • 灵活性: 组合模式支持动态地添加、移除对象,以及改变对象的层次结构,使得系统更具灵活性。
  • 代码重用: 可以通过递归地使用相同的方法来处理整个对象树,从而实现代码的重用。
  • 层次关系: 可以清晰地表示对象之间的层次关系,帮助理解问题领域的结构。

缺点:

  • 复杂性: 当对象结构过于复杂时,组合模式可能会导致系统的复杂性增加。
  • 不适用于所有情况: 并不是所有问题都适合使用组合模式,可能会导致过度抽象和不必要的复杂性。
基于Java的代码示例:
interface FileSystemNode {
    String getName();
    void print();
}
class File implements FileSystemNode {
    private String name;

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void print() {
        System.out.println("File: " + name);
    }
}

class Folder implements FileSystemNode {
    private String name;
    private List<FileSystemNode> children = new ArrayList<>();

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

    public void add(FileSystemNode node) {
        children.add(node);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void print() {
        System.out.println("Folder: " + name);
        for (FileSystemNode child : children) {
            child.print();
        }
    }
}
public class CompositePatternExample {
    public static void main(String[] args) {
        Folder root = new Folder("Root");
        Folder documents = new Folder("Documents");
        Folder pictures = new Folder("Pictures");
        
        // 使用组合模式来构建文件系统的层次结构
        File resume = new File("Resume.docx");
        File catPicture = new File("Cat.jpg");
        
        root.add(documents);
        root.add(pictures);
        
        documents.add(resume);
        pictures.add(catPicture);
        
        root.print();
    }
}

16. 观察者模式

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

应用场景:

  • 发布-订阅模型: 当你希望一个对象的状态变化能够通知多个其他对象,以便它们可以根据状态变化采取相应的行动时,观察者模式非常适用。
  • 事件处理: 在事件驱动的编程中,观察者模式常被用于处理事件的触发和响应,例如 GUI 应用程序中的事件处理。
  • 解耦: 当两个或多个类之间的耦合性较高,而你希望降低它们之间的依赖关系时,观察者模式可以实现解耦。
  • 动态变化: 当一个对象的状态可能在运行时发生变化,且其他对象需要实时得知状态变化时,观察者模式很有用。

优点:

  • 松耦合: 观察者模式将主题对象与观察者对象解耦,使得它们可以独立演化,减少了对象之间的依赖性。
  • 动态注册: 可以在运行时动态地将新的观察者注册到主题对象中,而不需要修改主题类的代码。
  • 可扩展性: 可以方便地添加新的观察者,不影响已有的观察者。
  • 实时性: 主题对象状态变化后,观察者会实时得到通知,可以迅速响应变化。

缺点:

  • 可能引起性能问题: 当观察者较多且主题频繁发生变化时,可能会引起性能问题。
  • 意外通知: 如果主题对象通知观察者的过程中发生异常,可能会导致未预料的错误。
  • 顺序问题: 观察者的通知顺序可能对于观察者的响应产生影响,需要注意管理通知顺序。
基于Java的代码示例:
interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

interface Observer {
    void update(float temperature, float humidity, float pressure);
}
import java.util.ArrayList;

class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

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

    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

class StatisticsDisplay implements Observer {
    private float temperatureSum;
    private int numReadings;

    @Override
    public void update(float temperature, float humidity, float pressure) {
        temperatureSum += temperature;
        numReadings++;
        display();
    }

    public void display() {
        System.out.println("Average temperature: " + (temperatureSum / numReadings) + "F degrees");
    }
}
public class ObserverPatternExample {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay();

        weatherData.addObserver(currentConditionsDisplay);
        weatherData.addObserver(statisticsDisplay);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
    }
}

17. 门面模式(外观模式)

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

应用场景:

  • 简化接口: 当一个复杂子系统包含多个类,而客户端需要调用多个不同的类来完成某个操作时,门面模式可以提供一个统一的接口,将这些操作进行封装,使客户端调用更简单。
  • 解耦: 当客户端与子系统之间存在较高的耦合性时,引入门面模式可以减少客户端与子系统之间的直接交互,从而降低耦合性。
  • 包装遗留代码: 当存在大量遗留代码或复杂的第三方库时,可以使用门面模式将这些复杂性封装起来,提供更简单的接口给客户端使用。
  • 系统接口: 当需要为一个复杂的系统提供一个更高级别的接口,以便其他系统与之交互时,门面模式可以帮助定义系统的外部接口。

优点:

  • 简化客户端代码: 门面模式提供了一个简化的接口,使客户端代码更加清晰和简单,不需要了解复杂的子系统结构。
  • 解耦: 门面模式将客户端与子系统解耦,使得子系统的变化不会影响客户端。
  • 提高可维护性: 通过将子系统的复杂性隐藏在门面后面,可以降低系统的复杂性,从而提高可维护性。
  • 易于扩展: 可以在不影响客户端的情况下扩展和修改子系统。

缺点:

  • 违反开闭原则: 如果需要新增或修改子系统的功能,可能需要修改门面类的代码,违反了开闭原则。
  • 可能引入不必要的复杂性: 如果门面类本身过于复杂,可能会引入不必要的复杂性,甚至比直接调用子系统更复杂。
  • 限制灵活性: 门面模式隐藏了子系统的细节,有时可能会限制客户端直接访问子系统的灵活性。
基于Java的代码示例:
class CPU {
    public void start() {
        System.out.println("CPU is starting...");
    }

    public void shutdown() {
        System.out.println("CPU is shutting down...");
    }
}

class Memory {
    public void start() {
        System.out.println("Memory is starting...");
    }

    public void shutdown() {
        System.out.println("Memory is shutting down...");
    }
}

class HardDisk {
    public void start() {
        System.out.println("Hard Disk is starting...");
    }

    public void shutdown() {
        System.out.println("Hard Disk is shutting down...");
    }
}
class ComputerFacade {
    private CPU cpu;
    private Memory memory;
    private HardDisk hardDisk;

    public ComputerFacade() {
        cpu = new CPU();
        memory = new Memory();
        hardDisk = new HardDisk();
    }

    public void start() {
        System.out.println("Computer is starting...");
        cpu.start();
        memory.start();
        hardDisk.start();
        System.out.println("Computer is started!");
    }

    public void shutdown() {
        System.out.println("Computer is shutting down...");
        cpu.shutdown();
        memory.shutdown();
        hardDisk.shutdown();
        System.out.println("Computer is shut down!");
    }
}
public class FacadePatternExample {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
        System.out.println("====================");
        computer.shutdown();
    }
}

18. 备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

应用场景:

  • 撤销与恢复功能: 当需要实现撤销与恢复功能时,备忘录模式可以帮助在不破坏对象封装的情况下保存和恢复对象的状态。
  • 版本控制: 在需要跟踪和管理对象历史状态的情况下,备忘录模式可以用来记录对象的不同版本状态。
  • 快照: 当需要在某个时间点保存一个对象的快照,以便后续可以回滚到该状态时,备忘录模式非常适用。
  • 编辑器应用: 在编辑器中,例如文本编辑器或图形编辑器,备忘录模式可以用来实现撤销、恢复、历史记录等功能。

优点:

  • 封装性: 备忘录模式允许对象在不暴露其内部状态的情况下进行状态保存和恢复。
  • 撤销与恢复: 提供了一种有效的方式来实现撤销与恢复功能,保证用户界面的友好性。
  • 历史记录: 可以方便地跟踪和管理对象状态的历史记录,用于版本控制等场景。

缺点:

  • 资源消耗: 如果备忘录的状态较大或频繁保存,可能会占用较多的内存和存储资源。
  • 性能问题: 在频繁保存和恢复状态的情况下,可能会影响性能。
  • 复杂性增加: 在某些情况下,备忘录模式可能会引入复杂性,特别是在需要管理多个备忘录对象的情况下。
基于Java的代码示例:
class Memento {
    private String content;

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

    public String getContent() {
        return content;
    }
}
class Editor {
    private String content;

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

    public String getContent() {
        return content;
    }

    public Memento save() {
        return new Memento(content);
    }

    public void restore(Memento memento) {
        content = memento.getContent();
    }
}
public class MementoPatternExample {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.setContent("Hello, world!");

        Memento savedState = editor.save();

        editor.setContent("Updated content");
		
        // 使用备忘录来恢复内容
        editor.restore(savedState);

        System.out.println(editor.getContent()); 
    }
}

19. 访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

应用场景:

  • 对象结构稳定: 当对象结构相对稳定,但需要在不同对象上执行不同操作时,访问者模式可以有效地将操作与对象解耦。
  • 新增操作频繁: 当需要在对象结构中频繁添加新的操作,但不希望每个对象类都包含这些操作时,访问者模式可以将这些操作添加到访问者类中。
  • 多个不相关操作: 当存在多个不相关的操作,而这些操作的组合会导致类的爆炸性增长时,可以使用访问者模式将这些操作分开。
  • 对象集合操作: 当需要对一个对象集合中的每个对象执行不同的操作时,访问者模式可以避免在集合类中添加过多的操作方法。

优点:

  • 分离关注点: 访问者模式将对象结构与操作分离,使得可以独立添加新的操作,而不需要修改现有的对象类。
  • 新增操作简单: 添加新的操作只需要添加一个新的访问者类,不需要修改已有的代码。
  • 易扩展性: 可以通过增加新的访问者类来扩展系统的功能,而无需修改已有的代码。

缺点:

  • 违反封装原则: 在访问者模式中,访问者类需要访问对象的内部状态,可能会违反对象的封装原则。
  • 增加复杂性: 引入访问者模式会增加类的数量,可能会增加系统的复杂性。
  • 对象结构改变困难: 如果对象结构经常发生变化,可能需要同时修改访问者类和对象结构类,增加了维护的复杂性。
  • 违背了依赖倒置转原则:访问者依赖的是具体元素,而不是抽象元素,这破坏了依赖倒置原则,特别是在面向对象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
基于Java的代码示例:
interface Item {
    void accept(Visitor visitor);
}
class ElectronicItem implements Item {
    private double price;

    public ElectronicItem(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
class FoodItem implements Item {
    private double price;

    public FoodItem(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
interface Visitor {
    void visit(ElectronicItem item);
    void visit(FoodItem item);
}
class PriceCalculator implements Visitor {
    private double totalPrice = 0;

    public double getTotalPrice() {
        return totalPrice;
    }

    @Override
    public void visit(ElectronicItem item) {
        totalPrice += item.getPrice();
    }

    @Override
    public void visit(FoodItem item) {
        totalPrice += item.getPrice();
    }
}
public class VisitorPatternExample {
    public static void main(String[] args) {
        Item electronic = new ElectronicItem(100);
        Item food = new FoodItem(50);

        PriceCalculator calculator = new PriceCalculator();
        electronic.accept(calculator);
        food.accept(calculator);

        System.out.println("Total price: " + calculator.getTotalPrice());
    }
}

20. 状态模式

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

应用场景:

  • 对象具有多个状态: 当对象具有多个状态,且在不同状态下行为不同时,状态模式可以帮助消除大量的条件判断语句。
  • 状态转换固定: 当对象的状态转换是固定的,且状态之间的转换较为复杂时,状态模式可以提供一种清晰的方式来管理状态转换。
  • 避免条件嵌套: 当存在多层嵌套的条件判断,且代码难以维护时,可以使用状态模式将每个状态的逻辑独立出来。
  • 上下文行为依赖于状态: 当对象的行为取决于它的状态,而不是特定的属性值时,状态模式能够更好地组织和管理对象的行为。

优点:

  • 封装状态: 状态模式将对象的不同状态封装成不同的状态类,提高了代码的可维护性和可读性。
  • 消除条件判断: 状态模式通过将状态转换的逻辑从主流程中分离出来,减少了大量的条件判断语句。
  • 易于扩展: 可以方便地添加新的状态类和对应的行为,扩展性良好。

缺点:

  • 类数量增加: 引入状态模式可能会增加类的数量,特别是在状态较多的情况下。
  • 状态切换逻辑: 状态切换逻辑可能会分散在不同的状态类中,使得整体逻辑变得分散。
  • 增加复杂性: 在简单情况下使用状态模式可能会增加代码复杂性,不适合所有场景。
基于Java的代码示例:
interface ElevatorState {
    void open();
    void close();
    void moveUp();
    void moveDown();
}
class OpenState implements ElevatorState {
    @Override
    public void open() {
        System.out.println("The door is already open.");
    }

    @Override
    public void close() {
        System.out.println("Closing the door.");
    }

    @Override
    public void moveUp() {
        System.out.println("Cannot move up while the door is open.");
    }

    @Override
    public void moveDown() {
        System.out.println("Cannot move down while the door is open.");
    }
}
class CloseState implements ElevatorState {
    @Override
    public void open() {
        System.out.println("Opening the door.");
    }

    @Override
    public void close() {
        System.out.println("The door is already closed.");
    }

    @Override
    public void moveUp() {
        System.out.println("Moving up.");
    }

    @Override
    public void moveDown() {
        System.out.println("Moving down.");
    }
}
class Elevator {
    private ElevatorState state;

    public Elevator() {
        state = new CloseState();
    }

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

    public void open() {
        state.open();
    }

    public void close() {
        state.close();
    }

    public void moveUp() {
        state.moveUp();
    }

    public void moveDown() {
        state.moveDown();
    }
}
public class StatePatternExample {
    public static void main(String[] args) {
        Elevator elevator = new Elevator();

        elevator.open();
        elevator.moveUp();

        elevator.close();
        elevator.moveUp();

        elevator.moveDown();
        elevator.open();
    }
}

21. 解释器模式

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

应用场景:

  • DSL(领域特定语言): 当需要为特定领域定义一个简单的语言或脚本时,解释器模式可以帮助解析和执行这个语言。
  • 正则表达式: 解释器模式可以用于解析和匹配正则表达式,从而进行字符串匹配和处理。
  • 编译器和解释器: 在编译器和解释器中,解释器模式用于解析源代码,并将其转换为中间表示或机器代码。
  • XML 解析: 解释器模式可以用于解析和处理 XML 文档,提取其中的数据和信息。

优点:

  • 扩展性: 可以通过添加新的解释器来扩展语言的语法和功能,而无需修改现有代码。
  • 灵活性: 解释器模式使得语法树的构建和解释分离,使系统更具灵活性和可维护性。

缺点:

  • 复杂性: 解释器模式可能会导致大量的解释器类,特别是对于复杂的文法规则,会增加系统的复杂性。
  • 性能问题: 解释器模式通常比编译器模式执行速度较慢,因为它在运行时进行解释和分析。
  • 难以维护: 随着语法规则的增加,解释器模式的维护可能变得困难,因为每个解释器都需要独立地进行维护。
基于Java的代码示例:
interface Expression {
    int interpret(Context context);
}
class AddExpression implements Expression {
    private Expression left;
    private Expression right;

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

    @Override
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }
}

class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

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

    @Override
    public int interpret(Context context) {
        return left.interpret(context) - right.interpret(context);
    }
}

class MultiplyExpression implements Expression {
    private Expression left;
    private Expression right;

    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        return left.interpret(context) * right.interpret(context);
    }
}

class DivideExpression implements Expression {
    private Expression left;
    private Expression right;

    public DivideExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        int divisor = right.interpret(context);
        if (divisor != 0) {
            return left.interpret(context) / divisor;
        } else {
            throw new ArithmeticException("Division by zero");
        }
    }
}
class Context {
    private Map<String, Integer> variables = new HashMap<>();

    public void setVariable(String name, int value) {
        variables.put(name, value);
    }

    public int getVariableValue(String name) {
        return variables.getOrDefault(name, 0);
    }
}
public class InterpreterPatternExample {
    public static void main(String[] args) {
        Context context = new Context();
        context.setVariable("x", 5);
        context.setVariable("y", 3);

        Expression expression = new AddExpression(
            new MultiplyExpression(new DivideExpression(new SubtractExpression(new VariableExpression("x"), new VariableExpression("y")), new ConstantExpression(2)), new ConstantExpression(3)),
            new VariableExpression("y")
        );

        int result = expression.interpret(context);
        System.out.println("Result: " + result);
    }
}

22. 享元模式

使用共享对象可有效地支持大量的细粒度的对象。

应用场景:

  • 对象数量大: 当系统中存在大量相似的对象,且这些对象的状态可以分为内部状态和外部状态时,可以使用享元模式来共享内部状态,减少内存消耗。
  • 内部状态和外部状态: 内部状态是对象共享的一部分,外部状态是根据具体场景变化的一部分。享元模式将内部状态共享,而外部状态通过参数传递。
  • 缓存和池: 享元模式常用于实现缓存和对象池,以便重复利用对象,降低对象的创建和销毁成本。

优点:

  • 减少内存消耗: 通过共享内部状态,减少了相似对象的内存占用,提高了系统的性能和资源利用率。
  • 提高性能: 由于对象共享,创建对象的成本降低,从而提高了系统的性能。
  • 可维护性: 通过分离内部状态和外部状态,使得系统更易于扩展和维护。

缺点:

  • 复杂性增加: 引入享元模式可能会增加代码的复杂性,需要管理共享对象的状态和外部状态。
  • 不适用于所有情况: 享元模式适用于存在大量相似对象的情况,如果对象的状态差异较大,使用享元模式可能效果有限。
  • 线程安全性: 如果多个线程同时修改共享对象的状态,需要考虑线程安全性问题。
基于Java的代码示例:
interface Character {
    void display();
}
class ConcreteCharacter implements Character {
    private char character;

    public ConcreteCharacter(char character) {
        this.character = character;
    }

    public void display() {
        System.out.println("Character: " + character);
    }
}

class Color {
    private String color;

    public Color(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}

class Font {
    private String font;

    public Font(String font) {
        this.font = font;
    }

    public String getFont() {
        return font;
    }
}
class CharacterFactory {
    private Map<Character, ConcreteCharacter> characters = new HashMap<>();

    public ConcreteCharacter getCharacter(char character) {
        if (!characters.containsKey(character)) {
            characters.put(character, new ConcreteCharacter(character));
        }
        return characters.get(character);
    }
}
public class FlyweightPatternExample {
    public static void main(String[] args) {
        CharacterFactory characterFactory = new CharacterFactory();

        ConcreteCharacter a = characterFactory.getCharacter('A');
        ConcreteCharacter b = characterFactory.getCharacter('B');
        ConcreteCharacter c = characterFactory.getCharacter('A');

        a.display();
        b.display();
        c.display();

        System.out.println(a == c);
    }
}

23. 桥梁模式

将抽象和实现解耦,使得两者可以独立地变化。

应用场景:

  • 抽象与实现的分离: 当一个类有多个变化的维度(例如,操作系统和图形界面),可以使用桥梁模式将抽象与实现分离,使它们可以独立演化。
  • 平台无关性: 当希望在不同平台上共享一些通用的代码,同时又允许平台特定的实现,可以使用桥梁模式来实现这种分离。
  • 系统分层: 当需要将一个系统分成多个层次,且每个层次可以独立变化时,桥梁模式能够帮助管理各个层次之间的关系。

优点:

  • 分离抽象和实现: 桥梁模式能够将抽象和实现部分分离,降低了系统的复杂性,提高了可维护性和可扩展性。
  • 适应变化: 桥梁模式允许抽象和实现部分独立变化,使系统更能适应未来的需求变化。
  • 可扩展性: 桥梁模式支持在两个维度上进行扩展,而不需要修改已有代码。

缺点:

  • 增加复杂性: 引入桥梁模式可能会增加类的数量,增加了系统的复杂性。
  • 设计难度: 合理定义抽象和实现接口,以及它们之间的关系,可能需要一定的设计和抽象能力。
基于Java的代码示例:
interface Shape {
    void draw();
}
interface Color {
    void applyColor();
}
class Circle implements Shape {
    private Color color;

    public Circle(Color color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.print("Drawing a circle with ");
        color.applyColor();
    }
}
class Rectangle implements Shape {
    private Color color;

    public Rectangle(Color color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.print("Drawing a rectangle with ");
        color.applyColor();
    }
}
class RedColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("red color");
    }
}
class BlueColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("blue color");
    }
}
public class BridgePatternExample {
    public static void main(String[] args) {
        Shape redCircle = new Circle(new RedColor());
        Shape blueRectangle = new Rectangle(new BlueColor());
		
        // 使用桥梁模式来绘制不同类型的图形以及不同颜色的图形
        redCircle.draw();
        blueRectangle.draw();
    }
}

三、设计模式PK


一、创建类模式PK

1. 工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象。

工厂方法模式和建造者模式都属于对象创建类模式,都用来创建类的对象。

区别:
  • 意图不同

在工厂方法模式里,我们关注的是一个产品整体,无须关心产品的各部分是如何创建出来的;但在建造者模式中,一个具体产品的产生是依赖各个部件的产生以及装配顺序,它关注的是“由零件一步一步地组装出产品对象”。
简单地说,工厂模式是一个对象创建的粗线条应用,建造者模式则是通过细线条勾勒出一个复杂对象,关注的是产品组成部分的创建过程。

  • 产品的复杂度不同

工厂方法模式创建的产品一般都是单一性质产品,如成年超人,都是一个模样,而建造者模式创建的则是一个复合产品,它由各个部件复合而成,部件不同产品对象当然不同。这不是说工厂方法模式创建的对象简单,而是指它们的粒度大小不同。一般来说,工厂方法模式的对象粒度比较粗,建造者模式的产品对象粒度比较细。

如何选择:

如果需要详细关注一个产品部件的生产、安装步骤,则选择建造者,否则选择工厂方法模式。

2. 抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

区别:
  • 尺度不同

抽象工厂模式比建造者模式的尺度要大,它关注产品整体,而建造者模式关注构建过程,因此建造者模式可以很容易地构建出一个崭新的产品。

如何选择:

如果希望屏蔽对象的创建过程,只提供一个封装良好的对象,则可以选择抽象工厂方法模式。而建造者模式可以用在构件的装配方面,如通过装配不同的组件或者相同组件的不同顺序,可以产生出一个新的对象,它可以产生一个非常灵活的架构,方便地扩展和维护系统。


二、结构类模式PK

1. 代理模式VS装饰模式

装饰模式就是代理模式的一个特殊应用,两者的共同点是都具有相同的接口,不同点则是代理模式着重对代理过程的控制,而装饰模式则是对类的功能进行加强或减弱,它着重类的功能变化。

区别:
  • 代理模式是把当前的行为或功能委托给其他对象执行,代理类负责接口限定:是否可以调用真实角色,以及是否对发送到真实角色的消息进行变形处理,它不对被主题角色(也就是被代理类)的功能做任何处理,保证原汁原味的调用。代理模式使用到极致开发就是AOP,它就是使用了代理和反射的技术。
  • 装饰模式是在要保证接口不变的情况下加强类的功能,它保证的是被修饰的对象功能比原始对象丰富(当然,也可以减弱),但不做准入条件判断和准入参数过滤,如是否可以执行类的功能,过滤输入参数是否合规等,这不是装饰模式关心的。
怎么选择:

如果需要保证接口不变的情况下加强类的功能选择装饰模式,如果只是被代理并不做任何处理使用代理模式。

2. 装饰模式VS适配器模式

装饰模式和适配器模式的功能都是包装作用,都是通过委托方式实现其功能。
不同点是:装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此它的本质还是非相同接口的对象。

区别:
  • 意图不同

装饰模式的意图是加强对象的功能(当然了,减弱对象的功能也是可能存在的);而适配器模式关注的则是转化,它的主要意图是两个不同对象之间的转化。

  • 施与对象不同

装饰模式装饰的对象必须是自己的同宗,也就是相同的接口或父类,只要在具有相同的属性和行为的情况下,才能比较行为是增加还是减弱;适配器模式则必须是两个不同的对象,因为它着重于转换,只有两个不同的对象才有转换的必要,如果是相同对象还转换什么?!

  • 场景不同

装饰模式在任何时候都可以使用,只要是想增强类的功能,而适配器模式则是一个补救模式,一般出现在系统成熟或已经构建完毕的项目中,作为一个紧急处理手段采用。

  • 扩展性不同

装饰模式很容易扩展!今天不用这个修饰,好,去掉;明天想再使用,好,加上。这都没有问题。而且装饰类可以继续扩展下去;但是适配器模式就不同了,它在两个不同对象之间架起了一座沟通的桥梁,建立容易,去掉就比较困难了,需要从系统整体考虑是否能够撤销。

怎么选择:

如果需要加强或者减弱对象的功能使用装饰模式,如果需要适配、转化使用适配器模式。


三、行为类模式PK

1. 命令模式VS策略模式

命令模式和策略模式的类图确实很相似,只是命令模式多了一个接收者(Receiver)角色。它们虽然同为行为类模式,但是两者的区别还是很明显的。策略模式的意图是封装算法,它认为“算法”已经是一个完整的、不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户;而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立而不相互影响。

区别:
  • 关注点不同

策略模式关注的是算法替换的问题,一个新的算法投产,旧算法退休,或者提供多种算法由调用者自己选择使用,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其可以自由切换。
命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它需要首先解决的,解耦的要求就是把请求的内容封装为一个一个的命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等。

  • 角色功能不同

策略模式中的具体算法是负责一个完整算法逻辑,它是不可再拆分的原子业务单元,一旦变更就是对算法整体的变更。
而命令模式则不同,它关注命令的实现,也就是功能的实现。例如我们在分支中也提到接收者的变更问题,它只影响到命令族的变更,对请求者没有任何影响,从这方面来说,接收者对命令负责,而与请求者无关。命令模式中的接收者只要符合六大设计原则,完全不用关心它是否完成了一个具体逻辑,它的影响范围也仅仅是抽象命令和具体命令,对它的修改不会扩散到模式外的模块。当然,如果在命令模式中需要指定接收者,则需要考虑接收者的变化和封装,例如一个老顾客每次吃饭都点同一个厨师的饭菜,那就必须考虑接收者的抽象化问题。

怎么选择:

策略模式适用于算法要求变换的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令多撤销的场景。

2. 策略模式VS状态模式

策略模式封装的是不同的算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。这两种模式虽然都有变换的行为,但是两者的目标却是不同的。

区别:
  • 环境角色的职责不同

策略模式只是一个委托作用,负责算法的替换;而状态模式不仅仅是委托行为,它还具有其他的功能。

  • 解决问题的重点不同

策略模式旨在解决内部算法如何改变的问题,也就是将内部算法的改变对外界的影响降低到最小,它保证的是算法可以自由地切换;而状态模式旨在解决内在状态的改变而引起行为改变的问题,它的出发点是事物的状态,封装状态而暴露行为,一个对象的状态改变,从外界来看就好像是行为改变。

  • 解决问题的方法不同

策略模式只是确保算法可以自由切换,但是什么时候用什么算法它决定不了;而状态模式对外暴露的是行为,状态的变化一般是由环境角色和具体状态共同完成的,也就是说状态模式封装了状态的变化而暴露了不同的行为或行为结果。

  • 复杂度不同

通常策略模式比较简单,这里的简单指的是结构简单,扩展比较容易,而且代码也容易阅读。当然,一个具体的算法也可以写得很复杂,只有具备很高深的数学、物理等知识的人才可以看懂,这也是允许的,我们只是说从设计模式的角度来分析,它是很容易被看懂的。而状态模式则通常比较复杂,因为它要从两个角色看到一个对象状态和行为的改变,也就是说它封装的是变化,要知道变化是无穷尽的,因此相对来说状态模式通常都比较复杂,涉及面很多,虽然也很容易扩展,但是一般不会进行大规模的扩张和修正。

怎么选择:

如果你的主要目标是在不同情况下选择不同的算法或策略,那么策略模式可能更适合。这种情况下,你可以创建一系列算法类,并将它们封装成策略对象,然后在运行时动态地选择使用哪个策略。
如果你的主要目标是根据对象的内部状态来改变其行为,那么状态模式可能更适合。这种情况下,你可以定义不同的状态类,并在对象内部维护一个当前状态的引用,对象会根据当前状态来执行不同的行为。


四、混合PK

1. 策略模式VS桥梁模式

策略模式和桥梁模式是面向对象设计的两种模式。它们共同关注解耦和灵活性,强调抽象与实现分离。策略模式适用于根据情况选择不同策略,实现不同行为;桥梁模式则用于分离多维抽象和实现,提高可维护性。

区别:
  • 策略模式:主要关注的是对象的行为,在不同策略之间进行切换。
  • 桥梁模式:主要关注的是对象的结构和组合,以及如何将不同的抽象和实现进行连接。
怎么选择:

只要好用,能够解决问题就成,“不管黑猫白猫,抓住老鼠的就是好猫”

2. 门面模式VS中介者模式

门面模式为复杂的子系统提供一个统一的访问界面,它定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模块深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对象来封装一系列同事对象的交互行为,它使各对象之间不再显式地引用,从而使其耦合松散,建立一个可扩展的应用架构。

区别:
  • 知晓状态不同

对门面模式来说,子系统不知道有门面存在,而对中介者来说,每个同事类都知道中介者存在,因为要依靠中介者调和同事之间的关系,它们对中介者非常了解。

  • 封装程度不同

门面模式是一种简单的封装,所有的请求处理都委托给子系统完成,而中介者模式则需要有一个中心,由中心协调同事类完成,并且中心本身也完成部分业务,它属于更进一步的业务功能封装。

怎么选择:

门面模式只是增加了一个门面,它对子系统来说没有增加任何的功能,子系统若脱离门面模式是完全可以独立运行的。而中介者模式则增加了业务功能,它把各个同事类中的原有耦合关系移植到了中介者,同事类不可能脱离中介者而独立存在,除非是想增加系统的复杂性和降低扩展性。

所以需要增加业务功能的使用中介者模式,只是提供一个外观简化调用的、不增加额外功能的使用门面(外观)模式。

五、包装模式群PK

包装模式是大家在系统设计中经常会用到的模式,它们具有相似的特征:都是通过委托的方式对一个对象或一系列对象(例如门面模式)施行包装,有了包装,设计的系统才更加灵活、稳定,并且极具扩展性。从实现的角度来看,它们都是代理的一种具体表现形式。

  • 代理模式主要用在不希望展示一个对象内部细节的场景中,比如一个远程服务不需要把远程连接的所有细节都暴露给外部模块,通过增加一个代理类,可以很轻松地实现被代理类的功能封装。此外,代理模式还可以用在一个对象的访问需要限制的场景中,比如AOP。

  • 装饰模式是一种特殊的代理模式,它倡导的是在不改变接口的前提下为对象增强功能,或者动态添加额外职责。就扩展性而言,它比子类更加灵活,例如在一个已经运行的项目中,可以很轻松地通过增加装饰类来扩展系统的功能。

  • 适配器模式的主要意图是接口转换,把一个对象的接口转换成系统希望的另外一个接口,这里的系统指的不仅仅是一个应用,也可能是某个环境,比如通过接口转换可以屏蔽外界接口,以免外界接口深入系统内部,从而提高系统的稳定性和可靠性。

  • 桥梁模式是在抽象层产生耦合,解决的是自行扩展的问题,它可以使两个有耦合关系的对象互不影响地扩展,比如对于使用笔画图这样的需求,可以采用桥梁模式设计成用什么笔(铅笔、毛笔)画什么图(圆形、方形)的方案,至于以后需求的变更,如增加笔的类型,增加图形等,对该设计来说是小菜一碟。

  • 门面模式是一个粗粒度的封装,它提供一个方便访问子系统的接口,不具有任何的业务逻辑,仅仅是一个访问复杂系统的快速通道,没有它,子系统照样运行,有了它,只是更方便访问而已。



四、扩展

一、 MVC框架

mvc

mvc2

浏览器通过HTTP协议发出数据请求①,由控制器接收请求,通过路径②委托给数据模型处理,模型通过与逻辑层和持久层的交互(路径③④),把处理结果反馈给控制器(路径⑤),控制器根据结果组装视图(路径⑥⑦),并最终反馈给浏览器可以接受的HTML数据(路径⑧)。

MVC框架的优点:
  • 高重用性

一个模型可以有多个视图,比如同样是一批数据,可以是柱状展示,也可以是条形展示,还可以是波形展示。同样,多个模型也可以共享一个视图,同样是一个登录界面,不同用户看到的菜单数量(模型中的数据)不同,或者不同业务权限级别的用户在同一个视图中展示。

  • 低耦合

因为模型和视图分离,两者没有耦合关系,所以可以独立地扩展和修改而不会产生相互影响。

  • 快速开发和便捷部署

模型和视图分离,可以使各个开发人员自由发挥,做视图的人员和开发模型的人员可以制订自己的计划,然后在控制器的协作下实现完整的应用逻辑。

MVC的系统架构:

mvc3

  • 核心控制器:MVC框架的入口,负责接收和反馈HTTP请求。
  • 过滤器:Servlet容器内的过滤器,实现对数据的过滤处理。由于它是容器内的,因此必须依靠容器才能运行,它是容器的一项功能,与容器息息相关。
  • 拦截器:对进出模型的数据进行过滤,它不依赖系统容器,只过滤MVC框架内的业务数据。
  • 模型管理器:提供一个模型框架,该框架内的所有业务操作都应该是无状态的,不关心容器对象,例如Session、线程池等。
  • 视图管理器:管理所有的视图,例如提供多语言的视图等。
  • 辅助工具:它其实就是一大堆的辅助管理工具,比如文件管理、对象管理等。
MVC框架中用到的设计模式:
  • 工厂方法模式:通过工厂方法模式把所有的拦截器链实现出来,方便在系统初始化时直接处理。
  • 单例模式:默认配置都是单例模式,在一般的应用中单例已经足够了,在复杂情况下可以使用享元模式提供应用性能,减少单例模式的性能隐患。
  • 责任链模式:建立拦截器链以及过滤器链,实现任务的链条化处理。
  • 迭代器模式:非常方便地遍历拦截器链内的拦截器,而不用再自己写遍历拦截器链的方法。
  • 中介者模式:以核心控制器为核心,其他同事类都负责为核心控制器“打工”,保证核心控制器瘦小、稳定。
  • 观察者模式:配置文件修改时,不用重启应用可以即刻生效,提供使用者的体验。
  • 桥梁模式:使不同的视图配合不同的语言文件,为终端用户展示不同的界面。
  • 策略模式:对XML文件的检查可以使用两种不同的策略,而且可以在测试机和开发机中使用不同的检查策略,方便系统间自由切换。
  • 访问者模式:在解析XML文件时,使用访问者非常方便地访问到需要的对象。
  • 适配器模式:把一个开发者不熟悉的对象转换为熟悉的对象,避免工具或框架对开发者的影响。
  • 门面模式:分发器负责所有的分发工作,它提供了调用的唯一入口,避免外部模块深入到模型模块内部。
  • 代理模式:大量使用动态代理,确保了框架的智能化。

二、 新模式

1. 规格模式

规格模式(Specification Pattern)是一种用于软件设计的模式,它主要用于在领域驱动设计(DDD)中,帮助描述对象的业务规则和约束条件。规格模式的目标是将业务规则从对象的实现中分离出来,从而提高可维护性和可重用性。

在规格模式中,一个规格(Specification)是一个表示业务规则的对象,它可以用来判断对象是否满足一定的条件。规格通常被用于进行对象的筛选、过滤或验证。

规格模式包含以下几个主要元素:

  • 规格接口(Specification Interface):规定了规格对象必须实现的方法,通常包括判断对象是否满足规则的方法。
  • 具体规格(Concrete Specifications):实现了规格接口的具体规格对象,定义了具体的业务规则和判断条件。
  • 组合规格(Composite Specifications):用于将多个规格组合在一起,可以通过逻辑操作(如与、或、非)来构建更复杂的规格。
  • 对象(Object):被规格判断的对象,通常是领域中的实体。

通过规格模式,可以将复杂的业务规则从对象的代码中抽离出来,使得对象的实现更加清晰和简洁。规格模式也有助于提高代码的可测试性,因为规格对象可以独立地进行单元测试,而不需要依赖具体的对象实现。它能够帮助分离业务规则与对象实现,提高代码的可维护性和可测试性。

基于Java的代码示例:
public interface CarSpecification {
    boolean isSatisfiedBy(Car car);
}
public class ElectricCarSpecification implements CarSpecification {
    @Override
    public boolean isSatisfiedBy(Car car) {
        return car.getFuelType() == FuelType.ELECTRIC;
    }
}

public class SUVSpecification implements CarSpecification {
    @Override
    public boolean isSatisfiedBy(Car car) {
        return car.getCarType() == CarType.SUV;
    }
}
public class Car {
    private String model;
    private FuelType fuelType;
    private CarType carType;
}

public enum FuelType {
    GASOLINE, DIESEL, ELECTRIC
}

public enum CarType {
    SEDAN, SUV, HATCHBACK
}
public class Main {
    public static void main(String[] args) {
        List<Car> cars = new ArrayList<>();
        cars.add(new Car("Model 3", FuelType.ELECTRIC, CarType.SEDAN));
        cars.add(new Car("CR-V", FuelType.GASOLINE, CarType.SUV));
        cars.add(new Car("Golf", FuelType.GASOLINE, CarType.HATCHBACK));

        CarSpecification electricCarSpec = new ElectricCarSpecification();
        CarSpecification suvSpec = new SUVSpecification();

		// 通过规格来筛选汽车
        List<Car> electricCars = filterCars(cars, electricCarSpec);
        List<Car> suvs = filterCars(cars, suvSpec);

        System.out.println("Electric cars:");
        electricCars.forEach(car -> System.out.println(car.getModel()));

        System.out.println("\nSUVs:");
        suvs.forEach(car -> System.out.println(car.getModel()));
    }

    private static List<Car> filterCars(List<Car> cars, CarSpecification spec) {
        List<Car> filteredCars = new ArrayList<>();
        for (Car car : cars) {
            if (spec.isSatisfiedBy(car)) {
                filteredCars.add(car);
            }
        }
        return filteredCars;
    }
}

2. 对象池模式

对象池模式,或者称为对象池服务,其意图是通过循环使用对象,减少资源在初始化和释放时的昂贵损耗。

应用场景:

  • 数据库连接池:在数据库操作中,频繁地创建和关闭连接会造成性能开销,通过连接池可以重用数据库连接,减少连接的创建和关闭次数。
  • 线程池:在多线程应用中,频繁地创建和销毁线程会造成资源和性能问题,通过线程池可以重用线程,降低系统开销。
  • 网络连接池:在网络通信中,频繁地创建和断开网络连接也会产生性能开销,通过连接池可以重用连接,提高网络通信效率。
  • 对象创建成本高:当对象的创建成本较高,例如需要进行复杂的初始化操作时,可以使用对象池来避免重复的创建。

优点:

  • 提高性能:通过重用已经创建的对象,避免了频繁的创建和销毁操作,从而减少了系统开销,提高了性能。
  • 节省资源:避免了过多的内存和资源消耗,特别在资源受限的环境中,对象池可以有效节省资源。
  • 减少延迟:对象池中已经初始化的对象可以立即提供给使用者,避免了因创建对象而产生的延迟。

缺点:

  • 复杂性增加:引入对象池会增加代码的复杂性,需要管理对象的创建、销毁以及对象的状态等。
  • 可能引起资源泄露:如果没有正确管理对象池,可能会导致资源泄露,即对象被占用而无法释放。
  • 不适用于所有情况:并不是所有的情况都适合使用对象池,特别是对象创建和销毁成本较低、对象使用不频繁的情况。
基于Java的代码示例:
public class ConnectionPool {
    private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/mydb";
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";
    private static final int INITIAL_POOL_SIZE = 5;

    private LinkedList<Connection> connectionPool = new LinkedList<>();

    public ConnectionPool() {
        initializeConnectionPool();
    }

    private void initializeConnectionPool() {
        for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
            try {
                Connection connection = createNewConnection();
                connectionPool.add(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private Connection createNewConnection() throws SQLException {
        return DriverManager.getConnection(DATABASE_URL, USERNAME, PASSWORD);
    }

    public synchronized Connection getConnection() {
        if (connectionPool.isEmpty()) {
            return null;
        }
        return connectionPool.removeFirst();
    }

    public synchronized void releaseConnection(Connection connection) {
        connectionPool.addLast(connection);
    }
}
public class Main {
    public static void main(String[] args) {
        ConnectionPool connectionPool = new ConnectionPool();

        Connection conn1 = connectionPool.getConnection();
        Connection conn2 = connectionPool.getConnection();

        if (conn1 != null && conn2 != null) {
            connectionPool.releaseConnection(conn1);
            connectionPool.releaseConnection(conn2);
        }
    }
}
3. 雇工模式

雇工模式也叫做仆人模式(Servant Design Pattern),是行为模式的一种,它为一组类提供通用的功能,而不需要类实现这些功能,它是命令模式的一种扩展。

应用场景:

  • 并行处理: 当需要处理大量任务时,可以将任务分割成小块,交给多个工人并行执行,从而加快整体处理速度。
  • 异步操作: 在需要执行一些耗时的操作时(如文件下载、数据处理等),可以将这些操作交给工人异步执行,避免阻塞主线程。
  • 任务调度: 在任务调度系统中,可以使用雇工模式来动态地分配任务给可用的工人,以优化任务的执行顺序和资源利用率。
  • 分布式系统: 在分布式系统中,各个节点可以充当工人,协同完成某些计算、数据处理等任务。

优点:

  • 并行性和吞吐量提升: 雇工模式允许多个工人并行地执行任务,从而加速整体处理速度,提高系统的吞吐量。
  • 资源利用优化: 可以更好地利用多核处理器和分布式系统中的计算资源,提高系统的资源利用率。
  • 响应性提升: 异步执行任务可以避免主线程的阻塞,从而提高系统的响应性,使用户体验更好。
  • 灵活性: 可以根据实际需求动态地增加或减少工人数量,以适应不同负载情况。

缺点:

  • 复杂性: 实现并管理多个工人可能增加系统的复杂性,需要考虑任务分配、同步、错误处理等问题。
  • 资源竞争: 多个工人并行执行时,可能出现资源竞争的问题,需要注意线程安全性和共享资源的管理。
  • 上下文切换: 在多线程环境下,工人之间的切换会引入一定的上下文切换开销,可能影响性能。
基于Java的代码示例:
class Task {
    private String name;

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

    public void execute() {
        System.out.println("任务 " + name + " 正在被线程 " + Thread.currentThread().getName() + " 执行");
    }
}
class Worker implements Runnable {
    private BlockingQueue<Task> taskQueue;

    public Worker(BlockingQueue<Task> taskQueue) {
        this.taskQueue = taskQueue;
    }

    @Override
    public void run() {
        while (true) {
            try {
             	// 从任务队列中取出任务
                Task task = taskQueue.take();
                // 执行任务
                task.execute(); 
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}
public class HireWorkerPattern {
    public static void main(String[] args) {
        int numWorkers = 3;
        BlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(10);

        Thread[] workers = new Thread[numWorkers];
        for (int i = 0; i < numWorkers; i++) {
            workers[i] = new Thread(new Worker(taskQueue));
            // 启动工人线程
            workers[i].start();
        }

        // 向任务队列添加任务
        for (int i = 1; i <= 10; i++) {
            taskQueue.offer(new Task("任务" + i));
        }

        // 通过中断停止工人线程
        for (Thread worker : workers) {
            worker.interrupt();
        }
    }
}

4. 黑板模式

黑板模式(Blackboard Design Pattern)是观察者模式的一个扩展,知名度并不高,但是我们使用的范围却非常广。黑板模式的意图是允许消息的读写同时进行,广泛地交互消息。

黑板模式

主要解决的问题是消息的生产者和消费者之间的耦合问题,它的核心是消息存储(黑板),它存储所有消息,并可以随时被读取。当消息生产者把消息写入到消息仓库后,其他消费者就可以从仓库中读取。当然,此时消息的写入者也可以变身为消息的阅读者,读写者在时间上解耦。对于这些消息,消费者只需要关注特定消息,不处理与自己不相关的消息,这一点通常通过过滤器来实现。

黑板模式的实现方法
  1. 数据库
  2. 消息队列作
5. 空对象模式

空对象模式(Null Object Pattern)是通过实现一个默认的无意义对象来避免null值出现,简单地说,就是为了避免在程序中出现null值判断而诞生的一种常用设计方法。

应用场景:

  • 避免空指针异常: 空对象模式适用于那些在多个地方需要对对象为空情况进行检查的场景,它可以减少在代码中进行空值检查的繁琐性。
  • 默认值设置: 当对象为空时,可以使用空对象模式返回一个默认值,从而避免在客户代码中设置默认值的逻辑。
  • 接口实现: 在某些情况下,为了避免实现某个接口而不得不创建一个空的实现类,可以使用空对象模式来提供一个默认的实现,避免了空实现的繁琐。
  • 代码简化: 在一些需要引入条件分支来处理 null 值的地方,使用空对象模式可以简化代码逻辑,提高可读性。

优点:

  • 避免空值检查: 空对象模式可以让客户代码无需频繁检查对象是否为 null,从而简化代码逻辑。
  • 减少异常: 通过返回一个空对象,可以减少因为 null 值引起的空指针异常。
  • 代码可读性: 使用空对象模式可以使代码更加清晰易懂,不再充斥着大量的空值检查。
  • 默认值处理: 可以为对象为空的情况提供一个默认的返回值,避免了客户代码中重复设置默认值。

缺点:

  • 内存占用: 使用空对象模式可能会占用额外的内存空间,尤其是当需要大量的空对象实例时。
  • 不适用复杂情况: 在某些情况下,使用空对象模式可能无法完全替代真实的 null 值,尤其是在需要精确处理 null 情况的场景。
基于Java的代码示例:
interface Animal {
    void makeSound();
}
class RealAnimal implements Animal {
    private String name;

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

    @Override
    public void makeSound() {
        System.out.println(name + " makes a sound.");
    }
}
class NullAnimal implements Animal {
    @Override
    public void makeSound() {
        // 什么都不做,表示空对象
    }
}

public class NullObjectPatternDemo {
    public static void main(String[] args) {
        Animal dog = new RealAnimal("Dog");
        Animal cat = new RealAnimal("Cat");
        Animal nullAnimal = new NullAnimal();
       
        // 输出:Dog makes a sound.
        dog.makeSound(); 
        // 输出:Cat makes a sound.
        cat.makeSound();
         // 什么都不输出 避免了空指针异常
        nullAnimal.makeSound();
    }
}

五、心得

《设计模式之禅》是一本经典的软件工程领域的书籍,它深入探讨了常见的设计模式及其在软件开发中的应用。在阅读这本书的过程中,我获得了许多关于优秀软件设计的启示与思考。

首先,这本书强调了设计模式在软件开发中的重要性。通过对23种不同的设计模式进行详细解释和案例分析,书中展示了如何在不同的场景中运用这些模式来解决特定的问题。这使我认识到,设计模式不仅可以提高代码的可维护性和可扩展性,还能够促使团队在开发过程中保持一致的思维方式和结构。

其次,书中的实例和案例对于理解抽象概念非常有帮助。每个设计模式都通过一个生动的示例来进行解释,这使得抽象的概念变得更加具体和易于理解。这种实例化的方法有助于读者更好地掌握每个模式的实际用途和实现方式。

另外,书中也强调了灵活性与变化的重要性。随着软件项目的发展,需求和技术都会不断变化,因此设计模式可以帮助开发人员更好地应对变化。通过将功能模块解耦并采用松耦合的架构,设计模式可以使系统更容易适应新的需求和技术栈。

最后,这本书鼓励了我在实际开发中的思考方式。它不仅仅是一本讲述设计模式的书,更是一本教会我如何思考和分析问题的指南。在阅读后,我开始更注重从整体上审视软件系统,思考如何将不同的模块组合起来,以实现更高效、可维护的解决方案。

综上所述,《设计模式之禅》是一本值得深入阅读和思考的书籍。通过学习其中的设计模式,我获得了关于软件设计的新的见解和启发,这些见解在我日常的开发工作中也带来了实际的应用和收益。

设计模式是软件工程领域的重要概念,然而要真正理解和应用设计模式,需要足够多的开发经验作为基础。尽管能够理解每个设计模式的用途和具体用法是一项关键的里程碑,但这并不意味着已经完全掌握了设计模式的精髓。真正的洞察力来自于在实际项目中的层层思考和应用。

设计模式不仅仅是一种代码的抽象,更是一种对于问题解决方法的思维方式。只有在实际的软件开发过程中,通过不断地将设计模式应用于不同的场景中,我们才能逐渐体会到它们的价值。通过将设计模式嵌入到项目中,我们能够更好地理解它们如何在不同情境下相互作用,以及如何在面对需求变更和技术挑战时保持系统的弹性和可维护性。

因此,虽然学习设计模式的基本概念是重要的,但真正的领悟和体验来自于在实践中的持续应用和反思。在项目中,从整体架构到具体代码实现,都需要反复思考如何恰当地运用不同的设计模式,以达到代码清晰、可扩展和易维护的目标。只有通过不断的实践,才能真正领略到设计模式的深远意义,将其融入到自己的开发工作。

设计模式的理解和应用是一个持续的过程,需要不断地思考和回顾。在软件开发的职业生涯中,我们逐渐能够领略设计模式的精髓,这需要时间和经验的积累。随着不断的实践和反思,我们能够更加深入地理解每个设计模式的适用场景以及它们的优势。通过不断地回头复习,我们可以发现之前可能未曾察觉的细微之处,从而在实际项目中更加恰当地运用各个设计模式。这个过程是一个不断成长和完善的过程,随着时间的推移,我们将能够在合适的时机选择并运用适当的设计模式,为软件开发增添更多的灵活性和可维护性。