Bootstrap

浅谈Java中的24种设计模式(详细完整版)

Java中的24种设计模式

创建型模式(共6种)

1. 简单工厂模式(Simple Factory Pattern)

2. 工厂方法模式(Factory Method Pattern)

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

4. 单例模式(Singleton Pattern)

5. 建造者模式(Builder Pattern)

6. 原型模式(Prototype Pattern)

结构型模式(共7种)

1. 适配器模式(Adapter Pattern)

2. 装饰器模式(Decorator Pattern)

3. 代理模式(Proxy Pattern)

4. 外观模式(Facade Pattern)

5. 桥接模式(Bridge Pattern)

6. 组合模式(Composite Pattern)

7. 享元模式(Flyweight Pattern)

行为型模式(共11种)

1. 策略模式(Strategy Pattern)

2. 模板方法模式(Template Method Pattern)

3. 观察者模式(Observer Pattern)

4. 迭代子模式(Iterator Pattern)

5. 责任链模式(Chain of Responsibility Pattern)

6. 命令模式(Command Pattern)

7. 备忘录模式(Memento Pattern)

8. 状态模式(State Pattern)

9. 访问者模式(Visitor Pattern)

10. 中介者模式(Mediator Pattern)

11. 解释器模式(Interpreter Pattern)

引子

设计模式是很多程序员总结出来的最佳实践。曾经在刚开始写项目的时候学习过设计模式,在开发过程中,也主动或者被动的使用过。现在写代码虽说不会特意明确在用哪种设计模式,但潜移默化的写出来公认的最佳实践代码,毕竟看的比较清爽。为什么再看一遍设计模式,主要有几个原因:第一,很多优秀的源码基本都使用了设计模式,明确设计模式能够更好的看源码。第二,很多中间件设计理念也是基于设计模式的,还有其他的语言,都有自己的设计最佳实践。对于我来说,设计模式始于java,不止于java。第三,有了这种规范,可以更好的和他人沟通,言简意赅。

设计模式原则

很多优秀的文章和书籍都讲的很明白了,我说下自己的体会。 1.单一职责原则,就是一个类只负责做一件事情。这样就可以做到解耦合的效果,让代码看起来比较清爽,也体现了java的封装性。还有个原则叫迪米特法则,就是一个对象对另一个对象有尽量少的了解,说的也是解耦合的事情。 2.里氏替换原则和依赖倒置原则,说的是继承的事情。父类可以做的事情,子类都可以去做,子类可以尽量去依赖父类去做事情;但是反过来,父类不能依赖子类去做一些事情。体现了java的继承特性。 3.接口隔离原则,接口也应该尽可能的隔离开来。其实类写多了,的确耦合性低,为了让他们交流起来,用的最多的就是接口,毕竟只需要知道做什么,怎么做,去访问那个具体的类吧。 4.开闭原则,对修改关闭,对拓展开放。就是代码需要有很好的延展性,对原有代码结构不能破坏。

1、创建型模式

1.1 简单工厂模式

​ 简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式。在简单工厂模式中,我们创建一个工厂类,该类可以根据传入的参数来返回不同类的对象实例。

以下是一个使用Java编写的简单工厂模式的示例:

1. 产品接口
首先,我们定义一个产品接口,它将是我们想要创建的对象类型的公共接口。

// 产品接口  
public interface Product {  
    void use();  
}

2. 具体产品类
接着,我们实现几个具体的产品类,它们将实现上述产品接口。

// 具体产品类 A  
public class ProductA implements Product {  
    @Override  
    public void use() {  
        System.out.println("使用产品A");  
    }  
}  

3. 工厂类
然后,我们创建一个工厂类,它包含静态方法来创建产品对象。

// 工厂类  
public class SimpleFactory {  
    // 静态方法,根据传入的参数返回不同的产品实例  
    public static Product createProduct(String type) {  
        if ("A".equals(type)) {  
            return new ProductA();  
        } else if ("B".equals(type)) {  
            return new ProductB();  
        } else {  
            return null; // 或者抛出异常  
        }  
    }  
}

4. 客户端代码
最后,在客户端代码中,我们使用工厂类来创建产品对象。

public class Client {  
    public static void main(String[] args) {  
        // 使用工厂类创建产品A  
        Product productA = SimpleFactory.createProduct("A");  
        productA.use(); // 输出:使用产品A  
  
        // 使用工厂类创建产品B  
        Product productB = SimpleFactory.createProduct("B");  
        productB.use(); // 输出:使用产品B  
  
        // 尝试创建未知类型的产品,这里会返回null或者抛出异常(取决于工厂类的实现)  
        Product unknownProduct = SimpleFactory.createProduct("C");  
        if (unknownProduct != null) {  
            unknownProduct.use();  
        } else {  
            System.out.println("未知类型的产品");  
        }  
    }  
}

说明

  • 优点:客户端代码无需知道产品类的具体实现,只需要知道产品接口即可;降低了客户端代码与具体产品类的耦合度;提高了系统的可扩展性,当需要添加新产品时,只需要在工厂类中增加相应的逻辑即可,无需修改客户端代码。
  • 缺点:工厂类集中了所有产品创建逻辑,违反了高内聚责任分配原则,一旦工厂类出现问题,将影响整个系统的稳定性;如果产品类型非常多,会造成工厂类逻辑过于复杂,不利于系统的维护和扩展。
  • 适用场景:当系统中需要创建的对象数量较少,且对象的创建逻辑相同时,可以使用简单工厂模式来统一管理和创建对象。

1.2 工厂方法模式

​ 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使得一个类的实例化延迟到其子类。

以下是使用Java编写的工厂方法模式的示例代码及说明:

1. 产品接口

首先,我们定义一个产品接口,这将是我们要创建的对象类型的公共接口。

// 产品接口  
public interface Product {  
    void use();  
}

2. 具体产品类

接着,我们实现几个具体的产品类,它们将实现上述产品接口。

// 具体产品类 A  
public class ProductA implements Product {  
    @Override  
    public void use() {  
        System.out.println("使用产品A");  
    }  
}  
  
// 具体产品类 B  
public class ProductB implements Product {  
    @Override  
    public void use() {  
        System.out.println("使用产品B");  
    }  
}

3. 抽象工厂类

然后,我们创建一个抽象工厂类,它声明了一个工厂方法,但并未实现它。子类将提供该工厂方法的具体实现。

// 抽象工厂类  
public abstract class Creator {  
    // 工厂方法,声明为抽象方法,由子类实现  
    public abstract Product factoryMethod();  
  
    // 模板方法,调用工厂方法创建产品对象并使用  
    public void createAndUseProduct() {  
        Product product = factoryMethod();  
        product.use();  
    }  
}

4. 具体工厂类

我们创建具体工厂类,它们继承自抽象工厂类,并实现工厂方法以返回具体的产品实例。

// 具体工厂类 A  
public class CreatorA extends Creator {  
    @Override  
    public Product factoryMethod() {  
        return new ProductA();  
    }  
}  
  
// 具体工厂类 B  
public class CreatorB extends Creator {  
    @Override  
    public Product factoryMethod() {  
        return new ProductB();  
    }  
}

5. 客户端代码

最后,在客户端代码中,我们使用具体工厂类来创建产品对象,并调用模板方法来使用它。

public class Client {  
    public static void main(String[] args) {  
        // 使用具体工厂类A创建产品  
        Creator creatorA = new CreatorA();  
        creatorA.createAndUseProduct(); // 输出:使用产品A  
  
        // 使用具体工厂类B创建产品  
        Creator creatorB = new CreatorB();  
        creatorB.createAndUseProduct(); // 输出:使用产品B  
    }  
}

说明

  • 优点:在工厂方法模式中,客户端代码不再负责对象的创建,而是将这一责任转移给了具体的工厂类。这使得系统更易于扩展,因为当需要添加新产品时,只需要添加新的具体产品类和相应的具体工厂类即可,而无需修改已有的客户端代码。

  • 缺点:当产品类较多时,可能需要定义大量的具体工厂类,这可能会增加系统的复杂性。

  • 适用场景:当需要创建的对象具有共同的接口,但创建对象的具体实现因条件而异时,可以使用工厂方法模式。此外,当系统需要扩展时,使用工厂方法模式可以避免修改已有的客户端代码。

1.3 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种方式来封装一组具有共同主题的单个工厂接口,以创建一系列相互关联或相互依赖的产品族。

以下是使用Java编写的抽象工厂模式的示例代码及说明:

1. 产品接口

首先,我们定义两组产品的接口。

// 产品接口A  
public interface ProductA {  
    void use();  
}  
  
// 产品接口B  
public interface ProductB {  
    void assemble();  
}

2. 具体产品类

接着,我们实现这些接口的具体产品类。

// 具体产品类A1  
public class ProductA1 implements ProductA {  
    @Override  
    public void use() {  
        System.out.println("使用产品A1");  
    }  
}  
  
// 具体产品类A2  
// 假设存在另一个与A1相关但不同的产品A2  
  
// 具体产品类B1  
public class ProductB1 implements ProductB {  
    @Override  
    public void assemble() {  
        System.out.println("组装产品B1");  
    }  
}  
  
// 具体产品类B2  
// 假设存在另一个与B1相关但不同的产品B2

3. 抽象工厂接口

然后,我们定义一个抽象工厂接口,它声明了一组用于创建产品的方法。

// 抽象工厂接口  
public interface AbstractFactory {  
    ProductA createProductA();  
    ProductB createProductB();  
}

