设计模式宝典(持续更新中...)

73 阅读1小时+

前言

设计模式从面向对象的角度出发,通过对封装、继承、多态、组合等技术的反复使用提炼出可反复使用的面向对象设计技巧,不涉及具体的代码实现,而是提供解决问题的思路和结构,他的作用是让人们写出可复用和可维护性高的程序,但不可避免可能会增加程序的复杂度,鼓励将行为划分到各个对象内部,把对象划分为更小的粒度,有助于增强对象的可复用性,以及减少耦合性。

本文的主要目的是收集一些常见的设计模式,方便查询与学习,欢迎大家补充和纠正。

设计模式的分类

设计模式大体上分为三大类:

  1. 创建型模式:关注对象的创建过程,帮助我们创建对象而不会暴露创建逻辑,并且通过某种方式提供更好的对象创建管理。
    • 常见的创建型模式有:
      • 工厂模式(Factory Method)
      • 抽象工厂模式(Abstract Factory)
      • 单例模式(Singleton)
      • 建造者模式(Builder)
      • 原型模式(Prototype)
  2. 结构型模式:处理类或对象的组合,目的是为了获得更大的灵活性和复用性。它们帮助我们以某种方式组合对象,以便更好地组织代码结构。
    • 常见的结构型模式有:
      • 适配器模式(Adapter)
      • 桥接模式(Bridge)
      • 装饰者模式(Decorator)
      • 组合模式(Composite)
      • 外观模式(Facade)
      • 享元模式(Flyweight)
      • 代理模式(Proxy)
  3. 行为型模式:关注对象之间的通信,帮助定义对象之间的职责以及如何进行交互。它们旨在解决如何合理地分配责任和管理对象间复杂的通信。
    • 常见的行为型模式有:
      • 策略模式(Strategy)
      • 观察者模式(Observer)
      • 责任链模式(Chain of Responsibility)
      • 命令模式(Command)
      • 迭代器模式(Iterator)
      • 中介者模式(Mediator)
      • 备忘录模式(Memento)
      • 状态模式(State)
      • 模板方法模式(Template Method)
      • 访问者模式(Visitor)
      • 解释器模式(Interpreter)

1 单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,旨在确保一个类只有一个实例,并提供全局访问该实例的方式。它常用于需要控制资源的唯一性,避免重复实例化的场景,如日志记录器、数据库连接池、配置管理器等。

1.1 单例模式的核心思想

  • 唯一实例:单例模式确保一个类只能有一个实例,并且该实例是全局可访问的。
  • 全局访问点:通过类本身提供的静态方法,外部可以获取唯一的实例。

1.2 单例模式的实现步骤

  1. 构造函数私有化:防止类在外部通过构造函数创建实例。
  2. 提供静态方法获取实例:通过一个静态方法来返回该类的唯一实例,确保外部只能通过此方法获取对象。
  3. 保存唯一实例:使用静态成员变量保存类的唯一实例。

1.3 单例模式的实现示例(懒汉式)

懒汉式单例模式是在第一次调用 getInstance() 方法时才创建实例,延迟初始化。这是最常见的单例模式实现。

public class Singleton {
    // 静态私有变量保存唯一实例
    private static Singleton instance;

    // 私有构造函数,防止外部创建实例
    private Singleton() {}

    // 提供一个公共的静态方法获取实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

使用示例

public class Main {
    public static void main(String[] args) {
        // 获取单例对象
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        // 判断两个实例是否是同一个对象
        System.out.println(singleton1 == singleton2);  // 输出:true
    }
}

1.4 单例模式的优缺点

1.4.1 优点:

  1. 控制实例数量:保证全局只有一个实例,节省系统资源。
  2. 全局访问点:提供一个全局访问对象的方法,易于访问。
  3. 延迟加载(懒汉式):可以在第一次使用时再创建实例,减少内存开销。

1.4.2 缺点:

  1. 扩展性差:单例类不易扩展,因为它的实例是唯一的,不能轻易继承或修改其行为。
  2. 并发问题:在多线程环境中,懒汉式的单例可能需要加锁来保证线程安全,这会带来一定的性能开销。
  3. 隐藏依赖性:使用全局单例可能导致代码中的隐藏依赖,使得代码可测试性和可维护性下降。

1.5 适用场景

  • 需要控制资源的唯一性:如数据库连接池、日志管理器、文件系统等。
  • 需要共享状态的地方:某些全局配置类或者状态控制器。

2 策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使得它们可以互相替换。这种模式的关键在于将算法的实现从使用算法的代码中分离出来,以便不同的算法可以灵活替换。

2.1 策略模式的基本组成:

  1. 策略接口(Strategy Interface):定义所有具体策略必须实现的算法接口。
  2. 具体策略类(Concrete Strategy Classes):实现策略接口,包含具体算法的实现。
  3. 上下文类(Context Class):使用策略接口,包含对策略对象的引用,允许上下文在运行时决定使用哪个策略。

2.2 具体实现

2.2.1 策略接口

// 支付策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

2.2.2 具体策略类

// 使用信用卡支付的策略
public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card: " + cardNumber);
    }
}

// 使用PayPal支付的策略
public class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal: " + email);
    }
}

// 使用比特币支付的策略
public class BitcoinPayment implements PaymentStrategy {
    private String walletAddress;

    public BitcoinPayment(String walletAddress) {
        this.walletAddress = walletAddress;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Bitcoin Wallet: " + walletAddress);
    }
}

2.2.3 上下文类

// 上下文类,用于选择并执行支付策略
public class PaymentContext {
    private PaymentStrategy paymentStrategy;

    // 构造方法中传入支付策略
    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    // 执行支付操作
    public void executePayment(int amount) {
        paymentStrategy.pay(amount);
    }
}

2.2.4 客户端代码

public class Main {
    public static void main(String[] args) {
        // 使用信用卡支付
        PaymentContext context = new PaymentContext(new CreditCardPayment("1234-5678-9012-3456"));
        context.executePayment(100);

        // 使用PayPal支付
        context = new PaymentContext(new PayPalPayment("user@example.com"));
        context.executePayment(200);

        // 使用比特币支付
        context = new PaymentContext(new BitcoinPayment("1BitcoinWalletAddress"));
        context.executePayment(300);
    }
}

2.3 总结:

  1. 扩展性强:如果需要新增一种支付方式,只需添加一个实现 PaymentStrategy 接口的新类,而无需修改现有的支付逻辑。
  2. 符合开闭原则:对修改关闭,对扩展开放。
  3. 灵活性:不同的策略可以在运行时动态选择和替换。

2.4 使用场景

策略模式非常适合那些需要动态替换算法或行为的场景,比如支付系统、折扣策略、排序算法等。

3 代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理,以控制对这个对象的访问。代理模式通常用来延迟对象的创建、控制访问权限或在访问对象时进行一些额外的处理。

3.1 代理模式的基本概念

  • 代理(Proxy):代理类持有对实际对象的引用,并提供与实际对象相同的接口。代理类可以在调用实际对象的方法之前或之后执行一些额外的操作。
  • 实际对象(Real Subject):代理所代表的实际业务类。
  • 客户端(Client):通过代理类间接与实际对象进行交互,客户端并不知道它是在和代理交互,而不是直接与实际对象交互。

3.2 代理模式的类型

根据代理的目的不同,代理模式可以分为以下几种类型:

  1. 远程代理(Remote Proxy):为一个位于不同地址空间的对象提供代理,通常在分布式系统中使用。
  2. 虚拟代理(Virtual Proxy):延迟初始化资源密集型对象,直到真正需要时才创建,节省系统资源。
  3. 保护代理(Protection Proxy):控制对对象的访问,常用于权限控制。
  4. 缓存代理(Cache Proxy):为耗时的操作提供临时的缓存,避免重复执行相同操作。
  5. 智能引用代理(Smart Reference Proxy):在访问对象时进行额外的操作,如计数、日志记录等。

3.3 代理模式的结构

代理模式通常包含以下角色:

  • 抽象主题(Subject):声明真实对象与代理对象的共同接口,以便在任何使用真实对象的地方都可以使用代理对象。
  • 具体主题(RealSubject):实现抽象主题的具体类,表示实际的业务逻辑。
  • 代理(Proxy):实现抽象主题的代理类,包含对真实对象的引用,可以在调用真实对象的方法前后做一些附加操作。

3.4 代理模式的实现示例

以访问图片的例子为例,假设图片的加载过程非常耗时,我们可以使用虚拟代理来延迟加载图片,直到真正需要显示时才加载。

3.4.1 抽象主题接口

// 抽象主题,定义图片的加载方法
public interface Image {
    void display();
}

3.4.2 具体主题类(实际对象)

// 真实的图片类,加载和显示图片
public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk();
    }

    // 加载图片
    private void loadFromDisk() {
        System.out.println("Loading " + fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
}

3.4.3 代理类

// 代理图片类,控制图片的加载
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        // 延迟加载图片,只有在需要时才创建RealImage对象
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}

