设计模式(Design Patterns)是软件工程中用于解决特定问题的一系列最佳实践。它们是经过时间考验的、被广泛认可的软件设计经验,可以帮助开发者在面对常见问题时做出更好的设计决策。设计模式不是现成的代码,而是一套指导原则,用来指导开发者如何组织代码结构,以便于更好地应对变化和提高代码的可维护性。
设计模式通常具有以下特点:
-
普遍性:设计模式解决的问题在软件设计中非常普遍,很多设计问题都可以用相应的设计模式来解决。
-
可重用性:设计模式提供了一种可重用的解决方案,可以帮助开发者避免重复发明轮子。
-
灵活性:设计模式通常具有良好的灵活性,可以适应不同的应用场景。
-
可维护性:使用设计模式可以提高代码的可维护性,因为它们提供了一种标准化的解决方案。
-
可扩展性:设计模式通常具有良好的可扩展性,可以方便地添加新的功能。
1.设计模式分类
-
创建型模式(Creational Patterns):主要关注对象的创建,隐藏对象创建的复杂性,提高程序的可扩展性。常见的创建型模式有单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。
-
结构型模式(Structural Patterns):主要关注对象的组合,通过对象的组合提高程序的模块化。常见的结构型模式有适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式和代理模式。
-
行为型模式(Behavioral Patterns):主要关注对象之间的交互,描述对象如何协作以完成某种任务。常见的行为型模式有责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式和访问者模式。
设计模式是软件工程中非常重要的概念,掌握设计模式对于提高编程能力和设计能力非常有帮助。但是,设计模式并不是万能的,需要根据具体的应用场景来选择是否使用设计模式,以及使用哪种设计模式。盲目地使用设计模式可能会导致过度设计,反而降低程序的性能和可维护性。
2.使用场景
当然,以下是以表格形式展示的设计模式及其使用场景的简要说明:
设计模式 | 目的 | 使用场景示例 |
---|---|---|
单例模式 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点 | - 日志记录器 - 配置管理器 |
工厂方法模式 | 定义一个创建对象的接口,让子类决定实例化哪个类 | - 各种工厂类(如视频游戏工厂模式创建不同类型的角色) |
抽象工厂模式 | 解决一个系列的工厂,用于创建一组相关或依赖的对象 | - GUI组件库 - 汽车组装线 |
建造者模式 | 分离对象的构建过程和表示,允许通过指定复杂对象类型和内容逐步构造一个复杂对象 | - 构建复杂对象,如SQL语句构建器 |
原型模式 | 通过复制现有的实例来创建新的实例 | - 需要频繁创建开销较大的对象时 |
适配器模式 | 允许将不兼容的接口转换为一个可以使用的兼容接口 | - 兼容旧的类库 - 第三方库的集成 |
桥接模式 | 分离抽象部分和实现部分,使它们可以独立地变化 | - 当一个类存在多个变化维度时 |
组合模式 | 允许将对象组合成树形结构以表示“部分-整体”的层次结构 | - 处理类似文件系统的层次结构 |
装饰器模式 | 动态地添加额外的功能到一个对象上,而不是通过继承 | - 扩展对象功能,而不改变其类 |
外观模式 | 为子系统中的一组接口提供一个统一的接口 | - 简化复杂的系统接口 |
享元模式 | 运用共享技术有效地支持大量细粒度的对象 | - 当系统中存在大量相似对象时 |
代理模式 | 为其他对象提供一个代理或占位符,以控制对这个对象的访问 | - 访问控制 - 延迟初始化 |
责任链模式 | 使多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合 | - 审批流程 - 错误处理 |
命令模式 | 将一个请求封装为一个对象,从而允许用户使用不同的请求来参数化其他对象 | - 宏系统 - 事务系统 |
解释器模式 | 定义一个语言的文法,并且建立一个解释器来解释该语言中的句子 | - 解析表达式或指令 |
迭代器模式 | 提供一种顺序访问一个聚合对象元素的方法,而不暴露其内部的表示 | - 需要遍历聚合对象,而不关心其内部结构 |
中介者模式 | 用一个中介对象来封装一系列对象之间的交互 | - 集中管理对象间的通信 |
备忘录模式 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态 | - 提供对象状态的快照,用于撤销操作 |
观察者模式 | 当对象间存在一对多关系时,则使用观察者模式 | - 事件多级触发 - 发布/订阅系统 |
状态模式 | 允许对象在其内部状态改变时改变它的行为 | - 行为随状态改变而改变的对象 |
策略模式 | 定义一系列算法,把它们一个个封装起来,并使它们可以互换 | - 需要动态地在运行时选择算法 |
访问者模式 | 表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 | - 数据结构相对稳定,但经常需要在此结构上定义新的操作 |
3. Demo
3.1. 创建型模式
3.1.1 单例模式
定义:
单例模式确保一个类只有一个实例,并提供一个全局访问点。
优点:
- 全局访问:提供了一个全局访问点,便于控制实例数量。
- 资源节约:避免了创建多个对象时的资源浪费。
- 线程安全:在多线程环境中,可以保证只创建一个实例。
- 控制实例化:可以控制对象的创建过程。
缺点:
- 代码耦合:单例模式可能会隐藏一些依赖关系,导致代码耦合。
- 可测试性差:由于单例模式是全局的,这使得单元测试变得困难。
- 内存浪费:单例对象在程序的整个生命周期内都占用内存。
- 滥用:单例模式有时会被滥用,导致程序设计不灵活。
适用场景:
- 需要确保在整个应用程序中只存在一个实例的情况,如配置管理器、线程池、缓存等。
Demo:
public class Singleton {// 私有构造函数,防止外部实例化private Singleton() {}// 私有静态变量,唯一的实例private static Singleton instance;// 公有静态方法,提供全局访问点public static Singleton getInstance() {// 双重检查锁定,确保线程安全if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}// 其他业务方法public void doSomething() {// ...}
}
使用示例:
public class Demo {public static void main(String[] args) {// 获取单例对象Singleton singleton = Singleton.getInstance();// 使用单例对象singleton.doSomething();}
}
请注意,单例模式的实现方式有多种,上面的实现是懒汉式单例模式,它在第一次调用getInstance()
方法时才创建实例,并且使用了双重检查锁定来保证线程安全。根据具体需求,单例模式的实现可能会有所不同。
3.1.2 工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,用于处理对象的创建。在工厂方法模式中,一个类将创建对象的任务委托给子类,而不是自己创建对象。这样,你可以在不修改工厂类的情况下,通过继承和覆写子类的工厂方法来创建不同的对象。
定义:
定义一个创建对象的接口,但让子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行。
优点:
- 代码解耦:将对象的创建与使用分离,使得代码更加灵活,易于扩展。
- 提高可维护性:当需要添加新的产品时,只需要添加一个具体的产品类和相应的具体工厂类即可,无需修改已有的工厂类。
- 封装性:隐藏了对象创建的细节,调用者只需要知道类型,不需要知道具体的实现。
缺点:
- 每增加一个产品类别,都需要增加一个产品类和一个工厂类,这可能导致类的数量成倍增加。
- 系统的抽象程度变得更高,对于简单的情况,可能会增加系统的复杂性。
适用场景:
- 当需要创建的对象类型较多,且具有共同的接口时。
- 当需要将对象的创建与使用分离,以提高扩展性和可维护性时。
Demo:
// 产品接口
interface Product {void use();
}// 具体产品类
class ConcreteProductA implements Product {public void use() {System.out.println("Product A is being used.");}
}class ConcreteProductB implements Product {public void use() {System.out.println("Product B is being used.");}
}// 工厂接口
interface Factory {Product createProduct();
}// 具体工厂类
class ConcreteFactoryA implements Factory {public Product createProduct() {return new ConcreteProductA();}
}class ConcreteFactoryB implements Factory {public Product createProduct() {return new ConcreteProductB();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 根据需求选择相应的工厂Factory factory = new ConcreteFactoryA();Product product = factory.createProduct();product.use();}
}
在这个示例中,Product
是产品接口,ConcreteProductA
和 ConcreteProductB
是具体产品类。Factory
是工厂接口,ConcreteFactoryA
和 ConcreteFactoryB
是具体工厂类,它们分别创建相应的产品。客户端代码通过工厂接口与具体工厂类交互,从而创建和使用产品。
工厂方法模式允许你通过继承和覆写工厂方法来灵活地创建不同类型的产品,而不需要修改客户端代码,这提高了系统的可扩展性。
3.1.3. 抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一个创建一系列相关或依赖对象的接口,而不需要指定它们的具体类。这种模式通常用于当需要生成多个系列的产品时,并且希望客户端不需要知道具体产品类的细节。
定义: 提供一个接口,用于创建一系列相关或依赖的对象,而不指定它们的具体类。
优点:
- 封装性:具体产品类的实现细节被抽象工厂类所封装。
- 扩展性:增加新的产品族很方便,不需修改现有系统。
- 解耦:产品等级结构的决策被推迟到对象创建时,由具体工厂类实现。
缺点:
- 难以支持新种类的产品:如果需要在系统中引入新的工厂,则需要修改抽象工厂接口。
- 增加系统复杂性:在添加新的产品对象时,需要为每种产品提供一个相应的具体工厂类。
适用场景:
- 当系统需要提供多个系列的产品,并且这些产品之间存在相关性时。
- 当系统需要独立地进行产品的系列扩展时。
Demo:
// 产品接口
interface Product { void use();
}// 产品实现类A
class ProductA1 implements Product {public void use() {System.out.println("Product A1 is used");}
}// 产品实现类B
class ProductB1 implements Product {public void use() {System.out.println("Product B1 is used");}
}// 抽象工厂接口
interface AbstractFactory {Product createProduct();
}// 具体工厂实现类1
class ConcreteFactory1 implements AbstractFactory {public Product createProduct() {return new ProductA1();}
}// 产品实现类A
class ProductA2 implements Product {public void use() {System.out.println("Product A2 is used");}
}// 产品实现类B
class ProductB2 implements Product {public void use() {System.out.println("Product B2 is used");}
}// 具体工厂实现类2
class ConcreteFactory2 implements AbstractFactory {public Product createProduct() {return new ProductB2();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 客户端代码针对抽象工厂编程AbstractFactory factory = new ConcreteFactory1();Product product = factory.createProduct();product.use();}
}
在这个示例中,Product
是一个产品接口,ProductA1
和 ProductB1
是具体的产品实现。AbstractFactory
是一个抽象工厂接口,定义了一个创建产品的方法。ConcreteFactory1
和 ConcreteFactory2
是具体工厂实现类,它们分别创建属于各自系列的产品。客户端代码通过抽象工厂接口与具体工厂类交互,从而创建和使用产品。
抽象工厂模式允许在不同的系列之间进行切换,而不需要修改客户端代码,这提高了系统的灵活性和可扩展性。
3.1.4. 建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,用于将复杂对象的构建与其表示分离,以便相同构建过程可以创建不同的表示。建造者模式通常适用于创建一个由多个部件或部分组成的复杂对象时,允许用户只通过指定复杂对象的类型和内容,就能构建和得到一个完整对象,而不必知道其内部构建细节。
定义: 将一个复杂对象的构建与其表示分离,允许通过相同的构建过程创建不同的表示。
优点:
- 解耦:将对象的创建和表示分离,使得修改和扩展建造者不影响对象的构建。
- 封装性:隐藏了对象构建的细节,只暴露一个创建接口。
- 灵活性:用户不需要知道构建过程的具体细节,只需要知道如何使用建造者接口即可构建出目标对象。
缺点:
- 可能会创建过多的建造者类:如果对象的内部变化较为复杂,可能会需要多个建造者类,这会增加系统的复杂性。
- 对于只需要简单创建的对象,使用建造者模式可能会过度设计。
适用场景:
- 当需要生成一个由多个部件或部分组成的复杂对象时。
- 当对象的构建过程独立于对象本身的其他业务逻辑时。
Demo
// 产品接口
interface Product {void assemblePart(String part);
}// 具体产品类
class ConcreteProduct implements Product {public void assemblePart(String part) {System.out.println("Product part " + part + " has been assembled.");}
}// 建造者接口
interface Builder {void buildPart1();void buildPart2();Product retrieveResult();
}// 具体建造者类
class ConcreteBuilder implements Builder {private Product product = new ConcreteProduct();public void buildPart1() {product.assemblePart("Part1");}public void buildPart2() {product.assemblePart("Part2");}public Product retrieveResult() {return product;}
}// 指挥者
class Director {public void construct(Builder builder) {builder.buildPart1();builder.buildPart2();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建指挥者和建造者Director director = new Director();Builder builder = new ConcreteBuilder();// 指挥者使用建造者来构建产品director.construct(builder);// 获得构建好的产品对象Product product = builder.retrieveResult();product.assemblePart("Final Assembly");}
}
在这个示例中,Product
是产品接口,ConcreteProduct
是具体产品实现。Builder
是建造者接口,定义了构建复杂对象的各个部件的方法,以及一个返回产品的接口。ConcreteBuilder
是具体建造者实现类,它实现了建造者接口,并创建了产品的具体实例。Director
是指挥者类,它负责使用建造者来构建产品。客户端代码通过指挥者与建造者交互,从而创建和使用产品。
建造者模式非常适合于创建包含多个部件或步骤的复杂对象,并且这些部件或步骤的创建逻辑可能会变化的情况。
3.1.5 原型模式
原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过创建新实例的方式。这种方式可以减少创建对象的开销,特别是在创建对象成本较高时,如初始化数据较为复杂或资源消耗较大。
定义: 使用原型实例指定创建对象的种类,并通过复制这个原型来创建新对象。
优点:
- 简化对象创建:通过复制已有对象来创建新对象,避免了复杂的创建过程。
- 性能优化:对于创建成本较高的对象,通过复制可以节省资源和时间。
- 扩展性:可以实现深拷贝和浅拷贝,提供灵活的拷贝方式。
缺点:
- 复制引用类型:如果对象包含引用类型,不当的复制可能会导致原始对象和复制对象共享同一引用类型,这可能引起副作用。
- 细小对象:对于创建代价不大的对象,使用原型模式可能会增加系统的复杂性。
适用场景:
- 当创建新对象的成本较高时,如需要创建大量相似对象时。
- 当系统需要避免使用类时,如动态加载对象。
最佳实践Demo(含注释):
// 原型接口
interface Prototype {Prototype clone(); // 克隆自身
}// 具体原型类
class ConcretePrototype implements Prototype {private String data;public ConcretePrototype(String data) {this.data = data;}// 深拷贝克隆自身public Prototype clone() {try {return (ConcretePrototype) super.clone();} catch (CloneNotSupportedException e) {// 实际上,由于实现了Cloneable接口,这个异常不会发生return null;}}// getters and setterspublic String getData() {return data;}public void setData(String data) {this.data = data;}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建原型对象ConcretePrototype prototype = new ConcretePrototype("Original");// 克隆原型对象Prototype clone = prototype.clone();// 使用克隆对象System.out.println(clone.getData()); // 输出: Original}
}
在这个示例中,Prototype
是原型接口,定义了一个克隆自身的方法。ConcretePrototype
是具体原型类,实现了原型接口的clone()
方法,用于克隆自己。客户端代码通过原型对象创建克隆对象,从而避免了直接创建对象的开销。
原型模式非常适合于需要快速复制对象的场景,尤其是在对象创建成本较高时。然而,原型模式需要考虑对象的拷贝方式,如深拷贝和浅拷贝,以避免共享引用类型带来的问题。
3.2. 结构型模式
3.2.1. 适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,用于将不兼容的接口转换为一个可以使用的兼容接口。它允许原本由于接口不兼容而不能一起工作的类可以一起工作,增加了类的可重用性。
定义: 允许将不兼容的接口转换为一个可以使用的兼容接口。
优点:
- 兼容性:使得不兼容的接口可以协同工作。
- 解耦:将客户端与需要适配的类解耦,客户端不需要知道适配的具体细节。
- 灵活性:可以设计一个类来适配多种不同的接口。
缺点:
- 增加系统的复杂性:过多使用适配器可能会使系统变得复杂。
- 性能损失:可能存在轻微的性能损失,因为多了一层间接调用。
适用场景:
- 当你希望复用一些类,但这些类的接口与你的系统不兼容时。
- 当你想创建一个可以与多种类协同工作的类,并且这些类都具有不同的接口时。
Demo:
// 目标接口,定义客户端使用的特定领域相关的接口
interface Target {void request();
}// 适配者接口,定义了被适配者的一个或多个不兼容的接口
interface Adaptee {void specificRequest();
}// 被适配者,实现了一个特定的业务逻辑
class ConcreteAdaptee implements Adaptee {public void specificRequest() {System.out.println("Executing specificRequest in Adaptee");}
}// 适配器类,将源接口转换成目标接口
class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}public void request() {// 调用被适配者的方法adaptee.specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建被适配者Adaptee adaptee = new ConcreteAdaptee();// 创建适配器,将被适配者和目标接口关联起来Target target = new Adapter(adaptee);// 使用适配器target.request();}
}
在这个示例中,Target
是目标接口,定义了客户端使用的接口。Adaptee
是被适配者接口,定义了被适配者的一个或多个不兼容的接口。ConcreteAdaptee
是被适配者的具体实现。Adapter
是适配器类,实现了目标接口,并通过组合被适配者对象来重用现有的接口。
适配器模式使得原本不兼容的类可以协同工作,提高了类的可重用性。它允许客户端通过一个统一的接口来与其它类交互,而不需要了解这些类的具体实现细节。
3.2.2. 桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其实现部分分离,使它们可以独立地变化。桥接模式主要目的是将接口部分和实现部分分离开来,从而能够独立地对它们进行扩展,避免在任何一部分改变时都要重新生成整体的类层次结构。
定义:
将抽象部分与其实现部分分离,使它们可以独立地变化。
优点:
- 解耦:桥接模式使抽象和实现分离,从而降低了它们之间的耦合。
- 扩展性:可以独立地扩展抽象和实现,而不需要改动另一部分。
- 复用性:实现部分可以在不同的抽象接口中被复用。
缺点:
- 复杂性:桥接模式可能会增加系统的理解与设计的复杂性。
- 开销:对于性能要求严格的场合,由于桥接模式引入了间接层,可能造成性能的损耗。
适用场景:
- 当一个类存在两个独立变化的维度时,例如,一个维度是逻辑行为,另一个维度是显示行为。
- 当希望复用现有的类层次时,并且希望将多个抽象类和多个具体类组合成多个不同的行为。
Demo:
// 抽象部分
abstract class Abstraction {protected Implementor implementor;public Abstraction(Implementor implementor) {this.implementor = implementor;}public void operation() {implementor.operationImpl();}
}// 实现部分的接口
interface Implementor {void operationImpl();
}// 实现部分的具体实现
class ConcreteImplementorA implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorA operation");}
}class ConcreteImplementorB implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorB operation");}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建实现部分Implementor implementor = new ConcreteImplementorA();// 创建抽象部分,绑定实现部分Abstraction abstraction = new ConcreteAbstraction(implementor);abstraction.operation();}
}// 具体的抽象部分
class ConcreteAbstraction extends Abstraction {public ConcreteAbstraction(Implementor implementor) {super(implementor);}// 可以覆写operation()方法,以扩展行为public void operation() {// 先调用Implementor的行为implementor.operationImpl();// 再执行Abstraction的额外行为additionalOperation();}private void additionalOperation() {System.out.println("Additional operation in ConcreteAbstraction");}
}
在这个示例中,Abstraction
是抽象部分,它包含一个对Implementor
的引用,并定义了一个operation()
方法。Implementor
是实现部分的接口,定义了一个operationImpl()
方法。ConcreteImplementorA
和 ConcreteImplementorB
是实现部分的具体实现。ConcreteAbstraction
是具体的抽象部分,它继承自Abstraction
并可以扩展行为。
桥接模式通过将抽象和实现分离,使得两者可以独立地扩展和复用,从而提高了系统的灵活性和可维护性。
3.2.3. 组合模式
组合模式(Composite Pattern),也被称为“部分-整体”模式,是一种结构型设计模式。它允许用户以一致的方式处理个别对象和对象组合。组合模式通过提供一个统一的接口来隐藏对象和对象组合之间的差异,使得客户端代码可以像处理对象一样处理对象的组合。
定义: 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
优点:
- 透明性:叶子节点和组合节点对客户端来说没有区别,客户端可以统一地使用。
- 灵活性:可以很容易地增加新的叶子节点和组合节点。
- 安全性:组合结构的安全和稳固,因为客户端代码不需要改变,就可以支持新的组合。
缺点:
- 增加设计复杂性:在简单的情况下,使用组合模式可能增加设计的理解难度。
- 使用限制:对树结构的节点增加、删除和搜索等操作可能需要额外的操作。
适用场景:
- 当需要将对象组合成树形结构时。
- 当希望用户对单个对象和组合对象的使用具有一致性时。
Demo:
// 组件接口
interface Component {void operation();
}// 叶子节点
class Leaf implements Component {public void operation() {System.out.println("Leaf operation.");}
}// 组合节点
class Composite implements Component {private List<Component> children = new ArrayList<>();public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}public void operation() {for (Component child : children) {child.operation();}}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建组合节点和叶子节点Composite composite = new Composite();Component leaf1 = new Leaf();Component leaf2 = new Leaf();// 将叶子节点添加到组合节点composite.add(leaf1);composite.add(leaf2);// 通过组合节点统一操作叶子节点composite.operation();}
}
在这个示例中,Component
是组件接口,定义了组合中所有对象的一致操作。Leaf
是叶子节点,实现了组件接口的具体对象。Composite
是组合节点,它实现了与叶子节点相同的接口,并且在内部聚合了其他组件,可以是叶子节点也可以是其他组合节点。
组合模式允许客户端通过统一的接口来处理对象和对象的组合,隐藏了如何通过组合来构建整体的复杂性。这种模式适用于创建树状结构的应用程序,例如文件系统、组织结构图等。
3.2.4. 装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于对象的组合,它是作为现有类的一个包装。
定义:
动态地添加额外的功能到一个对象上,同时又不改变其类。
优点:
- 灵活性:通过使用装饰器,可以在运行时给对象添加功能。
- 扩展性:可以为老系统添加装饰器,以提高其性能。
- 代码复用:装饰器可以被多个类重用。
缺点:
- 增加系统的复杂性:过度使用装饰器模式可能会使系统变得复杂。
- 可能会引起性能问题:在对象周围过度包装装饰器可能会影响性能。
适用场景:
- 当需要为对象添加功能时。
- 当不希望使用继承或使用继承不方便时。
Demo:
// 抽象组件
interface Component {void operate();
}// 具体组件
class ConcreteComponent implements Component {public void operate() {System.out.println("ConcreteComponent operate");}
}// 抽象装饰器
interface Decorator extends Component {// 可以设置一个关联组件的引用void setComponent(Component component);
}// 具体装饰器
class ConcreteDecoratorA implements Decorator {private Component component;public void setComponent(Component component) {this.component = component;}public void operate() {if (component != null) {component.operate();}// 添加额外的功能addedBehavior();}private void addedBehavior() {System.out.println("ConcreteDecoratorA added behavior");}
}// 客户端代码
public class Client {public static void main(String[] args) {Component component = new ConcreteComponent();component = new ConcreteDecoratorA();component.setComponent(component);component.operate();}
}
在这个示例中,Component
是一个抽象组件,定义了对象的接口。ConcreteComponent
是一个具体组件,实现了抽象组件定义的接口。Decorator
是一个抽象装饰器,它也遵循与组件相同的接口,并且持有一个组件的引用。ConcreteDecoratorA
是一个具体装饰器,通过实现Decorator
接口,它向组件添加了额外的行为。
装饰器模式允许你通过组合对象来提供新的行为,而不是通过继承。这使得你可以在不影响其他对象的情况下,控制对象的装饰,从而提供更灵活的设计。
3.2.5. 外观模式
外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口来访问一个子系统中的一组接口。外观模式定义了一个高层接口,让子系统中的复杂交互被简化成单一入口。这种设计模式隐藏了系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
定义: 为子系统中的一组接口提供一个统一的接口,让子系统更容易使用。
优点:
- 简化接口:客户端通过一个接口就能与子系统交互,无需了解子系统内部的复杂性。
- 解耦:减少了客户端与子系统组件的依赖,提高了子系统的独立性。
- 层次结构:客户端只需要关注外观对象,不需要了解子系统内部的实现细节。
缺点:
- 理解困难:对于不熟悉系统的新开发者,外观模式可能会隐藏内部功能,导致理解上的困难。
- 灵活性降低:使用外观模式可能会限制子系统的部分功能,因为外观对象可能只提供了有限的接口。
适用场景:
- 当需要提供一个简单接口来访问复杂的子系统时。
- 当需要客户端与多个子系统组件交互,并且这些交互被集中在一个对象中时。
Demo:
// 子系统接口
interface Subsystem {void operation();
}// 子系统实现
class SubsystemOne implements Subsystem {public void operation() {System.out.println("SubsystemOne operation");}
}class SubsystemTwo implements Subsystem {public void operation() {System.out.println("SubsystemTwo operation");}
}// 外观类
class Facade {private Subsystem subsystemOne;private Subsystem subsystemTwo;public Facade() {subsystemOne = new SubsystemOne();subsystemTwo = new SubsystemTwo();}public void operation() {// 协调子系统中的对象完成一系列操作subsystemOne.operation();subsystemTwo.operation();}
}// 客户端代码
public class Client {public static void main(String[] args) {Facade facade = new Facade();facade.operation();}
}
在这个示例中,Subsystem
是子系统接口,定义了子系统的操作。SubsystemOne
和 SubsystemTwo
是子系统的具体实现。Facade
是外观类,它聚合了子系统中的对象,提供了一个统一的操作接口。客户端通过外观对象的operation()
方法与子系统交互,而不需要知道子系统内部的实现细节。
外观模式通过提供一个简化的接口,使得客户端可以更容易地与复杂的子系统交互,同时隐藏了子系统的复杂性。这种模式非常适合于需要简化系统访问的场景。
3.2.6. 享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它主要用于减少创建对象的数量,从而减少内存占用和提高性能。享元模式通过共享对象来实现这一目的,确保大量相同或相似对象可以通过共享的方式重用。
定义: 使用共享对象来支持大量的细粒度对象,这些对象外部状态不同,但是内部状态可以保持一致。
优点:
- 减少内存占用:通过共享对象减少了内存中对象的数量。
- 提高性能:减少了创建和销毁对象的开销。
- 降低耦合度:享元对象不依赖于其他对象的状态。
缺点:
- 增加了系统的复杂性:需要分离内部状态和外部状态,这可能使得系统设计变得更复杂。
- 状态共享可能导致的并发问题:如果享元对象的内部状态被多个客户端共享,需要特别注意线程安全。
适用场景:
- 当一个应用程序使用了大量的对象,而这些对象造成了很大的内存开销时。
- 当对象的大多数状态可以外部化,并且可以由对象之外的参数来维护时。
Demo:
// 享元接口
interface Flyweight {void operation(String extrinsicState);
}// 具体享元类
class ConcreteFlyweight implements Flyweight {private String intrinsicState; // 内部状态public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}public void operation(String extrinsicState) {// 操作内部状态和外部状态System.out.println("Operation on internal state: " + intrinsicState + ", with extrinsic state: " + extrinsicState);}
}// 享元工厂,用于创建并管理享元对象
class FlyweightFactory {private static Map<String, Flyweight> flyweights = new HashMap<>();public static Flyweight getFlyweight(String intrinsicState) {if (!flyweights.containsKey(intrinsicState)) {flyweights.put(intrinsicState, new ConcreteFlyweight(intrinsicState));}return flyweights.get(intrinsicState);}
}// 客户端代码
public class Client {public static void main(String[] args) {// 使用享元工厂获取享元对象Flyweight flyweightOne = FlyweightFactory.getFlyweight("State1");flyweightOne.operation("StateA");Flyweight flyweightTwo = FlyweightFactory.getFlyweight("State2");flyweightTwo.operation("StateB");}
}
在这个示例中,Flyweight
是享元接口,定义了享元对象的操作。ConcreteFlyweight
是具体享元类,它包含了内部状态,并且实现了Flyweight
接口。FlyweightFactory
是享元工厂,负责创建和管理享元对象,确保相同内部状态的对象不会重复创建。
享元模式通过共享相同内部状态的享元对象来减少内存占用,适用于创建大量相似对象的场景。然而,这种模式需要仔细设计,以确保对象的内部状态和外部状态被适当地分离和维护。
3.2.7. 代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一个代理或占位符,以控制对这个对象的访问。代理模式可以在不改变对象的代码的情况下,为对象添加额外的功能,例如延迟初始化、访问控制、日志记录、缓存等。
定义: 为其他对象提供一个代理或占位符,以控制对这个对象的访问。
优点:
- 控制访问:可以控制对原始对象的访问,提供权限检查。
- 延迟初始化:可以在需要时才创建实际对象,提高性能。
- 其他功能:可以添加额外的功能,如日志记录、性能监控等。
缺点:
- 增加复杂性:可能会增加系统的复杂性。
- 性能影响:有时代理对象可能会引入一些性能开销。
适用场景:
- 当需要对对象创建加以控制时。
- 当需要为对象添加额外的功能,如延迟初始化、访问控制等。
Demo:
// 真实主题接口
interface Subject {void request();
}// 真实主题实现
class RealSubject implements Subject {public void request() {System.out.println("RealSubject: Handling request.");}
}// 代理主题,实现了与真实主题相同的接口
class ProxySubject implements Subject {private RealSubject realSubject;public void request() {if (realSubject == null) {// 延迟初始化realSubject = new RealSubject();}realSubject.request();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 使用代理对象Subject proxySubject = new ProxySubject();proxySubject.request();}
}
在这个示例中,Subject
是真实主题接口,定义了真实对象需要实现的方法。RealSubject
是真实主题实现,实现了主题接口的具体业务逻辑。ProxySubject
是代理主题,它实现了与真实主题相同的接口,并在内部维护了对真实主题的引用。客户端通过代理对象来访问真实对象,代理对象可以在内部控制对真实对象的访问。
代理模式可以在不修改真实对象的情况下,控制对真实对象的访问,并在访问过程中添加额外的功能。这种模式非常适合于需要对对象访问进行控制或添加额外功能的场景。
3.3. 行为型模式
3.3.1. 责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许将一个请求沿着处理者链进行传递,直到有处理者处理它为止。每个处理者都有责任去处理请求,或者将它转发给链中的下一个处理者。这种模式常用于在多个对象之间传递请求,直到某个对象能够处理该请求。
定义:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
优点:
- 解耦:将请求的发送者和接收者解耦。
- 可扩展性:可以动态地添加或删除处理者,或者改变处理者的顺序。
- 灵活性:请求的处理流程更加灵活。
缺点:
- 性能:请求可能会经过多个处理者,可能存在性能问题。
- 不确定性:不能保证请求一定被处理,除非明确设计。
- 循环依赖:不当的设计可能会导致循环依赖的问题。
适用场景:
- 当一个请求有多个对象可以处理,且具体哪个对象处理该请求由运行时刻决定。
- 当你想通过一个集中的入口点,将职责委托给多个处理对象时。
Demo:
// 请求接口
interface Request {String getRequestData();
}// 具体请求
class ConcreteRequest implements Request {private String data;public ConcreteRequest(String data) {this.data = data;}public String getRequestData() {return data;}
}// 处理者接口
abstract class Handler {protected Handler nextHandler; // 下一个处理者public void setNext(Handler nextHandler) {this.nextHandler = nextHandler;}abstract void handleRequest(Request request);
}// 具体处理者
class ConcreteHandler1 extends Handler {private static final String RESPONSIBILITY = "Request Type A";public void handleRequest(Request request) {if (RESPONSIBILITY.equals(request.getRequestData())) {System.out.println("Handler 1 is handling this request.");} else {if (nextHandler != null) {nextHandler.handleRequest(request);}}}
}class ConcreteHandler2 extends Handler {private static final String RESPONSIBILITY = "Request Type B";public void handleRequest(Request request) {if (RESPONSIBILITY.equals(request.getRequestData())) {System.out.println("Handler 2 is handling this request.");} else {if (nextHandler != null) {nextHandler.handleRequest(request);}}}
}// 客户端代码
public class Client {public static void main(String[] args) {Handler handler1 = new ConcreteHandler1();Handler handler2 = new ConcreteHandler2();// 构建责任链handler1.setNext(handler2);// 创建请求并发送Request request = new ConcreteRequest("Request Type A");handler1.handleRequest(request);}
}
在这个示例中,Request
是请求接口,定义了请求必须实现的方法。ConcreteRequest
是具体请求,实现了请求接口。Handler
是处理者接口,定义了处理请求的方法,并且包含一个指向下一个处理者的引用。ConcreteHandler1
和 ConcreteHandler2
是具体处理者,它们实现了处理请求的逻辑,并根据请求的类型决定是否处理请求或将请求转发给链中的下一个处理者。
责任链模式通过对象组成的链结构来处理请求,使得请求的发送者不必知道请求的处理者是谁,从而降低了系统的耦合度。
3.3.2. 命令模式
命令模式(Command Pattern)是一种行为型设计模式,它将一个请求封装为一个对象,从而允许用户使用不同的请求、队列或日志请求来参数化其他对象。命令模式通常用于解耦请求的发送者和接收者,让发送者和接收者之间通过命令对象进行交互。
定义: 将一个请求封装为一个对象,从而允许用户使用不同的请求来参数化其他对象。
优点:
- 解耦:命令模式将调用操作的对象与知道如何实现该操作的对象解耦。
- 扩展性:可以容易地扩展新的命令。
- 宏命令:可以组合多个命令到一个宏命令中。
缺点:
- 增加系统复杂性:可能会增加系统中对象的数量。
- 命令过多:如果系统中命令对象太多,可能会导致管理上的复杂性。
适用场景:
- 当需要将请求封装为对象,从而可以用不同的请求对客户进行参数化时。
- 当需要支持取消操作时。
- 当需要支持事务和日志功能时。
Demo:
// 命令接口
interface Command {void execute();
}// 接收者,知道如何实施与执行一个请求相关的操作
class Receiver {public void action() {System.out.println("Receiver performs action");}
}// 具体的命令类,实现execute()方法,负责调用接收者的功能
class ConcreteCommand implements Command {private Receiver receiver;public ConcreteCommand(Receiver receiver) {this.receiver = receiver;}public void execute() {receiver.action();}
}// 调用者,要求命令对象执行请求
class Invoker {public void invoke(Command command) {command.execute();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建接收者对象Receiver receiver = new Receiver();// 创建具体命令对象,将接收者对象传入Command command = new ConcreteCommand(receiver);// 创建调用者对象Invoker invoker = new Invoker();// 通过调用者对象执行命令对象invoker.invoke(command);}
}
在这个示例中,Command
是命令接口,定义了执行命令的方法。Receiver
是接收者类,它知道如何实施与执行一个请求相关的操作。ConcreteCommand
是具体命令类,它实现了命令接口的execute()
方法,负责调用接收者的功能。Invoker
是调用者类,它要求命令对象执行请求。
命令模式通过将请求封装为对象,允许用户使用不同的请求来参数化其他对象,从而实现请求发送者和接收者的解耦。这种模式非常适合于需要支持撤销操作、事务和日志功能的场景。
3.2.3. 解释器模式
解释器模式(Interpreter Pattern)是一种行为型设计模式,用于定义一个语言的语法规则,并且构建一个解释器来解释该语言中的句子。这种模式通常用于执行一种特定类型语言(通常是非通用的、简单的或领域特定语言)的解释。
定义: 定义一个语言的语法规则,并且建立一个解释器来解释该语言中的句子。
优点:
- 扩展性:可以方便地添加新的语言文法和表达式。
- 可读性:对于特定领域的问题,使用解释器模式可以提高可读性。
- 易于修改和扩展文法。
缺点:
- 效率问题:对于复杂的文法,解释器模式可能导致性能瓶颈。
- 错误诊断:解释器模式通常难以诊断错误,因为语句的解析过程通常很复杂。
适用场景:
- 当有一个语言需要解释执行,并且可以定义它的文法时。
- 当需要提供一个简单的解释器时。
Demo:
// 抽象表达式类
abstract class Expression {public abstract boolean interpret(String command);
}// 终端表达式
class TerminalExpression extends Expression {public boolean interpret(String command) {// 解释简单的命令System.out.println("Interpreting '" + command + "' as a terminal expression.");return true; // 假设命令被成功解释}
}// 非终端表达式
class NonTerminalExpression extends Expression {private Expression expression;public NonTerminalExpression(Expression expression) {this.expression = expression;}public boolean interpret(String command) {// 解释复合命令System.out.println("Interpreting '" + command + "' as a non-terminal expression.");return expression.interpret(command); // 递归解释子表达式}
}// 解释器的上下文
class Context {// 存储解释器需要的上下文信息
}// 客户端代码
public class Client {public static void main(String[] args) {Expression terminal = new TerminalExpression();Expression nonTerminal = new NonTerminalExpression(terminal);// 创建上下文Context context = new Context();// 解释命令boolean result = nonTerminal.interpret("command");System.out.println("Command interpreted: " + result);}
}
在这个示例中,Expression
是抽象表达式类,定义了解释方法。TerminalExpression
是终端表达式类,它实现了简单的命令解释。NonTerminalExpression
是非终端表达式类,它组合了一个表达式,并且可以解释复合命令。Context
是解释器的上下文,它存储解释过程中需要的信息。
解释器模式允许你定义一个简单的语言的文法,并构建一个解释器来解释该语言的句子。这种模式非常适合于需要解释特定领域语言的场景。然而,对于复杂或通用的语言,使用解释器模式可能导致性能问题和难以维护的代码。
3.2.4. 迭代器模式
迭代器模式(Iterator Pattern)是一种行为型设计模式,它允许用户按顺序访问一个聚合对象中的元素而不需要暴露其内部的表示。迭代器模式提供了一种通过统一的接口来遍历不同类型集合的方法。
定义: 提供一种方法顺序访问聚合对象的元素,同时保持对象的封装。
优点:
- 解耦:迭代器模式使得聚合对象的内部表示与客户端分离,客户端不需要知道集合的具体实现。
- 扩展性:可以在不修改客户端代码的情况下,向系统中添加新的聚合和迭代器。
- 一致性:提供了一种统一的方式来访问聚合对象。
缺点:
- 对于简单的集合,可能会增加编程复杂性。
- 迭代器的维护成本:需要为每个聚合类型都编写迭代器类。
适用场景:
- 当需要访问一个聚合对象,但不能改变其内部结构时。
- 当需要为聚合对象提供多种方式来遍历时。
Demo:
// 聚合接口
interface Aggregate {Iterator createIterator();
}// 具体聚合类
class ConcreteAggregate implements Aggregate {private List items = new ArrayList();public void addItem(String item) {items.add(item);}public Iterator createIterator() {return new ConcreteIterator(this);}
}// 迭代器接口
interface Iterator {boolean hasNext();Object next();
}// 具体迭代器类
class ConcreteIterator implements Iterator {private ConcreteAggregate aggregate;private int currentIndex = 0;public ConcreteIterator(ConcreteAggregate aggregate) {this.aggregate = aggregate;}public boolean hasNext() {return currentIndex < aggregate.items.size();}public Object next() {return aggregate.items.get(currentIndex++);}
}// 客户端代码
public class Client {public static void main(String[] args) {ConcreteAggregate aggregate = new ConcreteAggregate();aggregate.addItem("Item1");aggregate.addItem("Item2");aggregate.addItem("Item3");Iterator iterator = aggregate.createIterator();while (iterator.hasNext()) {Object item = iterator.next();System.out.println(item);}}
}
在这个示例中,Aggregate
是聚合接口,定义了创建迭代器的方法。ConcreteAggregate
是具体聚合类,它实现了聚合接口,并负责维护一个元素列表。Iterator
是迭代器接口,定义了迭代集合元素的方法。ConcreteIterator
是具体迭代器类,它实现了迭代器接口,并负责跟踪当前的遍历位置。
迭代器模式通过提供一个统一的接口来遍历聚合对象,使得客户端可以一致地访问不同类型的集合,而不需要关心集合的具体实现。这种模式非常适合于需要提供一致访问接口的场景。
3.2.5. 中介者模式
中介者模式(Mediator Pattern)是一种行为型设计模式,它定义了一个中介对象,用于封装一系列对象之间的交互。中介者使各对象不需要显示地相互引用,从而使耦合度降低,而且可以独立地改变它们之间的交互。
定义: 用一个中介对象来封装一系列的对象交互,使各对象之间不需要相互显示引用。
优点:
- 降低耦合度:减少了对象之间的依赖,使得它们可以独立地变化。
- 集中控制:中介者可以控制对象之间的交互方式,可以简化系统的理解。
- 易于扩展:增加新的同事对象(即通过中介者进行通信的对象)相对容易。
缺点:
- 中介者过于复杂:如果中介者承担了太多的职责,它可能会变得非常复杂。
- 性能问题:在某些情况下,使用中介者可能导致系统性能降低,因为所有交互都通过中介者来转发。
适用场景:
- 当一个系统由许多类似对象组成,而这些对象之间的通信非常频繁时。
- 当对象之间的交互非常复杂,并且当你希望将这些交互封装起来时。
Demo:
// 中介者接口
interface Mediator {void register(String colleagueName, Colleague colleague);void relay(String colleagueName);
}// 具体中介者
class ConcreteMediator implements Mediator {private Map<String, Colleague> colleagues = new HashMap<>();public void register(String colleagueName, Colleague colleague) {colleagues.put(colleagueName, colleague);}public void relay(String colleagueName) {Colleague colleague = colleagues.get(colleagueName);if (colleague != null) {colleague.notify();}}
}// 同事类接口
interface Colleague {void setMediator(Mediator mediator);void notify();
}// 具体同事类
class ConcreteColleagueA implements Colleague {private ConcreteMediator mediator;public void setMediator(ConcreteMediator mediator) {this.mediator = mediator;}public void notify() {System.out.println("Colleague A is notified.");}public void businessMethod() {// 业务逻辑mediator.relay("ColleagueB");}
}class ConcreteColleagueB implements Colleague {private ConcreteMediator mediator;public void setMediator(ConcreteMediator mediator) {this.mediator = mediator;}public void notify() {System.out.println("Colleague B is notified.");}// ...
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建中介者ConcreteMediator mediator = new ConcreteMediator();// 创建同事对象,并设置中介者ConcreteColleagueA colleagueA = new ConcreteColleagueA();colleagueA.setMediator(mediator);ConcreteColleagueB colleagueB = new ConcreteColleagueB();colleagueB.setMediator(mediator);// 中介者注册同事对象mediator.register("ColleagueA", colleagueA);mediator.register("ColleagueB", colleagueB);// 某个同事对象的业务逻辑触发交互colleagueA.businessMethod();}
}
在这个示例中,Mediator
是中介者接口,定义了中介者的行为。ConcreteMediator
是具体中介者,它管理同事对象之间的交互。Colleague
是同事类接口,定义了同事对象与中介者之间的通信协议。ConcreteColleagueA
和 ConcreteColleagueB
是具体同事类,它们实现了同事类接口,并通过中介者与其他同事对象进行通信。
中介者模式通过一个中介者对象来封装多个对象之间的交互,从而降低了对象之间的耦合度,使得对象可以更加独立地变化。这种模式非常适合于系统中对象之间存在复杂交互的情况。
3.2.6. 备忘录模式
备忘录模式(Memento Pattern)是一种行为型设计模式,用于在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将对象恢复到原先保存的状态。备忘录模式常用于实现软件中的撤销功能,或者在需要回滚操作时使用。
定义: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以后可以将对象恢复到原先保存的状态。
优点:
- 安全性:备忘录可以保护原始对象的内部状态不被外部直接修改。
- 灵活性:可以捕获和保存多个状态,实现多状态的存储和恢复。
缺点:
- 资源消耗:如果保存的状态过多,可能会消耗较多的内存。
- 复杂性:增加了系统的复杂性,需要理解状态的保存和恢复机制。
适用场景:
- 当需要保存一个对象的当前状态,以便在将来恢复时使用。
- 当对象的某些状态信息必须保密,不能直接暴露给其他对象时。
Demo:
// 发起人角色,需要被保存和恢复状态
class Originator {private String state;public String getState() {return state;}public void setState(String state) {this.state = state;}// 创建一个备忘录public Memento saveStateToMemento() {return new Memento(state);}// 从备忘录恢复状态public void getStateFromMemento(Memento memento) {state = memento.getState();}
}// 备忘录角色,存储发起人的内部状态
class Memento {private String state;public Memento(String state) {this.state = state;}public String getState() {return state;}
}// 负责人角色,负责创建和管理备忘录
class Caretaker {private Memento memento;public void setMemento(Memento memento) {this.memento = memento;}public Memento getMemento() {return memento;}
}// 客户端代码
public class Client {public static void main(String[] args) {Originator originator = new Originator();originator.setState("State #1");// 创建备忘录并保存状态Caretaker caretaker = new Caretaker();caretaker.setMemento(originator.saveStateToMemento());// 修改发起人状态originator.setState("State #2");// 从备忘录恢复状态originator.getStateFromMemento(caretaker.getMemento());System.out.println("Restored state: " + originator.getState());}
}
在这个示例中,Originator
是发起人角色,它拥有需要被保存和恢复的状态。saveStateToMemento()
方法用于创建备忘录并保存当前状态,getStateFromMemento()
方法用于从备忘录恢复状态。
Memento
是备忘录角色,它存储发起人的内部状态。为了保持封装性,备忘录通常只提供给负责人访问。
Caretaker
是负责人角色,它负责创建和管理备忘录。负责人不应该知道备忘录内部是如何实现的,因此它只负责保存和提供备忘录。
备忘录模式通过引入备忘录和负责人角色,允许在不破坏发起人封装性的前提下,捕获和恢复发起人的状态。这种模式非常适合于需要实现撤销或回滚功能的场景。
3.2.7. 观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。观察者模式常用于实现事件处理系统,如图形用户界面的事件处理。
定义: 当对象间存在一对多关系时,则使用观察者模式。一个被观察的对象变化时,所有依赖它的对象都会得到通知并自动更新。
优点:
- 解耦:观察者模式可以实现主题对象与其他对象之间的松耦合。
- 可扩展性:可以在不修改主题对象的情况下,添加新的观察者。
- 中间人:提供了一个主题对象和其他观察者对象之间的中介对象。
缺点:
- 性能:当一个主题对象有大量观察者或者观察者之间的依赖过于紧密时,通知的开销可能会很大。
- 循环依赖:如果处理不当,观察者模式可能会导致循环依赖。
适用场景:
- 当一个对象(观察目标)的状态改变,需要使其他对象(观察者)自动更新时。
- 当需要建立一个事件多级触发机制时。
最佳实践Demo(含注释):
// 观察者接口
interface Observer {void update(String message);
}// 主题接口
interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}// 具体主题类
class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();private String state;public void registerObserver(Observer o) {if (!observers.contains(o)) {observers.add(o);}}public void removeObserver(Observer o) {observers.remove(o);}public void notifyObservers() {for (Observer observer : observers) {observer.update(this.state);}}public void setState(String state) {this.state = state;notifyObservers();}public String getState() {return state;}
}// 具体观察者类
class ConcreteObserver implements Observer {private ConcreteSubject subject;public ConcreteObserver(ConcreteSubject subject) {this.subject = subject;}public void printState() {System.out.println("Observer state: " + subject.getState());}public void update(String message) {printState();}
}// 客户端代码
public class Client {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();// 创建观察者并注册到主题Observer observer1 = new ConcreteObserver(subject);subject.registerObserver(observer1);Observer observer2 = new ConcreteObserver(subject);subject.registerObserver(observer2);// 改变主题状态,观察者收到通知并更新subject.setState("New State");}
}
在这个示例中,Observer
是观察者接口,定义了更新接口。Subject
是主题接口,定义了注册、移除和通知观察者的方法。ConcreteSubject
是具体主题类,它实现了主题接口,并维护了观察者列表。当主题对象的状态发生变化时,它会通知所有注册的观察者。
ConcreteObserver
是具体观察者类,它实现了观察者接口,并定义了接收到通知后的更新行为。
客户端代码创建了主题对象和观察者对象,并将观察者注册到主题对象上。当主题对象的状态发生变化时,所有注册的观察者都会收到通知并自动更新。
观察者模式通过定义对象之间的依赖关系,允许在主题对象状态变化时自动更新观察者对象,从而实现解耦和自动更新。这种模式非常适合于需要实现事件处理和响应的场景。
3.2.8. 状态模式
状态模式(State Pattern)是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。这个对象看起来似乎修改了它的类。状态模式通过将各种状态转移逻辑分布到表示各种状态的一系列类中,将对象的状态逻辑与对象本身分离,实现了对象行为的动态变化。
定义: 允许对象在内部状态改变时改变它的行为,这个对象看起来就像改变了它的类一样。
优点:
- 封装性:将与特定状态相关的行为局部化,并且将所有与状态转换相关的行为保留在状态对象的内部。
- 可维护性:通过将每个状态转移逻辑放在单独的类中,使得代码易于管理。
- 可扩展性:当需要添加新的状态时,只需添加一个新的状态类,不会影响到其他类。
缺点:
- 增加系统复杂性:可能会导致系统中类的数量增加。
- 状态转移的控制:如果状态转移逻辑复杂,可能难以管理状态转换的顺序。
适用场景:
- 当一个对象的行为取决于它的状态,并且它的状态是可变的时。
- 当代码需要根据对象的状态进行条件判断时。
Demo:
// 环境类(Context),定义了客户端使用的接口,并且维护一个ConcreteState子类的实例
class Context {private State state;// 设置新状态public void setState(State state) {this.state = state;}// 根据当前状态执行操作public void request() {state.handle(this);}
}// 抽象状态类(State),定义一个接口以封装与Context的一个特定状态相关的行为
interface State {void handle(Context ctx);
}// 具体状态类(ConcreteState),实现State接口定义的行为
class ConcreteStateA implements State {public void handle(Context ctx) {System.out.println("Handling request in ConcreteStateA");// 根据逻辑可能需要改变Context的状态}
}class ConcreteStateB implements State {public void handle(Context ctx) {System.out.println("Handling request in ConcreteStateB");// 根据逻辑可能需要改变Context的状态}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建环境类Context context = new Context();// 初始化状态context.setState(new ConcreteStateA());context.request();// 改变状态context.setState(new ConcreteStateB());context.request();}
}
在这个示例中,Context
是环境类,它定义了客户端使用的接口,并且维护了一个状态对象的实例。State
是抽象状态类,定义了状态的接口。ConcreteStateA
和 ConcreteStateB
是具体状态类,实现了状态接口定义的行为。
客户端代码创建了环境类的对象,并且根据需要改变环境类的状态。当环境类的状态改变时,它的行为也随之改变。
状态模式通过将各种状态转移逻辑分布到表示各种状态的一系列类中,将对象的状态逻辑与对象本身分离。这种模式非常适合于需要根据对象状态进行条件判断的场景。
3.2.9. 策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来让它们可以互换使用。策略模式让算法的变化独立于使用算法的用户,用户只需要关注算法的结果,而不必知道算法的实现细节。
定义:
定义了一系列算法,并将每个算法封装起来让它们可以互换。
优点:
- 解耦:策略模式将算法的使用从算法的实现中分离出来。
- 可扩展性:新的算法可以很容易地添加到系统中。
- 可替换:可以很容易地替换或更新现有的算法。
缺点:
- 增加复杂性:可能会增加系统中策略类的数量。
- 性能考虑:由于策略模式使用多态,可能会有一些性能开销。
适用场景:
- 当需要在运行时选择不同的处理方式时。
- 当一个系统中有多个子系统,而各个子系统有类似的行为,但具体实现不同,可以避免使用条件语句来决定使用哪个子系统时。
Demo:
// 策略接口
interface Strategy {void execute();
}// 具体策略A
class ConcreteStrategyA implements Strategy {public void execute() {System.out.println("Executing ConcreteStrategyA");}
}// 具体策略B
class ConcreteStrategyB implements Strategy {public void execute() {System.out.println("Executing ConcreteStrategyB");}
}// 环境类
class Context {private Strategy strategy;public Context(Strategy strategy) {this.strategy = strategy;}public void setStrategy(Strategy strategy) {this.strategy = strategy;}public void executeStrategy() {strategy.execute();}
}// 客户端代码
public class Client {public static void main(String[] args) {// 创建环境类,注入策略Context context = new Context(new ConcreteStrategyA());// 执行策略context.executeStrategy();// 更换策略context.setStrategy(new ConcreteStrategyB());context.executeStrategy();}
}
在这个示例中,Strategy
是策略接口,定义了所有支持的算法。ConcreteStrategyA
和 ConcreteStrategyB
是具体策略类,它们实现了策略接口。Context
是环境类,它定义了客户端使用的接口,并且维护了一个策略对象。
客户端代码创建了环境类的对象,并且根据需要改变环境类使用的策略。当环境类使用不同的策略时,它的行为也随之改变。
策略模式通过定义算法族,并将每个算法封装起来,让它们可以互换使用,使得算法的变化独立于使用算法的用户。这种模式非常适合于需要动态选择算法的场景。
3.2.10. 模板方法模式
模板方法模式(Template Method Pattern)是一种行为型设计模式,它在一个方法中定义了一个算法的骨架,并将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
定义: 定义一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法结构的情况下,重新定义算法的某些步骤。
优点:
- 代码复用:模板方法模式通过提供一个通用的算法骨架,允许代码复用。
- 可扩展性:通过扩展子类,可以很容易地扩展新的功能。
- 约束行为:模板方法可以控制子类的扩展,限制子类覆盖特定的方法。
缺点:
- 每个不同的算法都需要一个子类实现,这可能会导致类的个数增加。
- 对于复杂的算法,模板方法可能难以适应。
适用场景:
- 当需要将一个算法分解为一系列步骤,并且希望子类控制某些步骤时。
- 当需要通过子类来实现算法的某些细节,而不影响算法的整体结构时。
Demo:
// 抽象模板类
abstract class Template {// 模板方法定义了算法的骨架,它可能由一系列步骤组成public final void templateMethod() {step1();step2();step3();hanger();}// 钩子方法,可在子类中重写,但不强制protected void 钩子方法() {// 默认行为}// 这些方法是需要由子类实现的,它们是具体步骤protected abstract void step1();protected abstract void step2();protected abstract void step3();
}// 具体实现类
class ConcreteTemplate extends Template {@Overrideprotected void step1() {System.out.println("执行步骤1");}@Overrideprotected void step2() {System.out.println("执行步骤2");}@Overrideprotected void step3() {System.out.println("执行步骤3");}// 重写钩子方法@Overrideprotected void hanger() {System.out.println("执行钩子方法");}
}// 客户端代码
public class Client {public static void main(String[] args) {Template template = new ConcreteTemplate();template.templateMethod();}
}
在这个示例中,Template
是一个抽象模板类,它定义了模板方法,该方法是一个算法的骨架,由一系列步骤组成。step1()
, step2()
, step3()
是需要由子类实现的抽象方法,它们代表算法的步骤。钩子方法()
是一个钩子方法,它提供了一个扩展点,允许子类在不修改模板方法的情况下,调整算法的某些细节。
ConcreteTemplate
是一个具体实现类,它继承自抽象模板类,并实现了所有的抽象方法以及钩子方法。
客户端代码通过创建具体实现类的实例,并调用模板方法来执行算法。
模板方法模式通过定义算法骨架和延迟一些步骤到子类中实现,允许子类在不改变算法结构的情况下,重新定义算法的某些步骤。这种模式非常适合于实现算法的可扩展性和可重用性。
3.2.11. 访问者模式
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许在不修改对象结构的情况下,添加新的操作。访问者模式通过将算法移动到对象结构之外,使对象结构更容易扩展,而不受算法变化的影响。
定义:
表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
优点:
- 扩展性:可以在不修改对象结构的情况下,添加新的操作。
- 分离算法和对象结构:使算法和对象结构分离,提高灵活性。
- 复用性:访问者可以对多个对象结构进行操作。
缺点:
- 对象结构的变化受限:如果对象结构需要变化,可能需要修改访问者接口和所有的具体访问者类。
- 增加系统的复杂性:引入新的访问者可能增加系统的复杂性。
适用场景:
- 当需要对一个对象结构添加新的操作,而又不想修改对象结构时。
- 当要处理的对象结构中的对象类型比较多,需要避免使用条件语句时。
Demo:
// 访问者接口
interface Visitor {void visit(ConcreteElementA element);void visit(ConcreteElementB element);
}// 具体元素A
class ConcreteElementA {public void accept(Visitor visitor) {visitor.visit(this);}public void performAction() {System.out.println("Performing action on ConcreteElementA");}
}// 具体元素B
class ConcreteElementB {public void accept(Visitor visitor) {visitor.visit(this);}public void performAction() {System.out.println("Performing action on ConcreteElementB");}
}// 具体访问者
class ConcreteVisitor implements Visitor {public void visit(ConcreteElementA element) {element.performAction();}public void visit(ConcreteElementB element) {element.performAction();}
}// 客户端代码
public class Client {public static void main(String[] args) {ConcreteElementA elementA = new ConcreteElementA();ConcreteElementB elementB = new ConcreteElementB();Visitor visitor = new ConcreteVisitor();elementA.accept(visitor);elementB.accept(visitor);}
}
在这个示例中,Visitor
是访问者接口,定义了访问者可以进行的操作。ConcreteElementA
和 ConcreteElementB
是具体元素类,它们实现了一个accept()
方法,该方法接受一个访问者对象。performAction()
方法是元素执行的操作。
ConcreteVisitor
是具体访问者类,实现了访问者接口中定义的所有方法。它定义了访问者如何对每一个具体元素执行操作。
客户端代码通过创建元素对象和访问者对象,然后通过元素对象的accept()
方法将访问者对象传入,从而执行相应的操作。
访问者模式通过将算法封装到访问者对象中,使对象结构更容易扩展,而不受算法变化的影响。这种模式非常适合于对象结构相对稳定,但需要在其上定义新的操作的情况。
4.总结
设计模式是软件工程中用于解决特定问题的一系列最佳实践,它们帮助开发者在面对常见问题时做出更好的设计决策。设计模式不是现成的代码,而是一套指导原则,用来指导开发者如何组织代码结构,以便于更好地应对变化和提高代码的可维护性。本总结将对设计模式进行分类,并提供每种模式的关键特点、优缺点、适用场景以及代码示例,以帮助理解设计模式的实用性和应用方法。
4.1. 创建型模式
创建型模式关注对象的创建,隐藏对象创建的复杂性,提高程序的可扩展性。
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
- 工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪个类。
- 抽象工厂模式:提供一个接口,用于创建一系列相关或依赖的对象。
- 建造者模式:将一个复杂对象的构建与其表示分离,允许通过相同的构建过程创建不同的表示。
- 原型模式:通过复制现有的实例来创建新的实例。
4.2. 结构型模式
结构型模式主要关注对象的组合,通过对象的组合提高程序的模块化。
- 适配器模式:允许将不兼容的接口转换为一个可以使用的兼容接口。
- 桥接模式:将抽象部分与其实现部分分离,使它们可以独立地变化。
- 组合模式:允许将对象组合成树形结构以表示“部分-整体”的层次结构。
- 装饰器模式:动态地添加额外的功能到一个对象上,而不是通过继承。
- 外观模式:为子系统中的一组接口提供一个统一的接口。
- 享元模式:运用共享技术有效地支持大量细粒度的对象。
- 代理模式:为其他对象提供一个代理或占位符,以控制对这个对象的访问。
4.3. 行为型模式
行为型模式主要关注对象之间的交互,描述对象如何协作以完成某种任务。
- 责任链模式:使多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合。
- 命令模式:将一个请求封装为一个对象,从而允许用户使用不同的请求来参数化其他对象。
- 解释器模式:定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
- 迭代器模式:提供一种方法顺序访问聚合对象的元素,同时保持对象的封装。
- 中介者模式:用一个中介对象来封装一系列的对象交互。
- 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 观察者模式:当对象间存在一对多关系时,则使用观察者模式。
- 状态模式:允许对象在其内部状态改变时改变它的行为。
- 策略模式:定义一系列算法,把它们一个个封装起来,并使它们可以互换。
- 模板方法模式:定义一个算法的骨架,将一些步骤延迟到子类中实现。
- 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
设计模式是软件设计的强大工具,它们提供了一种思考和沟通架构问题的方式。掌握设计模式有助于提高代码质量,促进重用,以及更有效地处理复杂性。然而,设计模式并不是万能的,它们应该根据具体的应用场景和实际需求来选择和应用。