4. 具体工厂类

我们创建具体工厂类,它们实现抽象工厂接口,并返回具体的产品实例。

// 具体工厂类1  
public class ConcreteFactory1 implements AbstractFactory {  
    @Override  
    public ProductA createProductA() {  
        return new ProductA1();  
    }  
  
    @Override  
    public ProductB createProductB() {  
        return new ProductB1();  
    }  
}  
  
// 具体工厂类2  
// 假设存在另一个与ConcreteFactory1不同的具体工厂类,它返回与A1和B1不同的产品实例

5. 客户端代码

最后,在客户端代码中,我们使用具体工厂类来创建产品对象。

public class Client {  
    public static void main(String[] args) {  
        // 创建具体工厂实例  
        AbstractFactory factory = new ConcreteFactory1();  
  
        // 使用工厂创建产品  
        ProductA productA = factory.createProductA();  
        ProductB productB = factory.createProductB();  
  
        // 使用产品  
        productA.use(); // 输出:使用产品A1  
        productB.assemble(); // 输出:组装产品B1  
  
        // 如果需要,可以更换工厂来创建不同的产品族  
        // AbstractFactory anotherFactory = new ConcreteFactory2();  
        // ...  
    }  
}

说明

  • 优点:抽象工厂模式允许客户端代码在不知道具体产品类的情况下创建一系列相互关联的产品对象。这增加了系统的灵活性和可扩展性,因为客户端代码不需要关心产品的具体实现细节。
  • 缺点:当产品族中需要增加新的产品时,所有的工厂类都需要进行修改,这可能会增加系统的复杂性。此外,如果系统中只存在单一的产品族,那么使用抽象工厂模式可能会带来不必要的复杂性。
  • 适用场景:当系统需要创建一系列相互关联或相互依赖的产品族时,抽象工厂模式非常有用。例如,在图形用户界面(GUI)工具包中,可能需要创建按钮、文本框、滚动条等控件,这些控件可以组成一个控件族,使用抽象工厂模式可以方便地创建和管理这些控件。

1.4 单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。以下是几种常见的单例模式实现方式及其代码和说明。

1.4.1 饿汉式(静态常量)
public class Singleton {  
    // 静态常量,类加载时即初始化  
    private static final Singleton INSTANCE = new Singleton();  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点  
    public static Singleton getInstance() {  
        return INSTANCE;  
    }  
}

说明

  • 这种方式在类加载时就完成了实例化,避免了线程同步问题。
  • 类加载较慢,但获取对象的速度快。
  • 由于 JVM 在加载类时只会加载一次,所以它是线程安全的。
1.4.2 饿汉式(静态代码块)
public class Singleton {  
    // 静态常量,静态代码块中初始化  
    private static final Singleton INSTANCE;  
  
    static {  
        INSTANCE = new Singleton();  
    }  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点  
    public static Singleton getInstance() {  
        return INSTANCE;  
    }  
}

说明

  • 这种方式和静态常量方式类似,只不过是在静态代码块中初始化实例。
  • 同样在类加载时即完成了实例化,线程安全。
1.4.3 懒汉式(线程不安全)
public class Singleton {  
    // 静态变量,延迟初始化  
    private static Singleton instance;  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

说明

  • 这种方式在第一次调用 getInstance() 方法时才进行实例化,实现了延迟加载。
  • 但是它在多线程环境下是不安全的,可能会创建多个实例。
1.4…4 懒汉式(线程安全,同步方法)
public class Singleton {  
    // 静态变量,延迟初始化  
    private static Singleton instance;  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点,同步方法  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

说明

  • 这种方式通过 synchronized 关键字实现了线程安全,但是效率较低,每次调用 getInstance() 都需要同步。
1.4.5 懒汉式(双重检查锁定/DCL,推荐)
public class Singleton {  
    // volatile 关键字确保多线程的正确性  
    private volatile static Singleton instance;  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}

说明

  • 这种方式称为双重检查锁定(Double-Check Locking, DCL),是懒汉式单例模式的最佳实现方式。
  • 使用 volatile 关键字来确保 instance 在多线程环境中的可见性,避免指令重排序导致的问题。
  • 第一次检查 instance == null 避免不必要的同步,第二次检查 instance == null 是在同步块中进行的,确保单例性。
1.4.6 静态内部类(推荐使用)
public class Singleton {  
    // 静态内部类,延迟加载  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
  
    // 私有构造方法,防止外部实例化  
    private Singleton() {}  
  
    // 提供全局访问点  
    public static Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

说明

  • 这种方式利用了 classloader 的机制来保证初始化 SingletonHolder 类时只有一个线程,从而确保 INSTANCE 的唯一性。
  • 既实现了延迟加载
1.4.7 枚举(推荐使用)
class SingletonEnum{
    // 1、创建一个枚举
    public enum CreateInstance{
        // 创建一个枚举实例
        INSTANCE;
        // 创建一个指向SingletomEnum的变量
        private SingletonEnum instance;

        // 2、私有化构造器,保证不能在类外部通过new构造器来构造对象
        private CreateInstance() {
            instance = new SingletonEnum();
            System.out.println(Thread.currentThread().getName());
        }

        // 3、创建一个公共的方法,由实例调用返回单例类
        public SingletonEnum getInstance() {
            return instance;
        }
    }
}

说明

  • 创建枚举默认就是线程安全的,不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象,枚举让JVM来帮我们保证线程安全和单一实例的问题,是JDK1.5版本后最适合用于创建单例设计模式的方法,是唯一一种不会被反射破坏单例状态的模式

1.5 建造者模式

建造者模式(Builder Pattern)是一种对象构建的设计模式,它允许你通过逐步构建复杂对象的不同部分来表示这些对象。一个 Builder 类会逐步构造最终的对象。该模式将对象的构建与其表示分离开来,使得同样的构建过程可以创建不同的表示。

以下是一个简单的 Java 示例,展示了如何使用建造者模式来构建一个用户(User)对象:

// 产品类(User)  
public class User {  
    private String name;  
    private int age;  
    private String address;  
  
    // 私有构造方法,禁止外部直接实例化  
    private User(Builder builder) {  
        this.name = builder.name;  
        this.age = builder.age;  
        this.address = builder.address;  
    }  
  
    // 静态内部类(Builder)  
    public static class Builder {  
        private String name;  
        private int age;  
        private String address;  
  
        // 链式调用方法设置属性值  
        public Builder setName(String name) {  
            this.name = name;  
            return this;  
        }  
  
        public Builder setAge(int age) {  
            this.age = age;  
            return this;  
        }  
  
        public Builder setAddress(String address) {  
            this.address = address;  
            return this;  
        }  
  
        // 构建并返回User对象  
        public User build() {  
            return new User(this);  
        }  
    }  
  
    // getter 方法(省略了setter,因为User是不可变的)  
    public String getName() {  
        return name;  
    }  
  
    public int getAge() {  
        return age;  
    }  
  
    public String getAddress() {  
        return address;  
    }  
  
    // toString 方法(省略了其他实现细节)  
    @Override  
    public String toString() {  
        return "User{" +  
                "name='" + name + '\'' +  
                ", age=" + age +  
                ", address='" + address + '\'' +  
                '}';  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        // 使用建造者构建User对象  
        User user = new User.Builder()  
                .setName("John Doe")  
                .setAge(30)  
                .setAddress("123 Main St")  
                .build();  
  
        // 打印User对象  
        System.out.println(user);  
    }  
}

说明

  • 产品类(User):这是需要被构建的对象。它包含一个私有的构造方法,该方法只接受一个 Builder 类型的参数。这确保了只能通过 Builder 来创建 User 对象。
  • 建造者类(Builder):它是一个静态内部类,用于逐步构建 User 对象。它包含了所有需要设置的 User 对象的属性,以及链式调用的 setter 方法(返回 Builder 类型本身以支持链式调用)。最后,它提供了一个 build() 方法,该方法使用当前设置的属性来构建并返回一个新的 User 对象。
  • 客户端代码(Client):客户端代码使用 Builder 来设置 User 对象的属性,并通过调用 build() 方法来获取构建好的 User 对象。这种方式使得代码更加清晰和易于阅读,尤其是当构建过程涉及到多个步骤和参数时。

1.6 原型模式

原型模式(Prototype Pattern)是一种创建型设计模式,它使用原型实例指定要创建对象的种类,并且通过复制这个原型来创建新的对象。在 Java 中,通常通过实现 Cloneable 接口并覆盖 clone() 方法来实现原型模式。

以下是原型模式的两种常见类型的具体代码及说明:

1.6.1 浅复制(Shallow Copy)

浅复制仅复制对象的基本数据类型和引用类型(但不复制引用类型所指向的对象本身)。

public class Prototype implements Cloneable {  
    private String name;  
    private List<String> items;  
  
    public Prototype(String name) {  
        this.name = name;  
        this.items = new ArrayList<>();  
    }  
  
    // Getter and Setter methods (omitted for brevity)  
  
    @Override  
    protected Prototype clone() throws CloneNotSupportedException {  
        return (Prototype) super.clone(); // 浅复制  
    }  
  
    public static void main(String[] args) throws CloneNotSupportedException {  
        Prototype prototype = new Prototype("Original");  
        prototype.getItems().add("Item 1");  
  
        Prototype cloned = prototype.clone();  
        cloned.getItems().add("Item 2"); // 这会影响到原始对象,因为它们引用同一个列表  
  
        System.out.println("Prototype items: " + prototype.getItems()); // [Item 1, Item 2]  
        System.out.println("Cloned items: " + cloned.getItems()); // [Item 1, Item 2]  
    }  
}

说明