3.4.4 客户端代码

public class Client {
    public static void main(String[] args) {
        Image image = new ProxyImage("test_image.jpg");

        // 图片第一次加载时,从磁盘读取
        image.display();
        System.out.println("");

        // 图片第二次显示时,直接使用已经加载的图片
        image.display();
    }
}

3.4.5 运行结果:

Loading test_image.jpg
Displaying test_image.jpg

Displaying test_image.jpg

3.5 代理模式的优点

  1. 控制访问:通过代理可以控制对真实对象的访问,比如延迟加载、权限控制等。
  2. 性能优化:虚拟代理可以在必要时才创建对象,减少资源浪费,提高系统性能。
  3. 增强功能:代理可以在真实对象的前后执行额外的操作,比如日志记录、计数器、安全控制等。

3.6 代理模式的缺点

  1. 增加复杂性:引入了额外的代理对象,可能会增加系统的复杂性。
  2. 延迟处理:虚拟代理可能会带来响应时间的延迟,特别是在首次访问时。

3.7 代理模式的应用场景

  1. 远程调用:通过远程代理处理不同地址空间中的对象通信。
  2. 权限控制:通过保护代理限制访问对象的权限,比如在大型系统中对敏感资源的访问控制。
  3. 延迟加载:虚拟代理延迟资源密集型对象的初始化,如大文件、图片、数据库连接等。
  4. 日志记录与监控:智能引用代理在访问对象时增加日志记录、计数等操作。

3.8 总结

代理模式的核心思想是控制对对象的访问,通过代理类来增强、延迟、或保护对实际对象的操作。它可以有效地提高系统的灵活性、性能和安全性,在很多场景中得到广泛应用。

例如代理图片预加载,和给源对象img设置src可以分别隔离在两个对象里面,如果某一天要去掉预加载不必更改源对象,开放封闭原则,附加功能用代理对象实现,不去更改源对象

面向对象设计鼓励将行为分布到更细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。

4 迭代器模式

迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种方法顺序访问一个集合对象中的各个元素,而不需要暴露集合对象的内部表示。通过迭代器模式,客户端可以使用相同的方式遍历不同集合结构(如数组、列表、树等),从而达到访问集合元素的统一性。

4.1 迭代器模式的组成部分

  1. 迭代器接口(Iterator Interface):定义访问和遍历元素的方法。
  2. 具体迭代器(Concrete Iterator):实现迭代器接口,负责具体集合元素的遍历。
  3. 聚合接口(Aggregate Interface):定义创建迭代器的方法。
  4. 具体聚合(Concrete Aggregate):实现聚合接口,存储集合中的元素并返回迭代器。

4.2 迭代器模式的工作原理

  • 聚合类(如集合、列表等)实现一个创建迭代器的接口,允许客户端获取一个迭代器对象。
  • 迭代器类负责遍历集合中的元素,并提供统一的遍历方法,如 next()hasNext()
  • 客户端通过迭代器访问集合中的元素,而不需要关心集合的内部实现。

4.3 迭代器模式的实现示例

假设我们有一个包含多个元素的数字集合,我们使用迭代器模式来遍历这个集合。

4.3.1 迭代器接口

// 迭代器接口,定义遍历集合的方法
public interface Iterator {
    boolean hasNext();
    Object next();
}

4.3.2 具体迭代器

// 数字集合的具体迭代器,实现遍历功能
public class NumberIterator implements Iterator {
    private int[] numbers;
    private int position = 0;

    public NumberIterator(int[] numbers) {
        this.numbers = numbers;
    }

    @Override
    public boolean hasNext() {
        return position < numbers.length;
    }

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

4.3.3 聚合接口

// 聚合接口,定义创建迭代器的方法
public interface Aggregate {
    Iterator createIterator();
}

4.3.4 具体聚合

// 数字集合类,存储元素并创建迭代器
public class NumberCollection implements Aggregate {
    private int[] numbers;

    public NumberCollection(int[] numbers) {
        this.numbers = numbers;
    }

    @Override
    public Iterator createIterator() {
        return new NumberIterator(numbers);
    }
}

4.3.5 客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建一个数字集合
        NumberCollection numberCollection = new NumberCollection(new int[] {1, 2, 3, 4, 5});

        // 创建迭代器
        Iterator iterator = numberCollection.createIterator();

        // 遍历集合中的元素
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

4.3.6 运行结果

1
2
3
4
5

4.4 迭代器模式的优点

  1. 分离遍历和集合的实现:迭代器模式将集合的遍历操作封装在迭代器中,不暴露集合的内部结构,使集合和遍历逻辑解耦。
  2. 统一遍历接口:迭代器模式提供了一套统一的接口,无论集合的内部结构如何(如数组、列表、链表等),客户端都可以通过相同的方式访问集合中的元素。
  3. 扩展性好:可以为不同类型的集合提供不同的迭代器实现,添加新的集合类型时,不需要修改客户端代码。

4.5 迭代器模式的缺点

  1. 开销增加:对于较小的集合,使用迭代器可能会增加一些额外的开销,如存储迭代器对象。
  2. 单一职责可能被打破:如果迭代器还要处理复杂的遍历逻辑,可能会承担过多的职责。

4.6 迭代器模式的应用场景

  1. 遍历复杂的数据结构:当需要遍历不同类型的集合时(如列表、树、图等),可以使用迭代器模式。
  2. 隐藏集合的内部结构:当不希望客户端了解集合的内部表示时,可以通过迭代器提供访问接口。
  3. 提供不同的遍历方式:可以实现多个不同的迭代器来提供不同的遍历方式,例如正序遍历、倒序遍历等。

4.7 总结

迭代器模式通过将遍历逻辑封装在迭代器中,提供了对集合元素的统一访问方式,解耦了集合的内部结构和遍历操作,增强了代码的灵活性和可扩展性。

5 发布订阅模式

发布-订阅模式(Publish-Subscribe Pattern),也称为观察者模式(Observer Pattern),是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个发布者对象。当发布者状态发生改变时,所有订阅者都会收到通知并自动更新。

5.1 发布-订阅模式的基本概念

  • 发布者(Publisher):发布者维护一组订阅者,当发布者状态发生变化时,会通知所有订阅者。
  • 订阅者(Subscriber):订阅者注册到发布者上,以便在发布者状态改变时接收到通知。
  • 通知机制:发布者通过某种方式(如回调函数)通知所有订阅者。

5.2 发布-订阅模式的组成部分

  1. 发布者(Publisher 或 Subject):负责管理订阅者列表,并在内部状态发生变化时通知订阅者。
  2. 订阅者(Subscriber 或 Observer):订阅发布者的通知,并在接收到通知时执行相应的更新操作。
  3. 通知机制:发布者通过通知机制告知订阅者有状态变化。

5.3 发布-订阅模式的工作流程

  1. 订阅:订阅者注册到发布者,表示对发布者的状态变化感兴趣。
  2. 发布:发布者的状态发生变化时,通知所有注册的订阅者。
  3. 通知:订阅者接收到通知后执行相应的操作(如更新状态、刷新视图等)。

5.4 发布-订阅模式的实现示例

以一个简化的新闻发布系统为例,新闻发布者(Publisher)可以发布新闻,当有新消息发布时,所有订阅者(Subscribers)都会收到通知。

5.4.1 发布者接口

import java.util.ArrayList;
import java.util.List;

// 发布者接口
public interface Publisher {
    void subscribe(Subscriber subscriber);     // 添加订阅者
    void unsubscribe(Subscriber subscriber);   // 移除订阅者
    void notifySubscribers();                  // 通知所有订阅者
}

5.4.2 具体发布者类

// 具体发布者,实现发布者接口
public class NewsPublisher implements Publisher {
    private List<Subscriber> subscribers = new ArrayList<>();
    private String news; // 存储最新的新闻

    // 添加订阅者
    @Override
    public void subscribe(Subscriber subscriber) {
        subscribers.add(subscriber);
    }

    // 移除订阅者
    @Override
    public void unsubscribe(Subscriber subscriber) {
        subscribers.remove(subscriber);
    }

    // 通知所有订阅者
    @Override
    public void notifySubscribers() {
        for (Subscriber subscriber : subscribers) {
            subscriber.update(news);
        }
    }

    // 发布新闻
    public void publishNews(String news) {
        this.news = news;
        notifySubscribers();  // 通知所有订阅者
    }
}

5.4.3 订阅者接口

// 订阅者接口
public interface Subscriber {
    void update(String news); // 更新方法,用于接收发布者的通知
}

5.4.4 具体订阅者类

// 具体订阅者,实现订阅者接口
public class NewsSubscriber implements Subscriber {
    private String name;

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