  • 浅复制只复制了对象的引用,而没有复制引用的对象。在上述例子中,prototypecloned 对象中的 items 引用指向同一个列表。
1.6.2 深复制(Deep Copy)

深复制不仅复制对象的基本数据类型,还复制对象的引用类型及其所指向的对象本身,直到对象的所有层次都被复制。

import java.io.*;  
  
public class DeepCloneablePrototype implements Serializable {  
    private String name;  
    private List<String> items;  
  
    // Constructor, Getter and Setter methods (omitted for brevity)  
  
    // 实现深复制的方法,通过序列化和反序列化实现  
    public DeepCloneablePrototype deepClone() throws IOException, ClassNotFoundException {  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return (DeepCloneablePrototype) ois.readObject();  
    }  
  
    public static void main(String[] args) throws Exception {  
        DeepCloneablePrototype prototype = new DeepCloneablePrototype("Original");  
        prototype.getItems().add("Item 1");  
  
        DeepCloneablePrototype cloned = prototype.deepClone();  
        cloned.getItems().add("Item 2"); // 这不会影响到原始对象,因为列表已经被复制  
  
        System.out.println("Prototype items: " + prototype.getItems()); // [Item 1]  
        System.out.println("Cloned items: " + cloned.getItems()); // [Item 1, Item 2]  
    }  
}

说明

  • 深复制通过序列化(将对象转换为字节序列)和反序列化(从字节序列恢复对象)来实现。这种方式可以确保所有层次的引用都被复制,生成的对象与原始对象完全独立。但请注意,序列化需要对象及其所有引用的对象都实现 Serializable 接口。此外,深复制通常比浅复制更耗时和资源密集。

2、 结构型模式

2.1 适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许一个类的接口与客户端期望的接口不兼容的对象协同工作。适配器模式将类的接口转换成客户端所期望的另一种接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。

下面是一个简单的适配器模式的实现,它演示了如何将一个旧的接口(Target)适配到新的接口(Adaptee)上。

接口定义

// 客户端使用的接口  
public interface Target {  
    void request();  
}  
  
// 需要适配的类的接口  
public class Adaptee {  
    public void specificRequest() {  
        System.out.println("Called specificRequest()");  
    }  
}

适配器类

// 适配器类,将Adaptee的接口转换成Target的接口  
public class Adapter implements Target {  
    private Adaptee adaptee;  
  
    public Adapter(Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  
  
    @Override  
    public void request() {  
        adaptee.specificRequest(); // 调用Adaptee的方法,实现转换  
    }  
}

**客户端代码 **

public class Client {  
    public static void main(String[] args) {  
        // 创建Adaptee对象  
        Adaptee adaptee = new Adaptee();  
  
        // 创建适配器对象,并将Adaptee对象作为参数传入  
        Target target = new Adapter(adaptee);  
  
        // 客户端通过适配器调用request()方法  
        target.request(); // 输出:Called specificRequest()  
    }  
}

说明

  • Target 接口是客户端所期望的接口,客户端使用 Target 接口来调用方法。
  • Adaptee 类是已经存在的类,它的接口与 Target 不兼容。
  • Adapter 类是适配器类,它实现了 Target 接口,并持有 Adaptee 类的实例。在 Adapter 类中,通过调用 Adaptee 类的方法来实现 Target 接口的方法。
  • 在客户端代码中,我们通过创建一个 Adapter 对象(并将 Adaptee 对象作为参数传入)来使用 Target 接口,从而间接地调用了 Adaptee 类的 specificRequest() 方法。

适配器模式使得我们可以将不兼容的接口转换为兼容的接口,从而提高了代码的复用性和灵活性。在实际开发中,适配器模式常用于解决遗留代码与新系统之间的接口不匹配问题。

2.2 装饰器模式

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。

装饰器模式通常包含以下几个角色:

  • 抽象组件(Component):定义了一个对象的接口,可以给这些对象动态地添加职责。
  • 具体组件(ConcreteComponent):实现了抽象组件接口,是我们要给其添加职责的对象。
  • 装饰器(Decorator):持有一个指向抽象组件的引用,并定义了一个与抽象组件一致的接口。
  • 具体装饰器(ConcreteDecorator):实现了装饰器接口,并给组件添加具体的职责。

以下是装饰器模式的一个简单示例:

装饰器模式代码

抽象组件(Component)

public interface Coffee {  
    double getCost();  
    String getIngredients();  
}

具体组件(ConcreteComponent)

public class SimpleCoffee implements Coffee {  
    @Override  
    public double getCost() {  
        return 1.99;  
    }  
  
    @Override  
    public String getIngredients() {  
        return "Coffee";  
    }  
}

装饰器(Decorator)

public abstract class CoffeeDecorator implements Coffee {  
    protected Coffee decoratedCoffee;  
  
    public CoffeeDecorator(Coffee coffee) {  
        this.decoratedCoffee = coffee;  
    }  
  
    @Override  
    public double getCost() {  
        return decoratedCoffee.getCost();  
    }  
  
    @Override  
    public String getIngredients() {  
        return decoratedCoffee.getIngredients();  
    }  
}

具体装饰器(ConcreteDecorator)

public class Milk extends CoffeeDecorator {  
    public Milk(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public double getCost() {  
        return super.getCost() + 0.30;  
    }  
  
    @Override  
    public String getIngredients() {  
        return super.getIngredients() + ", Milk";  
    }  
}  
  
public class WhippedCream extends CoffeeDecorator {  
    public WhippedCream(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public double getCost() {  
        return super.getCost() + 0.50;  
    }  
  
    @Override  
    public String getIngredients() {  
        return super.getIngredients() + ", Whipped Cream";  
    }  
}

客户端代码

public class CoffeeShop {  
    public static void main(String[] args) {  
        Coffee coffee = new SimpleCoffee();  
        System.out.println("Coffee: $" + coffee.getCost() + ", " + coffee.getIngredients());  
  
        Coffee coffeeWithMilk = new Milk(coffee);  
        System.out.println("Coffee with Milk: $" + coffeeWithMilk.getCost() + ", " + coffeeWithMilk.getIngredients());  
  
        Coffee coffeeWithExtras = new WhippedCream(new Milk(coffee));  
        System.out.println("Coffee with Milk and Whipped Cream: $" + coffeeWithExtras.getCost() + ", " + coffeeWithExtras.getIngredients());  
    }  
}

说明

  • 在这个例子中,Coffee 是一个抽象组件接口,它定义了咖啡的价格和配料。SimpleCoffee 是一个具体组件,实现了基本的咖啡。
  • CoffeeDecorator 是一个装饰器类,它持有一个 Coffee 对象的引用,并提供了默认的实现,即转发请求给被装饰的对象。
  • MilkWhippedCream 是具体装饰器,它们分别给咖啡添加了牛奶和奶泡,并修改了价格和配料信息。
  • 在客户端代码中,我们通过将具体装饰器包装在一起,动态地给咖啡添加配料,并计算最终的价格和配料列表。这种方式比通过继承来扩展功能更加灵活,因为我们可以根据需要添加任意数量的装饰器。

2.3 代理模式

代理模式(Proxy Pattern)是一种设计模式,它提供了一个代理类来控制对另一个对象的访问。代理类可以执行一些额外的操作,如访问控制、日志记录、事务管理、缓存等,而无需修改原始对象的代码。

代理模式主要有三种类型:远程代理、虚拟代理和保护代理。但值得注意的是,这些类型的区分并不是绝对的,它们之间的界限相对模糊,因为代理模式的实现方式可以根据具体需求而变化。

以下是对每种类型代理模式的简单代码示例和说明:

2.3.1 远程代理(Remote Proxy)

远程代理用于在本地应用中表示一个位于不同地址空间(如远程机器上)的对象。通常,远程代理通过网络与远程对象进行通信。

代码示例(使用假设的远程通信技术):

// 远程接口  
interface RemoteService {  
    void doSomething();  
}  
  
// 远程服务实现(通常在远程机器上)  
class RealRemoteService implements RemoteService {  
    @Override  
    public void doSomething() {  
        System.out.println("Remote service doing something...");  
    }  
}  
  
// 远程代理  
class RemoteServiceProxy implements RemoteService {  
    private RemoteService realService; // 假设有方法能够获取或连接到远程服务实例  
  
    @Override  
    public void doSomething() {  
        // 在这里可以进行网络请求、序列化/反序列化等操作  
        // 假设realService是远程服务的本地代理或存根  
        realService.doSomething();  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        RemoteService proxy = new RemoteServiceProxy(); // 使用代理与远程服务交互  
        proxy.doSomething(); // 实际上是在与远程服务通信  
    }  
}
2.3.2 虚拟代理(Virtual Proxy)

虚拟代理用于控制对资源密集型对象的访问。它通常用来延迟对象的实例化,直到真正需要该对象时为止。

// 目标接口  
interface Image {  
    void display();  
}  
  
// 真实图像类(资源密集型)  
class RealImage implements Image {  
    private final String filename;  
  
    public RealImage(String filename) {  
        this.filename = filename;  
        loadFromDisk(filename); // 假设这是一个耗时的操作  
    }  
  
    private void loadFromDisk(String filename) {  
        // 模拟从磁盘加载图像  
        System.out.println("Loading " + filename);  
    }  
  