    // 当发布者有新消息时,订阅者接收更新
    @Override
    public void update(String news) {
        System.out.println(name + " received news update: " + news);
    }
}

5.4.5 客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建发布者
        NewsPublisher newsPublisher = new NewsPublisher();

        // 创建订阅者
        Subscriber subscriber1 = new NewsSubscriber("Alice");
        Subscriber subscriber2 = new NewsSubscriber("Bob");

        // 订阅新闻
        newsPublisher.subscribe(subscriber1);
        newsPublisher.subscribe(subscriber2);

        // 发布新闻
        newsPublisher.publishNews("Breaking News: New Java release!");

        // 取消订阅
        newsPublisher.unsubscribe(subscriber1);

        // 发布新的新闻
        newsPublisher.publishNews("Update: New features in Java!");
    }
}

5.4.6 运行结果:

Alice received news update: Breaking News: New Java release!
Bob received news update: Breaking News: New Java release!
Bob received news update: Update: New features in Java!

5.5 发布-订阅模式的优点

  1. 松耦合:发布者和订阅者之间通过接口进行交互,彼此之间并不需要知道对方的具体实现,有效降低了耦合性。
  2. 动态添加订阅者:可以在运行时添加或移除订阅者,不影响其他订阅者的工作。
  3. 扩展性好:可以轻松地扩展发布者或订阅者,支持多个发布者和多个订阅者。

5.6 发布-订阅模式的缺点

  1. 通知延迟:在有大量订阅者时,通知的速度可能会降低,尤其是在同步通知的情况下。
  2. 复杂性增加:系统中涉及多个订阅者和发布者时,管理关系可能会变得复杂。
  3. 过度通知:如果没有进行良好的过滤或条件通知,所有订阅者可能会收到不必要的通知,造成效率低下。

5.7 发布-订阅模式的应用场景

  1. 事件驱动系统:如GUI事件处理机制中,按钮点击会通知监听器更新。
  2. 日志系统:不同的日志系统可以作为订阅者接收不同种类的日志信息。
  3. 消息队列系统:通过消息队列发布者和订阅者进行消息的异步通信。
  4. 数据绑定:如前端开发中的数据绑定框架,当数据模型发生变化时,视图会自动更新。

5.8 发布-订阅模式在实际中的应用

  • Java 中的 java.util.ObserverObservable 类是对观察者模式的标准实现,虽然它们已被标记为过时,但仍然能够很好地展示发布-订阅模式的核心思想。
  • 消息队列(Message Queue) 系统,如 RabbitMQ、Kafka 等,基于发布-订阅模式提供异步消息处理。

5.9 总结

发布-订阅模式通过解耦发布者和订阅者,使得它们可以独立发展。它不仅适用于简单的通知机制,还可以用在复杂的消息系统中,实现灵活的扩展和动态的消息分发。

6 命令模式

命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成对象,从而允许系统使用不同的请求、队列或日志来参数化方法。使用命令模式,可以将请求的发送者与接收者解耦,使得请求的发出和执行分离开来,从而更加灵活地控制请求的处理过程。

1. 命令模式的组成

命令模式通常由以下几部分组成:

  • 命令接口(Command Interface):声明执行命令的操作。
  • 具体命令类(Concrete Command):实现命令接口,封装具体的命令行为。
  • 接收者(Receiver):实际执行命令逻辑的对象。
  • 调用者(Invoker):负责调用命令,通常保存对命令对象的引用。
  • 客户端(Client):创建具体的命令对象,并将其传递给调用者。

2. 命令模式的工作流程

  • 客户端创建一个具体命令对象,具体命令对象会绑定执行者(接收者)。
  • 客户端将具体命令对象传递给调用者,调用者调用该命令的 execute() 方法。
  • 具体命令对象将调用传递给接收者,接收者执行相应的操作。

3. 命令模式的示例

以遥控器控制家电为例,遥控器可以执行各种命令,比如打开电视、关闭电灯等。我们可以通过命令模式将这些操作封装起来,使得遥控器和具体的家电控制逻辑解耦。

3.3.1 命令接口

java


复制代码
// 命令接口,定义执行操作
public interface Command {
    void execute();
}

3.3.2 接收者类

java


复制代码
// 电视类(接收者)
public class TV {
    public void on() {
        System.out.println("TV is On");
    }

    public void off() {
        System.out.println("TV is Off");
    }
}

// 电灯类(接收者)
public class Light {
    public void on() {
        System.out.println("Light is On");
    }

    public void off() {
        System.out.println("Light is Off");
    }
}

3.3.3 具体命令类

java


复制代码
// 打开电视的命令
public class TVOnCommand implements Command {
    private TV tv;

    public TVOnCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.on();
    }
}

// 关闭电视的命令
public class TVOffCommand implements Command {
    private TV tv;

    public TVOffCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.off();
    }
}

// 打开电灯的命令
public class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

// 关闭电灯的命令
public class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}

3.3.4 调用者类

java


复制代码
// 遥控器类(调用者),可以绑定不同的命令
public class RemoteControl {
    private Command command;

    // 设置命令
    public void setCommand(Command command) {
        this.command = command;
    }

    // 执行命令
    public void pressButton() {
        command.execute();
    }
}

3.3.5 客户端代码

java


复制代码
public class Client {
    public static void main(String[] args) {
        // 创建接收者
        TV tv = new TV();
        Light light = new Light();

        // 创建具体命令
        Command tvOn = new TVOnCommand(tv);
        Command tvOff = new TVOffCommand(tv);
        Command lightOn = new LightOnCommand(light);
        Command lightOff = new LightOffCommand(light);

        // 创建遥控器(调用者)
        RemoteControl remote = new RemoteControl();

        // 控制电视
        remote.setCommand(tvOn);
        remote.pressButton(); // TV is On

        remote.setCommand(tvOff);
        remote.pressButton(); // TV is Off

        // 控制电灯
        remote.setCommand(lightOn);
        remote.pressButton(); // Light is On

        remote.setCommand(lightOff);
        remote.pressButton(); // Light is Off
    }
}

3.4 命令模式的优点

  • 解耦发送者和接收者:发送者只负责调用命令,接收者执行实际逻辑,双方之间没有直接的依赖关系。
  • 扩展性好:新增命令时,只需添加新的具体命令类,无需修改已有的代码。
  • 支持撤销和重做操作:可以通过存储命令历史,实现命令的撤销和重做功能。

3.5 命令模式的应用场景

  • 请求排队:比如任务调度系统,将操作封装为命令对象并排队处理。
  • 日志记录:可以将命令记录下来,支持重做或回放功能。
  • 撤销操作:可以存储执行过的命令对象,从而实现撤销功能。

命令模式适用于需要解耦请求的发起者和执行者的场景,尤其是那些可能需要支持撤销、重做或者请求排队的系统。

7 组合模式

组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以统一地对待单个对象和对象组合,也就是说,客户端无需关心处理的是一个单个对象,还是一个组合对象,从而让对象使用的方式具有一致性。

7.1 组合模式的基本概念

  • 组合模式的核心思想是将单个对象和组合对象统一处理。组合模式将对象组合成树形结构,其中树的每个节点可以是叶子节点(表示单个对象),也可以是复合节点(表示对象的组合)。
  • 组件(Component):定义了组合对象和叶子对象的共同接口,这样客户端可以一致地处理组合对象和叶子对象。
  • 叶子(Leaf):表示树的基本单元对象,它不能再有子对象,具体实现组件接口中的操作。
  • 组合对象(Composite):包含子对象(可能是叶子或其他组合对象),并实现组件接口中的操作。

7.2 组合模式的组成部分

  1. 组件(Component):声明组合对象和叶子对象的共同接口,如添加、删除子对象,或执行某种操作等。
  2. 叶子节点(Leaf):实现组件接口,表示树的最底层节点,它没有子节点,通常是具体的操作对象。
  3. 组合节点(Composite):实现组件接口,表示有子对象的组合对象,它可以包含叶子对象和其他组合对象,并递归调用子对象的操作。

7.3 组合模式的结构图

Component(组件)
   |
   |----Leaf(叶子节点)
   |
   |----Composite(组合节点)
             |
             |---- Component
             |---- Component

7.4 组合模式的实现示例

假设我们要实现一个文件系统,其中有两种对象:文件文件夹。文件夹中可以包含文件或其他文件夹,通过组合模式,文件和文件夹可以统一处理,文件夹可以递归包含文件和其他文件夹。

7.4.1 组件接口

// 组件接口,定义了文件和文件夹的通用操作
public interface FileComponent {
    void show(); // 展示组件信息
}

7.4.2 叶子节点(文件类)

// 文件类,表示叶子节点
public class FileLeaf implements FileComponent {
    private String name;

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

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

7.4.3 组合节点(文件夹类)

import java.util.ArrayList;
import java.util.List;

// 文件夹类,表示组合节点,可以包含文件和其他文件夹
public class FolderComposite implements FileComponent {
    private String name;
    private List<FileComponent> components = new ArrayList<>();

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

    // 添加子组件
    public void addComponent(FileComponent component) {
        components.add(component);
    }

    // 删除子组件
    public void removeComponent(FileComponent component) {
        components.remove(component);
    }

    // 展示文件夹内容
    @Override
    public void show() {
        System.out.println("Folder: " + name);
        for (FileComponent component : components) {
            component.show();
        }
    }
}

7.4.4 客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建文件
        FileComponent file1 = new FileLeaf("file1.txt");
        FileComponent file2 = new FileLeaf("file2.txt");
        FileComponent file3 = new FileLeaf("file3.txt");

        // 创建文件夹并添加文件
        FolderComposite folder1 = new FolderComposite("folder1");
        folder1.addComponent(file1);

        FolderComposite folder2 = new FolderComposite("folder2");
        folder2.addComponent(file2);
        folder2.addComponent(file3);

        // 创建根文件夹并添加子文件夹
        FolderComposite rootFolder = new FolderComposite("root");
        rootFolder.addComponent(folder1);
        rootFolder.addComponent(folder2);

        // 展示文件夹结构
        rootFolder.show();
    }
}

7.4.5 运行结果:

Folder: root
Folder: folder1
File: file1.txt
Folder: folder2
File: file2.txt
File: file3.txt

7.5 组合模式的优点

  1. 统一的处理方式:组合模式让客户端可以统一地处理单个对象和组合对象,不需要区分处理叶子对象和组合对象的逻辑,简化了代码。
  2. 便于扩展:通过添加新的叶子对象或组合对象,系统可以很容易扩展,且不会影响现有代码。
  3. 简化客户端代码:客户端可以直接通过组件接口操作对象,而不需要关心是单个对象还是组合对象,代码更为简洁。

7.6 组合模式的缺点

  1. 设计复杂:如果组合对象的结构过于复杂,可能会导致系统设计变得复杂,维护也更为困难。
  2. 不容易限制组合结构:组合模式允许任意组合对象和叶子对象,有时可能会导致过度灵活,使系统难以管理。

7.7 组合模式的应用场景

  1. 文件系统:如文件夹和文件的层次结构,文件夹可以包含文件或子文件夹。
  2. 图形界面:窗口、按钮、文本框等界面组件可以组成复杂的界面结构,其中窗口可以包含其他窗口或组件。
  3. 菜单系统:菜单和子菜单的结构可以通过组合模式表示,子菜单可以包含菜单项或其他子菜单。
  4. 组织结构:企业的组织结构图可以通过组合模式来表示,部门可以包含员工,也可以包含子部门。

7.8 总结

组合模式通过将对象组合成树形结构,使得客户端可以统一地对待单个对象和组合对象,适用于需要处理树形结构的场景。它不仅提高了代码的灵活性和可扩展性,也简化了客户端代码的操作逻辑。

8 模板方法模式

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作的算法骨架,并允许子类在不改变算法结构的前提下,重新定义算法中的某些步骤。通过模板方法模式,开发者可以把算法中的不变部分放在父类中,将可变部分延迟到子类中实现,从而实现代码复用和灵活扩展。

8.1 组成部分

  1. 抽象类(Abstract Class):定义算法的骨架,在其中包含一个或多个模板方法(通常是具体方法),以及若干个可以由子类实现的抽象方法或钩子方法。
  2. 模板方法(Template Method):在抽象类中定义,封装了一个算法的基本结构,规定了执行的步骤及调用顺序。模板方法一般是 final 类型,防止子类改变其执行顺序。
  3. 具体类(Concrete Class):实现抽象类中定义的抽象方法或钩子方法,具体定义算法的某些步骤。

8.2 模板方法模式的工作原理

  • 在父类中定义一个算法的通用流程(算法骨架),但某些具体步骤由子类提供。
  • 子类可以重写父类的某些方法,但不能改变算法的整体结构。
  • 通过这种方式,确保算法的核心流程不被破坏,同时让子类可以根据需求灵活定制具体实现。

8.3 模板方法模式的类图

AbstractClass(抽象类)
   |
   |---- templateMethod(模板方法)  → 定义算法骨架
   |---- primitiveOperation1(抽象方法) → 由子类实现
   |---- primitiveOperation2(钩子方法) → 子类可选择实现
   |
ConcreteClass(具体类)
   |---- primitiveOperation1(具体实现)
   |---- primitiveOperation2(具体实现)

8.4 模板方法模式的示例

以一个咖啡和茶制作流程为例,这两个饮品的制作步骤大致相同,但某些步骤略有不同,我们可以通过模板方法模式将相同的部分提取出来,将不同的部分交给子类实现。

8.4.1 抽象类

public abstract class Beverage {
    // 模板方法,定义制作饮料的步骤
    public final void prepareRecipe() {
        // 煮水
        boilWater();
        // 沏茶或冲咖啡
        brew();
        // 倒入杯中
        pourInCup();
        // 添加调味品
        addCondiments();
    }

    // 通用的步骤
    private void boilWater() {
        System.out.println("Boiling water");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 抽象方法,由子类实现
    protected abstract void brew(); // 冲泡
    protected abstract void addCondiments(); // 添加调料
}

8.4.2 具体类(茶的制作)

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

    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon");
    }
}

8.5.3 具体类(咖啡的制作)

public class Coffee extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter");
    }

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

8.5.4 客户端代码

public class Client {
    public static void main(String[] args) {
        Beverage tea = new Tea();
        tea.prepareRecipe();
        
        System.out.println();

        Beverage coffee = new Coffee();
        coffee.prepareRecipe();
    }
}

8.5.5 运行结果:

Boiling water
Steeping the tea
Pouring into cup
Adding lemon

Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk

8.6 模板方法模式的优点

  1. 代码复用:将算法的通用部分放在父类中,避免了重复代码,子类只需关注定制化的部分,减少了代码冗余。
  2. 灵活扩展:通过子类实现不同的具体步骤,可以灵活定制算法的实现,而不影响算法的整体结构。
  3. 控制流程:父类控制着算法的整体流程,而子类实现具体步骤,保证了算法的一致性。

8.7 模板方法模式的缺点

  1. 子类增多:如果算法的步骤变化较多,每种变化都需要创建新的子类,导致类的数量增加,维护复杂。
  2. 父类对子类的侵入性:父类中的模板方法固定了算法的执行流程,子类在某些情况下可能无法灵活地调整流程。

8.8 模板方法模式的应用场景

  1. 流程稳定、细节可变:当算法的大部分步骤是固定的,只有某些部分需要灵活变化时,可以使用模板方法模式。例如,报表生成系统中,不同类型的报表有共同的生成步骤,但某些细节如数据格式、展示方式等可能不同。
  2. 代码复用:在有多个子类共享相同算法结构时,使用模板方法模式可以避免重复代码。
  3. 框架设计:框架中可以定义模板方法,让使用者通过继承和重写特定方法来定制化行为。

8.9 钩子方法

在模板方法模式中,通常还会有钩子方法(Hook Method)。钩子方法是一种可选的步骤,父类提供默认的空实现,子类可以根据需要重写该方法来插入额外的行为,而不破坏模板方法的整体结构。例如:

protected void hook() {
    // 子类可以选择重写该方法
}

8.10 总结

模板方法模式通过将算法的通用部分放在父类中,把具体的步骤交给子类实现,实现了代码复用与扩展性。在软件设计中,它适用于需要固定流程但某些步骤可变的场景,有助于简化代码、提高灵活性,同时保持算法的一致性。

9 享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享大量细粒度对象,减少系统中的内存消耗。享元模式通过将对象的共享部分提取出来,并将不可共享的部分进行外部化,从而减少重复对象的创建,节省内存资源。

9.1 核心思想

享元模式的核心思想是将系统中重复的对象进行共享,通过把对象的内在状态外在状态分离来实现内存的优化。

  • 内在状态:可以共享的部分,不会随环境改变而改变的属性,由享元对象存储并进行复用。
  • 外在状态:不能共享的部分,依赖于具体环境的属性,由客户端负责维护。

9.2 享元模式的组成部分

  1. 享元接口(Flyweight Interface):定义享元对象的行为,通过该接口可以访问对象的内在状态。
  2. 具体享元(Concrete Flyweight):实现享元接口,包含可以共享的内在状态。
  3. 不可共享的享元对象(Unshared Flyweight):在某些情况下,不可以共享的对象即是外在状态,通常由外部传入。
  4. 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保享元对象可以共享使用,避免重复创建。

9.3 享元模式的类图

Flyweight(享元接口)
   |
ConcreteFlyweight(具体享元类) ← FlyweightFactory(享元工厂)
   |
UnsharedFlyweight(非共享对象)

9.4 享元模式的实现示例