    @Override  
    public void display() {  
        System.out.println("Displaying " + filename);  
    }  
}  
  
// 图像代理类  
class ImageProxy implements Image {  
    private RealImage realImage;  
    private String filename;  
  
    public ImageProxy(String filename) {  
        this.filename = filename;  
    }  
  
    @Override  
    public void display() {  
        if (realImage == null) {  
            realImage = new RealImage(filename);  
        }  
        realImage.display();  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        ImageProxy proxy = new ImageProxy("test.jpg"); // 代理被实例化,但RealImage未被加载  
        proxy.display(); // RealImage在此时被加载和显示  
    }  
}
2.3.3 保护代理(Protecting Proxy)

保护代理用于控制对原始对象的访问权限。它可以检查调用者是否具有访问原始对象的权限,或者根据需要对原始对象进行访问限制。

// 目标接口  
interface User {  
    void doSomething();  
}  
  
// 用户实现类  
class RealUser implements User {  
    @Override  
    public void doSomething() {  
        System.out.println("User is doing something...");  
    }  
}  
  
// 保护代理类  
class ProtectingProxy implements User {  
    private User realUser;  
  
    public ProtectingProxy(User realUser) {  
        this.realUser = realUser;  
    }  
  
    @Override  
    public void doSomething() {  
        // 在这里可以添加权限检查逻辑  
        if (hasPermission()) { // 假设hasPermission()是一个检查权限的方法  
            realUser.doSomething();  
        } else {  
            System.out.println("Permission denied.");  
        }  
    }  
  
    private boolean hasPermission() {  
        // 模拟权限检查逻辑  
        return true; // 这里应该根据具体逻辑返回true或false  
    }  
}  
  
// 客户端
public class Client {  
    public static void main(String[] args) {  
        User user = new RealUser();
        ProtectingProxy proxy = new ProtectingProxy(user);  
        proxy.doSomething();  
    }  
}
2.3.4 智能引用代理(Smart Reference Proxy)

智能引用代理用于管理对原始对象的引用计数,以便在适当的时候释放资源(如内存中的对象)。这在垃圾回收不是即时或不保证的环境中可能很有用。

(由于Java的垃圾回收机制,通常不需要显式实现智能引用代理)

2.3.5 缓存代理(Caching Proxy)

缓存代理用于缓存对目标对象的计算结果或数据,以减少对原始对象的调用次数。

// 目标接口  
interface DataService {  
    String getData(String key);  
}  
  
// 真实数据服务实现  
class RealDataService implements DataService {  
    @Override  
    public String getData(String key) {  
        // 假设这里有一些复杂或耗时的操作来获取数据  
        // ...  
        return "Data for " + key;  
    }  
}  
  
// 缓存代理  
class CachingProxy implements DataService {  
    private DataService realService;  
    private Map<String, String> cache = new HashMap<>();  
  
    public CachingProxy(DataService realService) {  
        this.realService = realService;  
    }  
  
    @Override  
    public String getData(String key) {  
        if (cache.containsKey(key)) {  
            return cache.get(key);  
        }  
        String data = realService.getData(key);  
        cache.put(key, data);  
        return data;  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        DataService proxy = new CachingProxy(new RealDataService());  
        System.out.println(proxy.getData("key1")); //
    }
}

2.4 外观模式

外观模式(Facade Pattern)定义了一个高层接口,这个接口使得子系统更加容易使用。外观模式为子系统中的一组接口提供了一个统一的高层接口,使得子系统更加容易使用。当你想为一个复杂子系统提供一个简单接口时,可以使用外观模式。

// 子系统接口A  
interface SubsystemA {  
    void operationA();  
}  
  
// 子系统A的具体实现  
class SubsystemAImpl implements SubsystemA {  
    @Override  
    public void operationA() {  
        System.out.println("Subsystem A operation executed.");  
    }  
}  
  
// 子系统接口B  
interface SubsystemB {  
    void operationB();  
}  
  
// 子系统B的具体实现  
class SubsystemBImpl implements SubsystemB {  
    @Override  
    public void operationB() {  
        System.out.println("Subsystem B operation executed.");  
    }  
}  
  
// 外观类,为复杂的子系统提供简单接口  
class Facade {  
    private SubsystemA subsystemA;  
    private SubsystemB subsystemB;  
  
    public Facade() {  
        subsystemA = new SubsystemAImpl();  
        subsystemB = new SubsystemBImpl();  
    }  
  
    // 提供一个简单的操作,可能调用了多个子系统的方法  
    public void performTask() {  
        subsystemA.operationA();  
        subsystemB.operationB();  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        Facade facade = new Facade();  
        // 客户端只需要与外观类交互,而不需要直接与子系统交互  
        facade.performTask();  
    }  
}

说明:

  • 在上面的代码中,我们有两个子系统SubsystemASubsystemB,每个子系统都有一个具体的实现。客户端与这两个子系统交互可能会很复杂,因为需要知道如何与每个子系统交互。为了简化这个过程,我们引入了Facade类,它提供了performTask()方法,该方法内部调用了两个子系统的操作。客户端现在只需要与Facade类交互,而不需要直接与子系统交互,从而简化了客户端代码。

2.5 桥接模式

桥接模式(Bridge Pattern)是一种将抽象部分与实现部分分离,使它们都可以独立地变化的结构型设计模式。在这种模式中,抽象部分(抽象类)与实现部分(实现类)的接口是分开的,它们之间通过桥接接口进行通信。

// 桥接接口(接口化实现部分)  
interface Implementor {  
    void operation();  
}  
  
// 实现部分的具体实现A  
class ConcreteImplementorA implements Implementor {  
    @Override  
    public void operation() {  
        System.out.println("ConcreteImplementorA operation.");  
    }  
}  
  
// 实现部分的具体实现B  
class ConcreteImplementorB implements Implementor {  
    @Override  
    public void operation() {  
        System.out.println("ConcreteImplementorB operation.");  
    }  
}  
  
// 抽象部分(抽象类)  
abstract class Abstraction {  
    // 聚合关系,持有实现部分的接口  
    protected Implementor implementor;  
  
    // 构造函数注入实现部分  
    public Abstraction(Implementor implementor) {  
        this.implementor = implementor;  
    }  
  
    // 抽象部分的抽象方法,调用实现部分的方法  
    public void operation() {  
        implementor.operation();  
    }  
}  
  
// 抽象部分的具体实现(扩展抽象类)  
class RefinedAbstraction extends Abstraction {  
    public RefinedAbstraction(Implementor implementor) {  
        super(implementor);  
    }  
  
    // 可以在此添加额外的逻辑或修改行为  
    @Override  
    public void operation() {  
        super.operation(); // 调用抽象部分的抽象方法  
        System.out.println("RefinedAbstraction operation.");  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        // 创建实现部分的对象  
        Implementor implementorA = new ConcreteImplementorA();  
        Implementor implementorB = new ConcreteImplementorB();  
  
        // 创建抽象部分的对象,并传入实现部分的对象  
        Abstraction abstractionA = new RefinedAbstraction(implementorA);  
        Abstraction abstractionB = new RefinedAbstraction(implementorB);  
  
        // 客户端调用抽象部分的方法  
        abstractionA.operation();  
        abstractionB.operation();  
    }  
}

说明:

  • 在这个例子中,Implementor 是桥接接口,ConcreteImplementorAConcreteImplementorB 是实现这个接口的具体类。Abstraction 是抽象部分,其中包含一个 Implementor 类型的成员变量,并且有一个抽象方法 operation()RefinedAbstractionAbstraction 的子类,它提供了 operation() 方法的更具体实现,并且可以在其中添加额外的逻辑。
  • 在客户端代码中,我们创建了实现部分和抽象部分的对象,并将实现部分的对象传递给抽象部分的对象。然后,客户端通过抽象部分的对象调用 operation() 方法,该方法会调用实现部分的方法。这样,抽象部分和实现部分就通过桥接接口解耦了,它们可以独立地变化。

2.6 组合模式

组合模式(Composite Pattern)允许你将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

// 组件接口  
interface Component {  
    void operation();  
    void add(Component component);  
    void remove(Component component);  
    Component getChild(int index);  
}  
  
// 叶子节点类  
class Leaf implements Component {  
    private String name;  
  
    public Leaf(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void operation() {  
        System.out.println("Leaf: " + name + " operation.");  
    }  
  
    // 对于叶子节点,添加、删除和获取子节点的方法没有实际意义,但为了符合接口,可以抛出异常或为空实现  
    @Override  
    public void add(Component component) {  
        throw new UnsupportedOperationException("Leaf cannot have children.");  
    }  
  
    @Override  
    public void remove(Component component) {  
        throw new UnsupportedOperationException("Leaf cannot remove children.");  
    }  
  
    @Override  
    public Component getChild(int index) {  
        throw new UnsupportedOperationException("Leaf cannot have children.");  
    }  
}  
  
// 组合节点类  
class Composite implements Component {  
    private List<Component> children = new ArrayList<>();  
  
    @Override  
    public void operation() {  
        // 可以在这里添加组合节点的操作逻辑  
        System.out.println("Composite operation.");  
  
        // 递归调用子节点的操作  
        for (Component child : children) {  
            child.operation();  
        }  
    }  
  
    @Override  
    public void add(Component component) {  
        children.add(component);  
    }  
  
    @Override  
    public void remove(Component component) {  
        children.remove(component);  
    }  
  
    @Override  
    public Component getChild(int index) {  
        return children.get(index);  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        // 创建组合节点和叶子节点  
        Composite root = new Composite();  
        Composite comp = new Composite();  
        Leaf leaf1 = new Leaf("Leaf A");  
        Leaf leaf2 = new Leaf("Leaf B");  
  
        // 构建树形结构  
        root.add(comp);  
        root.add(leaf1);  
        comp.add(leaf2);  
  
        // 客户端调用操作  
        root.operation();  
    }  
}

说明:

  • 在这个例子中,Component 接口定义了所有组件(包括叶子节点和组合节点)共有的方法。Leaf 类实现了 Component 接口,并实现了 operation() 方法(对于叶子节点来说,添加、删除和获取子节点的方法没有实际意义)。Composite 类也实现了 Component 接口,并且包含了 List<Component> 来存储子节点。在 Composite 类的 operation() 方法中,我们递归地调用了所有子节点的 operation() 方法。
  • 在客户端代码中,我们创建了一个组合节点 root 和一个组合节点 comp,以及两个叶子节点 leaf1leaf2。然后,我们构建了一个树形结构,并将 compleaf1 添加到 root 中,将 leaf2 添加到 comp 中。最后,我们调用 rootoperation() 方法,这将递归地调用所有子节点的 operation() 方法。

2.7 享元模式

享元模式(Flyweight Pattern)是一种用于减少对象数量,从而改善应用所需的对象结构的设计模式。它通过共享尽可能多的对象来减少内存占用。享元模式通常与工厂模式一起使用,以便管理共享的实例。

// 享元接口(Flyweight)  
interface Flyweight {  
    void operation(String extrinsicState);  
}  
  
// 具体的享元实现  
class ConcreteFlyweight implements Flyweight {  
    // 内部状态(Intrinsic State),存储在享元对象内部,不随环境改变  
    private String intrinsicState;  
  
    public ConcreteFlyweight(String state) {  
        this.intrinsicState = state;  
    }  
  
    @Override  
    public void operation(String extrinsicState) {  
        // 这里可以使用内部状态和外部状态进行操作  
        System.out.println("Intrinsic: " + intrinsicState + ", Extrinsic: " + extrinsicState);  
    }  
}  
  
// 享元工厂(FlyweightFactory),负责创建和管理享元对象  
class FlyweightFactory {  
    // 享元对象的缓存池  
    private Map<String, Flyweight> flyweights = new HashMap<>();  
  
    public Flyweight getFlyweight(String key) {  
        Flyweight flyweight = flyweights.get(key);  
        if (flyweight == null) {  
            flyweight = new ConcreteFlyweight(key);  
            flyweights.put(key, flyweight);  
        }  
        return flyweight;  
    }  
}  
  
// 不享元的上下文(UnsharedConcreteFlyweight),如果需要的话,可以作为享元的补充  
class UnsharedConcreteFlyweight implements Flyweight {  
    @Override  
    public void operation(String extrinsicState) {  
        // 不需要内部状态,直接操作外部状态  
        System.out.println("This is an unshared flyweight: " + extrinsicState);  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        FlyweightFactory factory = new FlyweightFactory();  
  
        Flyweight flyweightA = factory.getFlyweight("A");  
        Flyweight flyweightB = factory.getFlyweight("B");  
        Flyweight flyweightA2 = factory.getFlyweight("A"); // 获取的是和 flyweightA 相同的实例  
  
        flyweightA.operation("first extrinsic state");  
        flyweightB.operation("second extrinsic state");  
        flyweightA2.operation("first extrinsic state again"); // 使用的是 flyweightA 的内部状态  
  
        // 如果需要不共享的对象,可以使用 UnsharedConcreteFlyweight  
        Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight();  
        unsharedFlyweight.operation("unshared state");  
    }  
}

说明:

  • 在这个例子中,Flyweight 接口定义了一个操作,它接受一个外部状态(extrinsic state)作为参数。ConcreteFlyweight 类实现了 Flyweight 接口,并保存了一个内部状态(intrinsic state)。享元工厂 FlyweightFactory 包含一个缓存池(在这里是 HashMap),用于存储和检索享元对象。如果请求的享元对象不存在于缓存池中,工厂会创建一个新的享元对象,并将其添加到缓存池中。
  • 客户端代码通过工厂来获取享元对象,并调用它们的操作。由于 FlyweightFactory 缓存了享元对象,因此多次请求具有相同内部状态的享元对象时,将返回相同的实例。这减少了内存使用,并且由于享元对象是不可变的(在这个例子中),因此它们是线程安全的。
  • 此外,还定义了一个 UnsharedConcreteFlyweight 类,用于表示不需要共享的对象。如果某些对象不适合作为享元,可以使用这种方式来处理它们。

3、行为型模式

3.1 策略模式

策略模式(Strategy Pattern)是一种行为设计模式,它允许在运行时根据需要从一组算法中选择一种。这种类型的设计模式属于行为模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象更改 context 对象的执行算法。

  1. 定义策略接口
public interface PaymentStrategy {  
    void pay(double amount);  
}
  1. 实现具体的策略

信用卡支付策略

public class CreditCardPayment implements PaymentStrategy {  
    @Override  
    public void pay(double amount) {  
        System.out.println("Paying " + amount + " using credit card.");  
        // 具体的信用卡支付逻辑  
    }  
}

现金支付策略

public class CashPayment implements PaymentStrategy {  
    @Override  
    public void pay(double amount) {  
        System.out.println("Paying " + amount + " using cash.");  
        // 具体的现金支付逻辑  
    }  
}
  1. 定义上下文

上下文通常持有一个策略对象的引用,并在某个方法中使用该策略对象。

public class PaymentContext {  
    private PaymentStrategy paymentStrategy;  
  
    public PaymentContext(PaymentStrategy paymentStrategy) {  
        this.paymentStrategy = paymentStrategy;  
    }  
  
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {  
        this.paymentStrategy = paymentStrategy;  
    }  
  
    public void executePayment(double amount) {  
        paymentStrategy.pay(amount);  
    }  
}
  1. 使用策略模式
public class Main {  
    public static void main(String[] args) {  
        // 创建上下文并设置信用卡支付策略  
        PaymentContext creditCardContext = new PaymentContext(new CreditCardPayment());  
        creditCardContext.executePayment(100.0); // 输出:Paying 100.0 using credit card.  
  
        // 更改策略为现金支付并再次执行支付  
        creditCardContext.setPaymentStrategy(new CashPayment());  
        creditCardContext.executePayment(50.0); // 输出:Paying 50.0 using cash.  
    }  
}

说明

  • 策略接口:定义了一个算法的接口,确保所有策略都遵循这个接口。
  • 具体策略:实现了策略接口,并提供具体的算法实现。
  • 上下文:持有一个策略对象的引用,并与该策略对象交互。上下文还可以让客户端在运行时动态地改变策略。

策略模式的主要优点包括:

  • 可维护性:策略模式提供了对开放-封闭原则的更好支持,策略易于切换、理解、扩展和维护。
  • 灵活性:客户端可以在运行时动态地改变策略。
  • 可重用性:策略可以被多个上下文重用。

然而,策略模式也有其缺点,例如策略类数量的增加可能导致系统变得复杂。因此,在决定使用策略模式之前,应该仔细考虑是否真的需要这么多的策略类。

3.2 模板方法模式

模板方法模式(Template Method Pattern)是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

以下是一个模板方法模式的示例代码及说明:

  1. 定义抽象类:包含基本方法(primitive operations)和模板方法(template method)。基本方法由子类实现,模板方法则调用这些基本方法并定义算法的骨架
public abstract class AbstractClass {  
  
    // 模板方法,定义了算法的骨架  
    public final void templateMethod() {  
        specificMethod1();  
        // ... 可能有其他逻辑  
        specificMethod2();  
    }  
  
    // 基本方法1,由子类实现  
    protected abstract void specificMethod1();  
  
    // 基本方法2,由子类实现  
    protected abstract void specificMethod2();  
  
    // 可能有其他基本方法  
}

2. 实现具体子类:继承抽象类并实现抽象方法。

public class ConcreteClassA extends AbstractClass {  
  
    @Override  
    protected void specificMethod1() {  
        System.out.println("ConcreteClassA: Implementing specificMethod1");  
    }  
  
    @Override  
    protected void specificMethod2() {  
        System.out.println("ConcreteClassA: Implementing specificMethod2");  
    }  
}  
  
public class ConcreteClassB extends AbstractClass {  
  
    @Override  
    protected void specificMethod1() {  
        System.out.println("ConcreteClassB: Implementing specificMethod1");  
    }  
  
    @Override  
    protected void specificMethod2() {  
        System.out.println("ConcreteClassB: Implementing specificMethod2");  
    }  
}

3. 使用模板方法:客户端代码通过调用模板方法来执行算法。

public class Client {  
    public static void main(String[] args) {  
    AbstractClass classA = new ConcreteClassA();  
        classA.templateMethod(); // 输出算法步骤,使用ConcreteClassA的实现  
  
        AbstractClass classB = new ConcreteClassB();  
        classB.templateMethod(); // 输出算法步骤,使用ConcreteClassB的实现  
    }  
}

说明