我们通过一个象棋游戏的例子来展示享元模式的实现。在象棋中,棋子的颜色和类型可以共享,而位置是每个棋子特有的。因此,我们可以使用享元模式共享棋子的颜色和类型,位置则作为外在状态由客户端来管理。

9.4.1 享元接口

// 享元接口,定义了棋子的行为
public interface ChessPiece {
    void place(int x, int y); // 设置棋子的位置信息
}

9.4.2 具体享元类(共享棋子)

// 具体享元类,表示可以共享的棋子(颜色和类型)
public class ConcreteChessPiece implements ChessPiece {
    private String color; // 棋子的颜色(内在状态)

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

    @Override
    public void place(int x, int y) {
        System.out.println("Placing a " + color + " chess piece at (" + x + ", " + y + ")");
    }
}

9.4.3 享元工厂类

import java.util.HashMap;
import java.util.Map;

// 享元工厂,负责管理享元对象的创建和共享
public class ChessPieceFactory {
    private static final Map<String, ChessPiece> pieces = new HashMap<>();

    // 获取享元对象,如果不存在则创建
    public static ChessPiece getChessPiece(String color) {
        if (!pieces.containsKey(color)) {
            pieces.put(color, new ConcreteChessPiece(color));
        }
        return pieces.get(color);
    }
}

9.4.4 客户端代码

public class Client {
    public static void main(String[] args) {
        // 获取享元对象(棋子),颜色相同的棋子会被共享
        ChessPiece blackPiece1 = ChessPieceFactory.getChessPiece("Black");
        ChessPiece blackPiece2 = ChessPieceFactory.getChessPiece("Black");
        ChessPiece whitePiece1 = ChessPieceFactory.getChessPiece("White");

        // 棋子的具体位置(外在状态)由客户端提供
        blackPiece1.place(1, 1);
        blackPiece2.place(2, 2);
        whitePiece1.place(3, 3);

        // 输出对象比较,验证是否共享
        System.out.println(blackPiece1 == blackPiece2); // true
        System.out.println(blackPiece1 == whitePiece1); // false
    }
}

9.4.5 运行结果:

Placing a Black chess piece at (1, 1)
Placing a Black chess piece at (2, 2)
Placing a White chess piece at (3, 3)
true
false

9.5 享元模式的优点

  1. 节省内存:通过共享重复的对象,享元模式大大减少了内存占用,适用于大量重复对象的场景。
  2. 提高性能:减少了对象的创建次数,降低了内存使用和垃圾回收压力。
  3. 实现对象的共享:通过分离内在状态和外在状态,多个对象可以共享相同的内在状态。

9.6 享元模式的缺点

  1. 引入了复杂性:系统需要分离内在状态和外在状态,增加了设计的复杂性,尤其是在对象的状态需要频繁变化时。
  2. 适用场景有限:享元模式主要适用于内在状态较少、可以共享的对象,过多的外在状态可能导致系统设计复杂化。

9.7 享元模式的应用场景

  1. 系统中有大量相似对象:当系统中有许多相同或相似对象时,可以使用享元模式减少内存开销。例如:图形编辑软件中的形状、文本编辑器中的字符对象。
  2. 对象的状态可以分为内在和外在:对象的内在状态是可以共享的,而外在状态依赖于上下文环境。例如:棋盘游戏中的棋子、网页中的字体渲染。
  3. 需要节省内存的场景:在需要高效使用内存的场景下,通过共享对象减少内存占用。

9.8 享元模式在实际中的应用

  • 数据库连接池:数据库连接池通过共享数据库连接对象,避免了每次请求都创建新的连接,从而减少了系统资源消耗。
  • 字符串常量池:Java 的字符串常量池机制也是享元模式的一种应用,当创建一个新的字符串时,JVM 会先检查常量池中是否存在相同的字符串对象,如果存在,则直接返回。

9.9 总结

享元模式通过共享对象的内在状态,有效减少了系统中重复对象的创建,节省了内存资源。在一些需要大量相似对象的场景中,享元模式可以显著提高性能和内存利用率。但同时,享元模式也会增加设计复杂性,需要合理划分内在状态和外在状态才能有效应用。

10 职责链模式

职责链模式(Chain of Responsibility Pattern)是一种行为型设计模式,允许多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合。将这些接收对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。

职责链模式的关键在于:每个对象都持有对下一个处理对象的引用。当一个请求到来时,对象首先尝试自己处理,如果无法处理,就将请求传递给链中的下一个对象,直到找到能够处理请求的对象。

10.1 职责链模式的组成部分

  1. 抽象处理者(Handler):定义一个处理请求的接口或抽象类,包含处理请求的抽象方法和设置下一个处理者的引用。
  2. 具体处理者(Concrete Handler):继承或实现抽象处理者,具体处理请求。如果无法处理请求,则将请求传递给下一个处理者。
  3. 客户端(Client):创建职责链并发出请求,由职责链中的处理者决定谁来处理请求。

10.2 职责链模式的类图

Handler(抽象处理者)
   |
   |---- ConcreteHandler1(具体处理者1)
   |---- ConcreteHandler2(具体处理者2)
   |---- ...

10.3 职责链模式的工作原理

  • 每个处理者对象包含对下一个处理者的引用。
  • 客户端将请求发送到第一个处理者,处理者根据具体逻辑判断是否能处理请求,如果不能,则将请求传递给链中的下一个处理者。
  • 这样,客户端不需要知道具体是哪个处理者处理了请求,消除了发送者与接收者的耦合。

10.4 职责链模式的实现示例

以一个报销审批的例子为例。不同层级的审批者(如组长、经理、总监)有不同的审批权限,报销金额在不同的区间时,应该由不同的审批者进行处理。我们使用职责链模式来实现这个报销审批流程。

10.4.1 抽象处理者

public abstract class Approver {
    protected Approver nextApprover; // 下一个处理者

    // 设置下一个处理者
    public void setNextApprover(Approver nextApprover) {
        this.nextApprover = nextApprover;
    }

    // 处理请求的抽象方法
    public abstract void processRequest(int amount);
}

10.4.2 具体处理者(组长、经理、总监)

// 组长,处理1000元以内的报销请求
public class TeamLeader extends Approver {
    @Override
    public void processRequest(int amount) {
        if (amount <= 1000) {
            System.out.println("TeamLeader approves " + amount + " RMB.");
        } else if (nextApprover != null) {
            nextApprover.processRequest(amount);
        }
    }
}

// 经理,处理5000元以内的报销请求
public class Manager extends Approver {
    @Override
    public void processRequest(int amount) {
        if (amount <= 5000) {
            System.out.println("Manager approves " + amount + " RMB.");
        } else if (nextApprover != null) {
            nextApprover.processRequest(amount);
        }
    }
}

// 总监,处理10000元以内的报销请求
public class Director extends Approver {
    @Override
    public void processRequest(int amount) {
        if (amount <= 10000) {
            System.out.println("Director approves " + amount + " RMB.");
        } else if (nextApprover != null) {
            nextApprover.processRequest(amount);
        }
    }
}

10.4.3 客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建审批者
        Approver teamLeader = new TeamLeader();
        Approver manager = new Manager();
        Approver director = new Director();

        // 设置职责链
        teamLeader.setNextApprover(manager);
        manager.setNextApprover(director);

        // 发出报销请求
        teamLeader.processRequest(500);    // TeamLeader approves 500 RMB.
        teamLeader.processRequest(3000);   // Manager approves 3000 RMB.
        teamLeader.processRequest(8000);   // Director approves 8000 RMB.
        teamLeader.processRequest(12000);  // 12000 exceeds all limits, no approval.
    }
}

10.4.4 运行结果:

TeamLeader approves 500 RMB.
Manager approves 3000 RMB.
Director approves 8000 RMB.

10.5 职责链模式的优点

  1. 降低耦合:请求的发送者和处理者解耦,发送者无需知道具体是哪个处理者处理了请求,处理者只需关注自己的职责。
  2. 灵活性:职责链可以动态组合,修改链中的处理者顺序或添加新的处理者非常容易。
  3. 增强扩展性:通过增加新的具体处理者,系统可以很方便地扩展链条的处理逻辑。

10.6 职责链模式的缺点

  1. 可能影响性能:如果职责链过长,请求需要经过多个处理者,可能会影响系统性能。
  2. 不易调试:由于请求是在多个处理者之间传递,链条较长时,调试问题会比较困难,尤其是某些请求没有明确的终结处理者。

10.7 职责链模式的应用场景

  1. 多级请求处理:在系统中,当请求需要经过多个对象逐级处理时,可以使用职责链模式,例如:审批流程、权限校验等。
  2. 可灵活处理的场景:请求的处理者不明确,可能由多个对象中的某一个或多个对象处理的场景,如:事件处理机制。
  3. 日志系统:日志处理系统可以根据日志的级别,选择不同的处理者(如将错误日志写入文件,普通日志输出到控制台)。
  4. 表单验证:验证电话或者邮箱