  • 抽象类:定义了一个或多个抽象方法(基本方法)以及一个或多个非抽象方法(模板方法)。模板方法通常会被标记为 final,以防止子类覆盖它,从而保证算法的骨架不会被改变。
  • 具体子类:继承抽象类并实现所有的抽象方法。它们为算法的不同步骤提供了具体的实现。
  • 客户端代码:通过创建具体子类的实例并调用模板方法来执行算法。由于模板方法已经定义了算法的骨架,因此客户端代码不需要知道算法的具体步骤,只需要知道如何调用模板方法即可。

模板方法模式的主要优点包括:

  • 代码复用:通过复用模板方法中的代码,可以避免代码重复。
  • 算法结构清晰:算法的结构在抽象类中定义,使得子类可以专注于实现具体的业务逻辑。
  • 可扩展性:通过增加新的子类,可以很容易地扩展系统的功能。

然而,如果算法中的基本方法过多,那么子类可能会变得非常庞大和复杂,这可能会增加代码维护的难度。在这种情况下,可能需要考虑将部分基本方法进一步抽象为新的模板方法或抽象类。

3.3 观察者模式

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

以下是观察者模式的一个简单实现示例,包括主题(Subject)接口、具体主题(ConcreteSubject)、观察者(Observer)接口和具体观察者(ConcreteObserver):

  1. 主题(Subject)接口
public interface Subject {  
    void registerObserver(Observer o);  
    void removeObserver(Observer o);  
    void notifyObservers();  
}
  1. 具体主题(ConcreteSubject)
import java.util.ArrayList;  
import java.util.List;  
  
public class ConcreteSubject implements Subject {  
    private List<Observer> observers;  
    private String state;  
  
    public ConcreteSubject() {  
        this.observers = new ArrayList<>();  
    }  
  
    @Override  
    public void registerObserver(Observer o) {  
        observers.add(o);  
    }  
  
    @Override  
    public void removeObserver(Observer o) {  
        int i = observers.indexOf(o);  
        if (i >= 0) {  
            observers.remove(i);  
        }  
    }  
  
    @Override  
    public void notifyObservers() {  
        for (Observer observer : observers) {  
            observer.update(this);  
        }  
    }  
  
    public void setState(String state) {  
        this.state = state;  
        notifyObservers();  
    }  
  
    public String getState() {  
        return state;  
    }  
}
  1. 观察者(Observer)接口
public interface Observer {  
    void update(Subject subject);  
}
  1. 具体观察者(ConcreteObserver)
public class ConcreteObserver implements Observer {  
    private String name;  
    private String observedState;  
  
    public ConcreteObserver(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void update(Subject subject) {  
        if (subject instanceof ConcreteSubject) {  
            ConcreteSubject concreteSubject = (ConcreteSubject) subject;  
            observedState = concreteSubject.getState();  
            System.out.println(name + " observed state change to " + observedState);  
        }  
    }  
}
  1. 客户端代码
public class Client {  
    public static void main(String[] args) {  
        ConcreteSubject subject = new ConcreteSubject();  
  
        Observer observer1 = new ConcreteObserver("Observer 1");  
        Observer observer2 = new ConcreteObserver("Observer 2");  
  
        subject.registerObserver(observer1);  
        subject.registerObserver(observer2);  
  
        subject.setState("New state");  
  
        // 如果不再需要某个观察者,可以将其移除  
        subject.removeObserver(observer1);  
  
        subject.setState("Another state");  
    }  
}

说明

  • 主题(Subject):它定义了可以被观察者(Observer)注册和注销的方法,以及通知所有观察者的方法。
  • 具体主题(ConcreteSubject):它实现了主题接口,并持有一个观察者列表以及用于保存状态的变量。当状态改变时,它会调用notifyObservers()方法来通知所有观察者。
  • 观察者(Observer):它是一个接口,定义了当主题状态改变时,观察者应该执行的方法(通常是update())。
  • 具体观察者(ConcreteObserver):它实现了观察者接口,并在update()方法中被调用时,会接收到主题的状态并作出响应。

在客户端代码中,我们创建了一个具体主题和两个具体观察者,并将这两个观察者注册到主题中。然后,我们改变主题的状态,这将触发主题通知所有注册的观察者。最后,我们演示了如何移除一个观察者,并在之后再次改变主题状态,以验证被移除的观察者不再接收到通知。

3.4 迭代子模式

迭代子模式(Iterator Pattern)是一种行为设计模式,它允许顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。通过迭代子模式,我们可以在不了解聚合对象内部实现的情况下,遍历其内部元素。

以下是迭代子模式的一个简单实现示例,包括聚合接口(Aggregate)、具体聚合(ConcreteAggregate)、迭代子接口(Iterator)和具体迭代子(ConcreteIterator):

  1. 聚合接口(Aggregate)
public interface Aggregate {  
    Iterator createIterator();  
}
  1. 具体聚合(ConcreteAggregate)
import java.util.ArrayList;  
import java.util.List;  
  
public class ConcreteAggregate implements Aggregate {  
    private List<String> items = new ArrayList<>();  
  
    public void add(String item) {  
        items.add(item);  
    }  
  
    @Override  
    public Iterator createIterator() {  
        return new ConcreteIterator(items.iterator());  
    }  
}
  1. 迭代子接口(Iterator)
public interface Iterator {  
    boolean hasNext();  
    Object next();  
}
  1. 具体迭代子(ConcreteIterator)
import java.util.Iterator;  
  
public class ConcreteIterator implements Iterator {  
    private Iterator<String> internalIterator;  
  
    public ConcreteIterator(Iterator<String> internalIterator) {  
        this.internalIterator = internalIterator;  
    }  
  
    @Override  
    public boolean hasNext() {  
        return internalIterator.hasNext();  
    }  
  
    @Override  
    public Object next() {  
        return internalIterator.next();  
    }  
}
  1. 客户端代码
public class Client {  
    public static void main(String[] args) {  
        Aggregate aggregate = new ConcreteAggregate();  
        aggregate.add("Item 1");  
        aggregate.add("Item 2");  
        aggregate.add("Item 3");  
  
        Iterator iterator = aggregate.createIterator();  
        while (iterator.hasNext()) {  
            System.out.println(iterator.next());  
        }  
    }  
}

说明

  • 聚合接口(Aggregate):定义了创建迭代子对象的方法。
  • 具体聚合(ConcreteAggregate):实现了聚合接口,并维护了一个包含元素的集合。createIterator() 方法返回了一个具体迭代子对象,该对象用于遍历集合中的元素。
  • 迭代子接口(Iterator):定义了访问聚合对象中元素所需的方法,包括 hasNext()(判断是否还有下一个元素)和 next()(获取下一个元素)。
  • 具体迭代子(ConcreteIterator):实现了迭代子接口,它包装了一个 Java 集合框架中的 Iterator 对象,并将 hasNext()next() 方法的调用转发给这个内部迭代子对象。
  • 客户端代码:客户端代码首先创建了一个具体聚合对象,并向其中添加了一些元素。然后,它调用 createIterator() 方法来获取一个迭代子对象,并使用这个迭代子对象遍历聚合中的所有元素。

在迭代子模式中,客户端代码只需要与聚合对象和迭代子接口进行交互,而不需要知道聚合对象内部的具体实现。这提供了一种遍历聚合对象的统一方式,并且可以在不修改聚合对象的情况下添加新的遍历算法。

3.5 责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求的发送者和多个请求处理者解耦。在这种模式中,多个对象都有机会处理请求,并且会按照它们在链中的顺序一个接一个地检查此请求。当某个对象能够处理该请求时,它就会处理它,否则它会将请求传递给链中的下一个对象。

以下是一个简单的责任链模式的实现示例:

  1. 抽象处理者(Handler)接口
public interface Handler {  
    void handleRequest(String request);  
    void setNext(Handler nextHandler);  
    Handler getNext();  
}
  1. 具体处理者(ConcreteHandler)类
public class ConcreteHandler implements Handler {  
    private Handler nextHandler;  
  
    @Override  
    public void handleRequest(String request) {  
        if (canHandle(request)) {  
            // 处理请求  
            System.out.println("ConcreteHandler 处理请求: " + request);  
        } else if (nextHandler != null) {  
            // 传递请求给下一个处理者  
            nextHandler.handleRequest(request);  
        } else {  
            // 没有下一个处理者,处理失败  
            System.out.println("没有处理者能够处理请求: " + request);  
        }  
    }  
  
    // 假设的条件来判断是否能处理请求  
    protected boolean canHandle(String request) {  
        // 根据实际情况实现  
        return false; // 示例中默认不处理请求  
    }  
  
    @Override  
    public void setNext(Handler nextHandler) {  
        this.nextHandler = nextHandler;  
    }  
  
    @Override  
    public Handler getNext() {  
        return nextHandler;  
    }  
}
  1. 具体的处理者实现

​ 假设我们有两个具体的处理者,分别处理不同类型的请求:

public class HandlerA extends ConcreteHandler {  
    @Override  
    protected boolean canHandle(String request) {  
        return request.startsWith("A类型");  
    }  
}  
  
public class HandlerB extends ConcreteHandler {  
    @Override  
    protected boolean canHandle(String request) {  
        return request.startsWith("B类型");  
    }  
}
  1. 客户端代码
public class Client {  
    public static void main(String[] args) {  
        Handler handlerA = new HandlerA();  
        Handler handlerB = new HandlerB();  
  
        // 设置责任链  
        handlerA.setNext(handlerB);  
  
        // 发送请求  
        handlerA.handleRequest("A类型请求"); // 由HandlerA处理  
        handlerA.handleRequest("B类型请求"); // 由HandlerB处理  
        handlerA.handleRequest("未知类型请求"); // 无人处理,输出没有处理者能够处理请求  
    }  
}

说明

  • 抽象处理者(Handler):定义了处理请求的接口,同时包含了设置和获取下一个处理者的方法。
  • 具体处理者(ConcreteHandler):实现了抽象处理者接口,并且包含了处理请求的逻辑和判断能否处理请求的条件。如果不能处理请求,它将请求传递给链中的下一个处理者。
  • 具体的处理者实现:根据实际需要,实现多个具体的处理者类,这些类继承自具体处理者类,并覆盖canHandle方法以定义自己的处理条件。
  • 客户端代码:负责创建处理者对象,并按照顺序将它们连接起来形成责任链。然后,客户端发送请求到责任链的头部,链中的处理者将依次检查并处理请求,直到找到能够处理该请求的处理者,或者遍历完整个链。

3.6 命令模式

命令模式(Command Pattern)是一种行为设计模式,它允许将请求封装为对象,从而可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

以下是一个简单的命令模式的实现示例:

1. 命令接口(Command)

定义一个命令接口,所有命令都实现这个接口。

public interface Command {  
    void execute();  
    void undo();  
}

2. 具体命令类(ConcreteCommand)

实现命令接口,持有接收者的引用,并在执行方法内调用接收者的相应操作。

public class ConcreteCommand implements Command {  
    private Receiver receiver;  
  
    public ConcreteCommand(Receiver receiver) {  
        this.receiver = receiver;  
    }  
  
    @Override  
    public void execute() {  
        receiver.action();  
    }  
  
    @Override  
    public void undo() {  
        receiver.undoAction();  
    }  
}
  1. 接收者类(Receiver)

知道如何执行与请求相关的操作。任何类都可能成为一个接收者。

public class Receiver {  
    public void action() {  
        System.out.println("执行操作");  
    }  
  
    public void undoAction() {  
        System.out.println("撤销操作");  
    }  
}
  1. 调用者(Invoker)

要求命令执行一个请求。它持有一个对命令对象的引用,并在适当时候调用该命令。

public class Invoker {  
    private Command command;  
  
    public Invoker(Command command) {  
        this.command = command;  
    }  
  
    public void executeCommand() {  
        command.execute();  
    }  
  
    public void undoCommand() {  
        command.undo();  
    }  
}
  1. 客户端代码(Client)

创建具体的命令对象,并设置它的接收者。命令对象被传递给调用者。调用者随后在适当时候调用命令对象的executeundo方法。

public class Client {  
    public static void main(String[] args) {  
        Receiver receiver = new Receiver();  
        Command command = new ConcreteCommand(receiver);  
        Invoker invoker = new Invoker(command);  
  
        // 执行命令  
        invoker.executeCommand();  
  
        // 撤销命令  
        invoker.undoCommand();  
    }  
}

说明

  • 命令接口(Command):声明了执行和撤销操作的接口。
  • 具体命令类(ConcreteCommand):实现了命令接口,将接收者对象作为参数保存起来,并调用接收者的方法来执行命令。
  • 接收者类(Receiver):真正执行命令的对象。任何类都可以作为接收者,只要它实现了命令所需的操作。
  • 调用者(Invoker):要求命令执行请求,它持有一个对命令对象的引用,并在适当时候调用命令的executeundo方法。
  • 客户端代码(Client):创建命令对象,并设置它的接收者。然后将命令对象传递给调用者,调用者负责在适当时候执行命令。

通过这种方式,调用者和接收者之间的依赖被解除了,它们之间通过命令对象进行通信。这增加了系统的灵活性和可维护性。

3.7 备忘录模式

备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

以下是备忘录模式的一个简单实现示例:

  1. 发起人(Originator)

发起人创建一个备忘录并存储其当前内部状态,也可以使用备忘录来恢复其内部状态。

public class Originator {  
    private String state;  
  
    // 发起人需要保存其内部状态  
    public Memento createMemento() {  
        return new Memento(state);  
    }  
  
    // 发起人恢复其内部状态  
    public void restoreFromMemento(Memento memento) {  
        this.state = memento.getState();  
    }  
  
    // 设置状态  
    public void setState(String state) {  
        this.state = state;  
    }  
  
    // 获取状态  
    public String getState() {  
        return state;  
    }  
  
    // 示例方法,展示发起人状态  
    public void showState() {  
        System.out.println("Current state: " + state);  
    }  
}
  1. 备忘录(Memento)

备忘录存储发起人的内部状态,发起人可以根据需要将其内部状态保存在备忘录对象中,稍后再从备忘录对象恢复其内部状态。

public class Memento {  
    private String state;  
  
    // 备忘录存储发起人的状态  
    public Memento(String state) {  
        this.state = state;  
    }  
  
    // 获取状态  
    public String getState() {  
        return state;  
    }  
}
  1. 管理者(Caretaker)

管理者负责保存好备忘录,并且不能对备忘录的内容进行操作或检查。

import java.util.ArrayList;  
import java.util.List;  
  
public class Caretaker {  
    private List<Memento> mementoList = new ArrayList<>();  
  
    // 添加备忘录  
    public void addMemento(Memento memento) {  
        mementoList.add(memento);  
    }  
  
    // 获取备忘录(根据索引)  
    public Memento getMemento(int index) {  
        return mementoList.get(index);  
    }  
}
  1. 客户端代码(Client)

客户端代码演示了如何使用备忘录模式来保存和恢复发起人的内部状态。

public class Client {  
    public static void main(String[] args) {  
        Originator originator = new Originator();  
        Caretaker caretaker = new Caretaker();  
  
        originator.setState("State #1");  
        originator.showState();  
  
        // 保存当前状态  
        caretaker.addMemento(originator.createMemento());  
  
        originator.setState("State #2");  
        originator.showState();  
  
        // 恢复到之前保存的状态  
        originator.restoreFromMemento(caretaker.getMemento(0));  
        originator.showState();  
    }  
}

说明

  • 发起人(Originator):记录当前时刻的内部状态,负责创建一个可表示其内部状态的备忘录,并使用备忘录恢复其内部状态。
  • 备忘录(Memento):存储发起人对象的内部状态,并且可以防止发起人以外的对象访问发起人对象。备忘录可以根据需要实现深拷贝或浅拷贝。
  • 管理者(Caretaker):负责保存好备忘录对象,并且不能对备忘录的内容进行操作或检查。管理者仅负责存储和检索备忘录。
  • 客户端代码(Client):创建发起人对象,并创建备忘录和管理者对象。客户端代码演示了如何使用备忘录模式来保存和恢复发起人的内部状态。

备忘录模式通常用于实现“撤销”操作,如文本编辑器中的撤销和重做功能。

3.8 状态模式

状态模式(State Pattern)是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类。

下面是一个使用状态模式的简单例子,该例子描述了一个电灯开关的状态(打开或关闭)。

  1. 状态接口 (State):定义了一个状态对象的接口,以便封装与上下文对象的特定状态相关的行为
public interface State {  
    void turnOn();  
    void turnOff();  
    void dim();  
    State getNextState();  
}
  1. 具体状态类:实现了状态接口并封装了与状态相关的行为。
  • OnState(打开状态)
public class OnState implements State {  
    private LightContext lightContext;  
  
    public OnState(LightContext lightContext) {  
        this.lightContext = lightContext;  
    }  
  
    @Override  
    public void turnOn() {  
        System.out.println("Light is already on");  
    }  
  
    @Override  
    public void turnOff() {  
        System.out.println("Light is turned off");  
        lightContext.setState(lightContext.getOffState());  
    }  
  
    // ... 其他方法实现  
  
    @Override  
    public State getNextState() {  
        return lightContext.getOffState();  
    }  
}
  • OffState(关闭状态) - 类似地实现,但行为与OnState不同。
  1. 上下文对象 (LightContext):维护一个表示当前状态的对象,并在状态对象上调用方法来改变上下文的行为。
public class LightContext {  
    private State state;  
    private OnState onState;  
    private OffState offState;  
  
    public LightContext() {  
        this.onState = new OnState(this);  
        this.offState = new OffState(this);  
        this.state = offState; // 初始状态为关闭  
    }  
  
    public void setState(State state) {  
        this.state = state;  
    }  
  
    public OnState getOnState() {  
        return onState;  
    }  
  
    public OffState getOffState() {  
        return offState;  
    }  
  
    // 委托给当前状态对象的方法  
    public void turnOn() {  
        state.turnOn();  
    }  
  
    public void turnOff() {  
        state.turnOff();  
    }  
  
    // ... 其他方法  
}
  1. 客户端代码:使用上下文对象并与之交互。
public class Client {  
    public static void main(String[] args) {  
        LightContext light = new LightContext();  
  
        // 打开灯  
        light.turnOn();  
  
        // 关闭灯  
        light.turnOff();  
  
        // ... 其他交互  
    }  
}

注意:在这个例子中,State 接口和具体状态类(如 OnStateOffState)是状态模式的核心。LightContext 类是上下文对象,它持有一个状态对象并委托给该对象来处理与状态相关的请求。客户端代码与上下文对象交互,而不是直接与状态对象交互。

3.9 访问者模式

访问者模式(Visitor Pattern)是一种将数据操作与数据结构分离的设计模式。它允许你在不修改数据结构的前提下定义新的操作。访问者模式通常包含两个主要部分:一个是元素(Element)的集合,这些元素都实现了接受访问者(accept visitor)的方法;另一个是访问者(Visitor)接口,该接口声明了访问每个元素时要执行的操作。

以下是访问者模式的一个简单示例,其中我们有一个元素集合(例如,一个简单的形状集合)和一个访问者,该访问者可以对这些形状执行特定的操作(例如,计算和打印它们的面积)。