10.8 职责链模式的实际应用

  • 异常处理机制:Java 中的异常处理使用了类似职责链的机制,多个 catch 块可以依次尝试处理异常。
  • Servlet 过滤器:Java Servlet 中的过滤器链(Filter Chain)是职责链模式的一个典型应用,请求可以在多个过滤器之间传递,直到被最终处理。

10.9

职责链模式通过将请求沿着处理者链传递,使得多个对象都有机会处理请求。它有效降低了发送者和接收者之间的耦合,使得系统更加灵活扩展,适用于请求处理责任不明确、需要动态配置责任链的场景。但在实际应用中,职责链的长度需要适度控制,避免引入性能问题。

11 中介者模式

中介者模式(Mediator Pattern)是一种行为型设计模式,用于减少对象之间的直接交互,通过一个中介者对象来封装多个对象之间的交互。中介者模式通过引入一个中介者对象,使各个对象不再直接引用彼此,而是通过中介者来进行消息传递和协调。这样可以有效降低对象之间的耦合度,使系统更易于维护和扩展。

11.1 核心思想

中介者模式的核心思想是引入一个中介者对象,来管理对象之间的通信,使对象间的交互从“多对多”变成“多对一”。每个对象不再需要知道其他对象的存在,而只需要知道中介者即可。

11.2 中介者模式的组成部分

  1. 中介者接口(Mediator):定义了与各同事对象交互的方法,通常包括一个事件通知的方法。
  2. 具体中介者(Concrete Mediator):实现中介者接口,负责协调各同事对象的交互和行为。
  3. 同事类(Colleague):各个具体的交互对象,持有对中介者的引用,通过中介者与其他同事对象进行交互。

11.3 中介者模式的类图

Mediator(中介者接口)
   |
   |---- ConcreteMediator(具体中介者)
         |
         |---- Colleague1(具体同事类1)
         |---- Colleague2(具体同事类2)
         |---- ...

11.4 中介者模式的工作原理

  • 同事类(Colleague)通过调用中介者的方法来与其他同事类进行交互。
  • 中介者负责管理同事类之间的通信和协调。
  • 这样,同事类之间的直接引用被消除了,所有交互都通过中介者进行。

11.5 中介者模式的示例

以一个聊天室系统为例,用户通过聊天室发送消息给其他用户,而用户之间并不知道彼此的存在,他们通过聊天室(中介者)来进行通信。

11.5.1 中介者接口

// 中介者接口,定义发送消息的行为
public interface ChatMediator {
    void sendMessage(String message, User user);
    void addUser(User user);
}

11.5.2 具体中介者类

import java.util.ArrayList;
import java.util.List;

// 具体中介者类,实现了聊天室的行为
public class ChatRoom implements ChatMediator {
    private List<User> users;

    public ChatRoom() {
        this.users = new ArrayList<>();
    }

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

    @Override
    public void sendMessage(String message, User user) {
        for (User u : users) {
            // 消息不发给自己
            if (u != user) {
                u.receive(message);
            }
        }
    }
}

11.5.3 同事类

// 同事类,表示聊天室中的用户
public abstract class User {
    protected ChatMediator mediator;
    protected String name;

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

    public abstract void send(String message);
    public abstract void receive(String message);
}

11.5.4 具体同事类

// 具体同事类,用户的具体实现
public class ConcreteUser extends User {
    public ConcreteUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

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

    @Override
    public void receive(String message) {
        System.out.println(this.name + " receives: " + message);
    }
}

11.5.5 客户端代码

public class Client {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom();

        User user1 = new ConcreteUser(chatRoom, "Alice");
        User user2 = new ConcreteUser(chatRoom, "Bob");
        User user3 = new ConcreteUser(chatRoom, "Charlie");

        chatRoom.addUser(user1);
        chatRoom.addUser(user2);
        chatRoom.addUser(user3);

        user1.send("Hi everyone!");
        user2.send("Hello Alice!");
    }
}

11.5.6 运行结果:

Alice sends: Hi everyone!
Bob receives: Hi everyone!
Charlie receives: Hi everyone!

Bob sends: Hello Alice!
Alice receives: Hello Alice!
Charlie receives: Hello Alice!

11.6 中介者模式的优点

  1. 减少类之间的耦合:对象不需要直接相互引用,所有通信都通过中介者进行,降低了对象间的耦合性。
  2. 更易扩展:增加新的同事类或修改交互行为时,只需修改中介者而不必修改每个同事类,系统更易于维护和扩展。
  3. 简化对象之间的交互:通过中介者,复杂的多对多关系被简化为一对多的关系,系统结构更加清晰。

11.7 中介者模式的缺点

  1. 中介者过于复杂:随着系统中交互逻辑的增加,中介者会承担越来越多的职责,可能会变得过于复杂,成为“上帝类”。
  2. 难以维护:如果中介者的逻辑复杂,维护起来也会变得困难,特别是在交互行为较多时。

11.8 中介者模式的应用场景

  1. 对象之间存在复杂的引用关系,导致对象之间的依赖性过强:例如,图形界面设计中,多个组件之间有相互通信的需求,使用中介者可以简化这些组件之间的复杂交互。
  2. 一个对象修改其他多个对象的行为:如果一个对象的操作会影响多个对象,且这些对象之间的行为存在依赖性,中介者模式可以帮助控制对象之间的相互作用。
  3. 事件驱动系统:在事件驱动系统中,事件的发送者和处理者之间的关系可以通过中介者来解耦。

11.9 中介者模式的实际应用

  • GUI 框架中的事件处理:例如,MVC 架构中的控制器(Controller)常常作为视图(View)和模型(Model)之间的中介者,协调它们之间的交互。
  • 飞机塔台调度系统:飞机之间不能直接通信,它们通过塔台(中介者)进行通信,塔台负责调度和管理飞机的飞行和降落。
  • 聊天室应用:聊天室系统中的用户通过聊天室(中介者)发送和接收消息,用户之间无需直接通信。

11.10 总结

中介者模式通过引入一个中介者对象,解耦了对象之间的直接交互,使得系统中的对象能够更好地独立工作。它适用于对象之间存在复杂交互、耦合度较高的场景,可以简化系统的结构并提升可维护性。但中介者模式的中介者对象容易变得复杂,需要在设计时控制好中介者的职责,以避免将所有逻辑都集中到中介者中。

12 工厂模式

工厂模式是一种创建型设计模式,用于通过定义一个用于创建对象的接口来实例化对象,而不需要知道具体的类。工厂模式的主要目的是将对象的创建与使用分离,使得代码更具可维护性和扩展性。

工厂模式有三种常见的实现方式:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)

12.1 简单工厂模式

简单工厂模式是最基本的一种工厂模式,提供一个创建对象的静态方法,根据传入的参数决定要实例化的对象。

12.1.1 示例

假设我们要创建不同类型的汽车,可以使用一个简单工厂来生产这些汽车。

// 抽象产品类
abstract class Car {
    public abstract void drive();
}

// 具体产品类
class Sedan extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a sedan.");
    }
}

class SUV extends Car {
    @Override
    public void drive() {
        System.out.println("Driving an SUV.");
    }
}

// 简单工厂类
class CarFactory {
    public static Car createCar(String type) {
        if (type.equals("sedan")) {
            return new Sedan();
        } else if (type.equals("suv")) {
            return new SUV();
        }
        return null;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Car sedan = CarFactory.createCar("sedan");
        sedan.drive();

        Car suv = CarFactory.createCar("suv");
        suv.drive();
    }
}

12.1.2 运行结果:

Driving a sedan.
Driving an SUV.

12.1.3 简单工厂模式的优点:

  • 简单易用:只需要一个工厂类,根据不同的参数就能创建不同的对象。

12.4.4 缺点:

  • 不符合开闭原则:如果需要添加新类型的产品,需要修改工厂类的代码,违反了“对扩展开放,对修改关闭”的原则。

12.2 工厂方法模式

工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪一个具体类。工厂方法将对象的创建延迟到子类。

12.2.1 示例

使用工厂方法模式,每个类型的汽车有各自的工厂来生产。

// 抽象产品类
abstract class Car {
    public abstract void drive();
}

// 具体产品类
class Sedan extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a sedan.");
    }
}

class SUV extends Car {
    @Override
    public void drive() {
        System.out.println("Driving an SUV.");
    }
}

// 抽象工厂类
interface CarFactory {
    Car createCar();
}

// 具体工厂类
class SedanFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Sedan();
    }
}

class SUVFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new SUV();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        CarFactory sedanFactory = new SedanFactory();
        Car sedan = sedanFactory.createCar();
        sedan.drive();

        CarFactory suvFactory = new SUVFactory();
        Car suv = suvFactory.createCar();
        suv.drive();
    }
}

12.2.2 运行结果:

Driving a sedan.
Driving an SUV.

12.2.3 工厂方法模式的优点:

  • 符合开闭原则:每当需要增加新的产品,只需要添加新的工厂类,而不必修改已有的代码。
  • 高内聚、低耦合:具体产品的创建由子类实现,父类只关心创建的接口,符合单一职责原则。

12.2.4 缺点:

  • 类的数量增加:每种产品都需要一个具体的工厂,导致类的数量增多,增加了系统的复杂性。

12.3 抽象工厂模式

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,无需指定具体类。它是一种创建产品家族的工厂模式,可以生产一组相关的产品。

12.3.1 示例

假设我们有不同类型的汽车,还分为不同的配件(例如轮胎、发动机等),抽象工厂模式可以创建相互依赖的产品。

// 抽象产品类
interface Car {
    void drive();
}

interface Engine {
    void start();
}

// 具体产品类
class Sedan implements Car {
    @Override
    public void drive() {
        System.out.println("Driving a sedan.");
    }
}

class SUV implements Car {
    @Override
    public void drive() {
        System.out.println("Driving an SUV.");
    }
}

class SedanEngine implements Engine {
    @Override
    public void start() {
        System.out.println("Starting a sedan engine.");
    }
}

class SUVEngine implements Engine {
    @Override
    public void start() {
        System.out.println("Starting an SUV engine.");
    }
}

// 抽象工厂类
interface CarFactory {
    Car createCar();
    Engine createEngine();
}

// 具体工厂类
class SedanFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Sedan();
    }

    @Override
    public Engine createEngine() {
        return new SedanEngine();
    }
}

class SUVFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new SUV();
    }

    @Override
    public Engine createEngine() {
        return new SUVEngine();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        CarFactory sedanFactory = new SedanFactory();
        Car sedan = sedanFactory.createCar();
        Engine sedanEngine = sedanFactory.createEngine();
        sedan.drive();
        sedanEngine.start();

        CarFactory suvFactory = new SUVFactory();
        Car suv = suvFactory.createCar();
        Engine suvEngine = suvFactory.createEngine();
        suv.drive();
        suvEngine.start();
    }
}

12.3.2 运行结果:

Driving a sedan.
Starting a sedan engine.
Driving an SUV.
Starting an SUV engine.

12.3.3抽象工厂模式的优点:

  • 产品族的一致性:保证同一个工厂生产出的产品是相互兼容的,产品族之间不相互影响。
  • 符合开闭原则:可以通过增加新的工厂来扩展新的产品族。

12.3.4 缺点:

  • 复杂度高:随着产品族的增加,系统的复杂度也随之增加。
  • 不易扩展新产品:如果需要增加一个新产品,而不是产品族中的某个产品,所有的工厂类都需要进行修改。

12.4 总结

模式优点缺点适用场景
简单工厂模式简单易用,代码集中违反开闭原则,工厂类复杂产品较少、需求变化不频繁的场景
工厂方法模式符合开闭原则,便于扩展类数量增多,系统复杂性增加需要频繁扩展产品的场景
抽象工厂模式便于创建产品家族,保证产品族的兼容性难以增加新产品,复杂度较高产品家族多,产品之间有依赖关系的场景

工厂模式通过将对象创建过程封装起来,有助于提高系统的灵活性和扩展性。

13 适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它的作用是将一个类的接口转换成客户端希望的另一个接口。适配器模式使得原本接口不兼容的类可以一起工作。它通过提供一个中间层(适配器)来连接不同接口的类,从而实现接口的适配。

适配器模式常用于将现有的类适配到现有的框架或库中,或者当我们想要重用现有的类,但接口与系统不兼容时,可以使用适配器模式来实现兼容性。

13.1 适配器模式的组成部分

  • 目标接口(Target):客户端需要的接口,这个接口定义了客户希望使用的方法。
  • 现有类(Adaptee):现有的接口,客户端不能直接使用,需要进行适配。
  • 适配器(Adapter):适配器类通过实现目标接口,调用现有类的方法,从而让现有类能够适配客户端。

13.2 适配器模式的类型

  • 类适配器模式:适配器通过继承现有类,并实现目标接口。
  • 对象适配器模式:适配器通过组合现有类,在内部持有现有类的对象,从而进行适配。

13.3 适配器模式的类图

13.3.1 类适配器模式类图:

+--------------+            +------------+
|  Target      |<-----------|  Adapter    |
+--------------+            +------------+
                              ^
                              |
                       +--------------+
                       |   Adaptee     |
                       +--------------+

13.3.2 对象适配器模式类图:

+--------------+            +------------+
|  Target      |<-----------|  Adapter    |
+--------------+            +------------+
                              ^
                              |
                       +--------------+
                       |   Adaptee     |
                       +--------------+

13.4 适配器模式的示例

假设我们有一个老式的圆形插头(Adaptee),但现在我们需要适配到现代的三孔插座(Target)。我们可以使用适配器模式来进行适配。

13.4.1 目标接口(Target)

// 定义客户端需要的接口
public interface ThreeHoleSocket {
    void plugIn();
}

13.4.2 现有类(Adaptee)

// 老式的两孔插头
public class TwoHolePlug {
    public void connect() {
        System.out.println("Connected using a two-hole plug.");
    }
}

13.4.3 类适配器(Adapter - 类适配器模式)

// 类适配器通过继承实现接口的适配
public class PlugAdapter extends TwoHolePlug implements ThreeHoleSocket {
    @Override
    public void plugIn() {
        // 调用老式插头的方法,实现接口适配
        connect();
    }
}

13.4.4 对象适配器(Adapter - 对象适配器模式)

// 对象适配器通过组合实现接口的适配
public class PlugAdapter implements ThreeHoleSocket {
    private TwoHolePlug twoHolePlug;

    public PlugAdapter(TwoHolePlug twoHolePlug) {
        this.twoHolePlug = twoHolePlug;
    }

    @Override
    public void plugIn() {
        // 调用老式插头的方法,实现接口适配
        twoHolePlug.connect();
    }
}

13.4.5 客户端代码

public class Client {
    public static void main(String[] args) {
        // 类适配器方式
        ThreeHoleSocket socket = new PlugAdapter();
        socket.plugIn();  // 输出: Connected using a two-hole plug.

        // 对象适配器方式
        TwoHolePlug oldPlug = new TwoHolePlug();
        ThreeHoleSocket socket2 = new PlugAdapter(oldPlug);
        socket2.plugIn();  // 输出: Connected using a two-hole plug.
    }
}

13.4.6 运行结果:

Connected using a two-hole plug.
Connected using a two-hole plug.

13.5 适配器模式的优缺点

13.5.1 优点:

  1. 提高了类的复用性:可以将现有类复用到不同接口的系统中,而不需要修改原有代码。
  2. 解耦性强:客户端与现有类之间的依赖通过适配器实现,客户端不直接依赖现有类,增强了系统的可扩展性。
  3. 灵活性强:可以在不修改现有类的情况下进行扩展,尤其是在第三方类库不易修改的情况下。

13.5.2 缺点:

  1. 复杂性增加:在增加适配器后,代码的复杂度可能会增加,尤其是类适配器模式需要继承多个类时(由于 Java 等语言不支持多继承,这点尤其困难)。
  2. 性能问题:在一些性能敏感的应用中,适配器可能引入额外的函数调用,影响性能。

13.6 适用场景

  • 接口不兼容时的适配:当系统需要使用一个已经存在的类,但是它的接口与当前系统不兼容时,可以使用适配器模式来进行适配。
  • 使用第三方库:如果我们要将一个第三方库整合到现有系统中,但其接口与系统不一致,可以使用适配器模式进行集成。
  • 遗留系统的升级:将旧系统中的模块或类迁移到新系统中,而不希望大规模修改旧代码时。

13.7 适配器模式 vs. 装饰器模式

  • 适配器模式:关注的是接口的兼容性,解决现有接口与目标接口不匹配的问题。
  • 装饰器模式:关注的是功能的扩展,通过动态地为对象添加行为来增强功能。

13.8 总结

适配器模式是一种重要的设计模式,通过它可以让不兼容的类一起工作,减少代码重复、增强代码的可维护性。在现代开发中,特别是在整合第三方系统、老系统改造等场景中非常有用。

14 装饰者模式

装饰者模式(Decorator Pattern)是一种结构型设计模式,允许通过动态地为对象添加额外的职责或行为,而不会影响其他对象。与继承相比,装饰者模式更灵活,因为它通过对象组合来实现功能扩展,而不是通过子类化。

装饰者模式的核心思想是:将核心功能与装饰功能分离,装饰功能通过装饰类动态地叠加在核心功能上。

14.1 装饰者模式的组成部分

  • 组件接口(Component):定义一个对象的基本接口,可以是抽象类或接口。
  • 具体组件(ConcreteComponent):实现组件接口,定义基本的对象行为。
  • 装饰者类(Decorator):持有一个组件对象的引用,实现组件接口,并为组件添加额外功能。它通常是抽象类。
  • 具体装饰者类(ConcreteDecorator):继承自装饰者类,向组件添加具体的功能。