  1. 接口定义

1.1 元素接口(Element)

public interface Element {  
    void accept(Visitor visitor);  
}

1.2 访问者接口(Visitor)

public interface Visitor {  
    void visit(Circle circle);  
    void visit(Rectangle rectangle);  
    // 可以添加更多元素的访问方法  
}
  1. 元素实现

    2.1 圆形(Circle)

public class Circle implements Element {  
    private double radius;  
  
    public Circle(double radius) {  
        this.radius = radius;  
    }  
  
    public double getRadius() {  
        return radius;  
    }  
  
    @Override  
    public void accept(Visitor visitor) {  
        visitor.visit(this);  
    }  
  
    // 计算面积的方法(为了演示)  
    public double calculateArea() {  
        return Math.PI * Math.pow(radius, 2);  
    }  
}

2.2 矩形(Rectangle)

public class Rectangle implements Element {  
    private double width;  
    private double height;  
  
    public Rectangle(double width, double height) {  
        this.width = width;  
        this.height = height;  
    }  
  
    // getters ...  
  
    @Override  
    public void accept(Visitor visitor) {  
        visitor.visit(this);  
    }  
  
    // 计算面积的方法(为了演示)  
    public double calculateArea() {  
        return width * height;  
    }  
}
  1. 访问者实现

  2. 1 面积计算访问者(AreaCalculatorVisitor)

public class AreaCalculatorVisitor implements Visitor {  
    @Override  
    public void visit(Circle circle) {  
        System.out.println("Circle area: " + circle.calculateArea());  
    }  
  
    @Override  
    public void visit(Rectangle rectangle) {  
        System.out.println("Rectangle area: " + rectangle.calculateArea());  
    }  
}
  1. 客户端代码
public class Client {  
    public static void main(String[] args) {  
        List<Element> elements = new ArrayList<>();  
        elements.add(new Circle(5));  
        elements.add(new Rectangle(4, 6));  
  
        Visitor visitor = new AreaCalculatorVisitor();  
  
        for (Element element : elements) {  
            element.accept(visitor);  
        }  
    }  
}

在这个例子中,我们定义了一个Element接口,它有一个accept方法用于接受一个Visitor对象。我们还定义了一个Visitor接口,它声明了访问不同形状(如圆形和矩形)的方法。然后我们创建了两个实现了Element接口的类(圆形和矩形),并在这些类中实现了accept方法以调用相应的Visitor方法。最后,我们创建了一个AreaCalculatorVisitor类,它实现了Visitor接口并计算了形状的面积。在客户端代码中,我们创建了一个形状列表和一个访问者对象,并使用accept方法遍历形状列表,对每个形状执行访问者的操作。

3.10 中介者模式

中介者模式(Mediator Pattern)是一种行为设计模式,它用于减少对象之间的直接依赖关系,使得对象之间的交互通过一个中介者对象进行。这样,当对象之间的交互逻辑变得复杂时,可以通过修改中介者对象来简化系统维护,并且减少了对象之间的耦合度。

以下是一个使用Java语言实现的中介者模式的示例代码及说明:

  1. 中介者接口(Mediator)

首先,我们定义一个中介者接口,该接口声明了与同事对象(Colleague)通信的方法。

public interface Mediator {  
    void send(String message, Colleague colleague);  
}
  1. 同事接口(Colleague)

然后,我们定义一个同事接口,该接口声明了与中介者对象交互的方法。

public interface Colleague {  
    void receive(String message);  
    void setMediator(Mediator mediator);  
    Mediator getMediator();  
}
  1. 具体中介者实现(ConcreteMediator)
import java.util.HashMap;  
import java.util.Map;  
  
public class ConcreteMediator implements Mediator {  
    private Map<String, Colleague> colleagues = new HashMap<>();  
  
    @Override  
    public void send(String message, Colleague colleague) {  
        // 假设这里有一些逻辑,比如广播消息给所有同事,或者只发送给特定的同事  
        for (Colleague c : colleagues.values()) {  
            if (c != colleague) {  
                c.receive(message);  
            }  
        }  
    }  
  
    public void register(String name, Colleague colleague) {  
        colleagues.put(name, colleague);  
        colleague.setMediator(this);  
    }  
  
    public void unregister(String name) {  
        colleagues.remove(name);  
    }  
}
  1. 具体同事实现(ConcreteColleague)
public class ConcreteColleague implements Colleague {  
    private Mediator mediator;  
    private String name;  
  
    public ConcreteColleague(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void receive(String message) {  
        System.out.println(name + " received: " + message);  
    }  
  
    @Override  
    public void setMediator(Mediator mediator) {  
        this.mediator = mediator;  
    }  
  
    @Override  
    public Mediator getMediator() {  
        return mediator;  
    }  
  
    public void send(String message) {  
        mediator.send(message, this);  
    }  
}
  1. 客户端代码(Client)

最后,在客户端代码中,我们创建中介者对象、同事对象,并将同事对象注册到中介者对象中。然后,我们让同事对象之间通过中介者对象进行通信。

public class Client {  
    public static void main(String[] args) {  
        Mediator mediator = new ConcreteMediator();  
  
        Colleague colleagueA = new ConcreteColleague("Colleague A");  
        Colleague colleagueB = new ConcreteColleague("Colleague B");  
  
        mediator.register("A", colleagueA);  
        mediator.register("B", colleagueB);  
  
        colleagueA.send("Hello from A!");  
    }  
}

在这个例子中,ConcreteMediator 是具体的中介者实现,它维护了一个同事对象的集合,并提供了发送消息给同事对象的方法。ConcreteColleague 是具体的同事实现,它持有中介者对象的引用,并提供了接收消息和发送消息给中介者对象的方法。在客户端代码中,我们创建了一个中介者对象和两个同事对象,并将同事对象注册到中介者对象中。然后,我们让同事对象A发送一条消息,该消息通过中介者对象被发送给所有的同事对象(在这个例子中,只有同事对象B)。

3.11 解释器模式

解释器模式(Interpreter Pattern)是一种行为设计模式,它定义了一个语言的文法,并且建立了一个解释器来解释该语言中的句子。解释器模式通常用于实现编程语言、表达式求值、SQL 解析、数学表达式转换、正则表达式等。

以下是一个简单的解释器模式的实现,用于计算算术表达式(如 “1 + 2 * 3”)。这个实现包括了一个抽象表达式接口、一个具体的表达式实现(用于数字和运算符)、一个上下文环境(虽然在这个简单的例子中不需要)以及一个客户端来测试表达式。

  1. 抽象表达式接口(Expression)
public interface Expression {  
    int interpret(Context context);  
}
  1. 终端表达式(Terminal Expression)
  • NumberExpression:表示数字的表达式
public class NumberExpression implements Expression {  
    private int number;  
  
    public NumberExpression(int number) {  
        this.number = number;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        // 对于数字,没有上下文依赖,直接返回数字值  
        return number;  
    }  
}
  1. 非终端表达式(Nonterminal Expression)
  • PlusExpression:表示加法的表达式
  • MultiplyExpression:表示乘法的表达式
public class PlusExpression implements Expression {  
    private Expression left;  
    private Expression right;  
  
    public PlusExpression(Expression left, Expression right) {  
        this.left = left;  
        this.right = right;  
    }  
  
    @Override  
    public int interpret(Context context) {  
        // 递归地解释左右两边的表达式,并将结果相加  
        return left.interpret(context) + right.interpret(context);  
    }  
}  
  
public 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);  
    }  
}
  1. 上下文环境(Context)

在这个简单的例子中,我们不需要上下文环境,因为表达式不依赖于任何外部状态。但在更复杂的场景中,你可能会需要一个上下文环境来存储和传递解释过程中所需的状态。

  1. 客户端代码(Client)
public class Client {  
    public static void main(String[] args) {  
        // 创建表达式树  
        Expression expression = new PlusExpression(  
            new NumberExpression(1),  
            new MultiplyExpression(  
                new NumberExpression(2),  
                new NumberExpression(3)  
            )  
        );  
  
        // 解释表达式  
        int result = expression.interpret(null); // 在这个例子中,我们不需要上下文,所以传入null  
  
        // 输出结果  
        System.out.println("Result: " + result); // 应该输出 7  
    }  
}

说明

  • 抽象表达式(Expression):定义了一个解释操作的接口,所有的表达式都应该实现这个接口。
  • 终端表达式(NumberExpression):实现了抽象表达式接口,并代表了一个不可再分的表达式,如一个具体的数字。
  • 非终端表达式(PlusExpression, MultiplyExpression):实现了抽象表达式接口,并代表了一个由其他表达式组合而成的表达式。非终端表达式需要递归地解释其包含的表达式。
  • 上下文环境(Context):通常用于存储解释过程中所需的全局信息。在这个例子中我们没有使用它,但在更复杂的情况下它可能会很有用。
  • 客户端代码(Client):构建了表达式树,并调用了解释器来解释这个表达式。

这个解释器模式的例子是非常基础的,但它展示了如何定义和解释一个简单的算术表达式。在实际应用中,解释器模式可能会更复杂,并可能包含更多的表达式类型和上下文环境。

;