14.2 装饰者模式的类图

+------------------+      +-----------------+
|   Component      |<-----|   Decorator      |
+------------------+      +-----------------+
        ^                          ^
        |                          |
+------------------+      +------------------+
| ConcreteComponent|      | ConcreteDecorator |
+------------------+      +------------------+
  • Component 是接口或抽象类,定义了基本功能。
  • ConcreteComponent 是实际的对象,实现了基础功能。
  • Decorator 是装饰者抽象类,持有 Component 的引用,用于动态地为 ConcreteComponent 添加新功能。
  • ConcreteDecorator 是具体的装饰者类,负责为对象添加新的职责。

14.3 装饰者模式的示例

假设我们有一个基本的饮料(Beverage),可以通过添加不同的配料(如牛奶、糖等)来装饰它,并增加其功能或价格。

14.3.1 组件接口(Component)

// 饮料接口,定义基础功能
public abstract class Beverage {
    public abstract String getDescription();
    public abstract double cost();
}

14.3.2 具体组件(ConcreteComponent)

// 具体的饮料类,比如 Espresso
public class Espresso extends Beverage {
    @Override
    public String getDescription() {
        return "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

14.3.3 装饰者类(Decorator)

// 装饰者类,扩展 Beverage 类
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

14.3.4 具体装饰者类(ConcreteDecorator)

// 加牛奶的装饰者
public class Milk extends CondimentDecorator {
    Beverage beverage;

    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

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

    @Override
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

// 加糖的装饰者
public class Sugar extends CondimentDecorator {
    Beverage beverage;

    public Sugar(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Sugar";
    }

    @Override
    public double cost() {
        return 0.10 + beverage.cost();
    }
}

14.3.5 客户端代码

public class CoffeeShop {
    public static void main(String[] args) {
        // 订一杯 Espresso
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        // 加牛奶和糖
        beverage = new Milk(beverage);
        beverage = new Sugar(beverage);
        System.out.println(beverage.getDescription() + " $" + beverage.cost());
    }
}

14.3.6 运行结果:

Espresso $1.99
Espresso, Milk, Sugar $2.29

14.4. 装饰者模式的优缺点

14.4.1 优点:

  1. 动态扩展对象功能:无需通过创建子类,就可以动态地为对象增加额外的职责,保持了类的灵活性。
  2. 遵循单一职责原则:每个装饰者类只负责增加一种特定的功能,使得代码更具模块化和可维护性。
  3. 遵循开闭原则:无需修改已有的类,就能扩展其功能。

14.4.2 缺点:

  1. 大量小对象的创建:使用装饰者模式会增加对象数量,过多的装饰者类可能增加系统的复杂性。
  2. 调试困难:由于功能是通过多个装饰者层层叠加的,调试时很难跟踪对象的行为。

14.5 适用场景

  • 希望动态地给对象添加功能,且这些功能可以随意组合时。
  • 不希望通过继承扩展类的功能,而是通过组合来实现功能扩展。
  • 需要为不同的对象组合不同的行为,使得系统更具弹性和可扩展性。

14.6 装饰者模式 vs. 代理模式 vs. 适配器模式

  • 装饰者模式:用于扩展对象的功能,不改变其接口,主要通过动态组合来添加职责。
  • 代理模式:用于控制对象的访问,通常用于懒加载、权限控制等。
  • 适配器模式:用于将现有类的接口转换成另一种接口,以便不同接口的类能够协同工作。

14.7 总结

装饰者模式通过组合的方式为对象添加功能,避免了类的继承层次过深的问题。它非常适合在运行时根据需要动态扩展对象功能的场景,同时还能保持原有对象的结构与行为,确保灵活性与可扩展性。

15 状态模式

状态模式(State Pattern)是一种行为型设计模式,允许对象在其内部状态改变时,改变它的行为。状态模式将对象的行为与其状态进行解耦,通过状态对象来管理状态转移,从而使得状态的变化能够导致不同的行为表现。

在状态模式中,状态是对象的一个重要组成部分,对象在运行过程中可以动态地改变其状态,并且每个状态可以有不同的行为。这种模式有助于避免使用大量的条件语句来管理状态的变化。

15.1 状态模式的组成部分

  • 上下文(Context):维护一个当前状态,并负责根据状态执行行为。
  • 状态接口(State):定义状态的公共接口,每个具体状态类需要实现这个接口。
  • 具体状态类(Concrete State):实现状态接口,定义在特定状态下对象的行为。

15.2 状态模式的类图

+------------------+      +-------------------+
|     Context      |<---->|     State          |
+------------------+      +-------------------+
        ^                           ^
        |                           |
+-------------------+       +------------------+
| ConcreteStateA    |       | ConcreteStateB   |
+-------------------+       +------------------+
  • Context:上下文对象,它持有状态的引用,并且可以切换状态。
  • State:状态接口,定义了在某个状态下,如何响应用户请求。
  • ConcreteStateA、ConcreteStateB:具体状态类,表示对象在某种状态下的具体行为。

15.3 状态模式的示例

假设我们要模拟一个电梯的运行过程,电梯有三种状态:待机状态(Idle)、运行状态(Running)和暂停状态(Paused)。不同状态下,电梯的行为是不同的。

15.3.1 状态接口(State)

// 状态接口,定义电梯的行为
public interface ElevatorState {
    void pressButton(ElevatorContext context);
}

15.3.2 具体状态类(Concrete State)

// 待机状态
public class IdleState implements ElevatorState {
    @Override
    public void pressButton(ElevatorContext context) {
        System.out.println("Elevator is now running.");
        context.setState(new RunningState()); // 切换到运行状态
    }
}

// 运行状态
public class RunningState implements ElevatorState {
    @Override
    public void pressButton(ElevatorContext context) {
        System.out.println("Elevator is now paused.");
        context.setState(new PausedState()); // 切换到暂停状态
    }
}

// 暂停状态
public class PausedState implements ElevatorState {
    @Override
    public void pressButton(ElevatorContext context) {
        System.out.println("Elevator is now idle.");
        context.setState(new IdleState()); // 切换到待机状态
    }
}

15.3.3 上下文类(Context)

// 上下文类,维护当前状态
public class ElevatorContext {
    private ElevatorState currentState;

    public ElevatorContext() {
        currentState = new IdleState(); // 初始状态为待机状态
    }

    public void setState(ElevatorState state) {
        currentState = state;
    }

    public void pressButton() {
        currentState.pressButton(this);
    }
}

15.3.4 客户端代码

public class ElevatorSimulation {
    public static void main(String[] args) {
        ElevatorContext elevator = new ElevatorContext();

        // 模拟电梯状态变化
        elevator.pressButton(); // 电梯从待机状态到运行状态
        elevator.pressButton(); // 电梯从运行状态到暂停状态
        elevator.pressButton(); // 电梯从暂停状态回到待机状态
    }
}

15.3.5 运行结果:

Elevator is now running.
Elevator is now paused.
Elevator is now idle.

15.4 状态模式的优缺点

15.4.1 优点:

  1. 遵循开闭原则:状态和行为是通过状态类实现的,增加新状态时无需修改已有代码,只需添加新的状态类。
  2. 避免条件语句:将状态与行为封装在不同的类中,避免了大量的 if-elseswitch 语句。
  3. 易于扩展和维护:通过独立的状态类实现不同状态下的行为,代码逻辑更清晰,易于维护。
  4. 封装状态转换:状态的转换逻辑被封装在具体的状态类中,上下文类无需关心状态的变化逻辑。

15.4.2 缺点:

  1. 类的数量增多:每个状态都需要定义一个类,可能导致类的数量增加,增加系统的复杂度。
  2. 状态切换的复杂性:如果状态之间的关系复杂,状态转换的管理可能变得难以维护。

15.5 适用场景

  • 对象的行为依赖于状态变化:当对象在不同状态下有不同的行为时,可以使用状态模式。
  • 状态切换比较复杂:当状态转换多且复杂时,状态模式可以将状态管理的逻辑封装在具体的状态类中。
  • 需要避免条件语句:当对象的行为随着状态变化而变化,如果使用 if-elseswitch 会导致代码冗长时,状态模式是一个很好的解决方案。

15.6 状态模式 vs 策略模式

  • 状态模式:状态模式的状态转换是自动发生的,它内部会管理状态的切换,行为依赖于状态。比如电梯从运行到暂停状态的切换。
  • 策略模式:策略模式的行为选择是手动的,由客户端来决定使用哪个策略,通常用于不同算法的选择。

15.7 总结

状态模式通过将对象的状态与行为分离开来,使得对象能够在不同的状态下表现出不同的行为,同时避免了大量条件语句的使用。它使得代码更加灵活且容易扩展,适用于那些状态变化复杂且行为与状态强相关的场景。