type
status
date
slug
summary
tags
category
icon
password
comment
属实没想到这课要考试,那就每天复习一点吧
一、OOP——UML——JAVA1. 类(Class)和对象(Object)2. 超类和子类3. 面向对象的内容3.1 抽象Abstraction3.2 封装Encapsulation3.3 继承Inheritance3.4 多态Polymorphism4. 类图中常用符号4.1 依赖(Dependency)4.2 关联(Association)4.3 聚合(Aggregation)4.4 组合(Composition)4.5 实现(Implementation)4.6 继承(Inheritance)4.7 总结5. Java中的OOP6. Java的OOP访问控制7. Java的OOP7.1 继承7.2 子类中的功能7.3 抽象方法和最终方法/类7.4 接口7.5 实现和使用接口7.6 接口与抽象类二、软件设计原则1. 优秀设计的特点1.1 代码重复使用1.2 可扩展性2. 良好设计原则2.1 封装不同内容2.2 面向接口编程,而不是面向实现2.3 优先使用组合(Composition)而不是继承(Inheritance)3. SOLID原则3.1 SOLID 1: 单一责任原则(Single Responsibility Principle)3.2 SOLID 2: 开放/封闭原则(Open/Closed Principle)3.3 SOLID 3: 里氏替换原则(Liskov Substitution Principle)3.4 SOLID 4: 接口隔离原则(Interface Segregation Principle)3.5 SOLID 5: 依赖倒置原则(Dependency Inversion Principle)三、创建型模式1. Factory Method Pattern2. Abstract Factory Pattern3. Builder Pattern4. Prototype Pattern5. Singleton Pattern四、结构型模式1. Adapter Pattern2. Bridge Pattern3. Composite Pattern4. Decorator Pattern5. Facade Pattern6. Flyweight Pattern7. Proxy Pattern五、行为型模式1. Chain of Responsibility Pattern2. Command Pattern3. Iterator Pattern4. Mediator Pattern5. Memento Pattern6. Observer Pattern7. State Pattern8. Strategy Pattern9. Template Method Pattern10. Visitor Pattern六、代码理解1. 工厂方法模式(Factory Method Pattern)1.1 Java代码实例1.2 生活实例(代码表示)2. 抽象工厂模式(Abstract Factory Pattern)2.1 Java代码实例2.2 生活实例(代码表示)3. 建造者模式(Builder Pattern)3.1 Java代码实例3.2 生活实例(代码表示)4. 原型模式(Prototype Pattern)4.1 Java代码实例4.2 生活实例(代码表示)5. 单例模式(Singleton Pattern)5.1 Java代码实例5.2 生活实例(代码表示)6. 适配器模式(Adapter Pattern)6.1 Java代码实例6.2 生活实例(代码表示)7. 桥接模式(Bridge Pattern)7.1 Java代码实例7.2 生活实例(代码表示)8. 组合模式(Composite Pattern)8.1 Java代码实例8.2 生活实例(代码表示)9.装饰器模式(Decorator Pattern)9.1 Java代码实例9.2 生活实例(代码表示)10.外观模式(Facade Pattern)10.1 Java代码实例10.2 生活实例(代码表示)11. 享元模式(Flyweight Pattern)11.1 Java代码实例11.2 生活实例(代码表示)12. 代理模式(Proxy Pattern)12.1 Java代码实例12.2 生活实例(代码表示)13. 责任链模式(Chain of Responsibility Pattern)13.1 Java代码实例13.2 生活实例(代码表示)14. 命令模式(Command Pattern)14.1 Java代码实例14.2 生活实例(代码表示)15. 迭代器模式(Iterator Pattern)15.1 Java代码实例15.2 生活实例(代码表示)16. 中介者模式(Mediator Pattern)16.1 Java代码实例16.2 生活实例(代码表示)17. 备忘录模式(Memento Pattern)17.1 Java代码实例17.2 生活实例(代码表示)18. 观察者模式(Observer Pattern)18.1 Java代码实例18.2 生活实例(代码表示)19. 状态模式(State Pattern)19.1 Java代码实例19.2 生活实例(代码表示)20. 策略模式(Strategy Pattern)20.1 Java代码实例20.2 生活实例(代码表示)21. 访问者模式(Visitor Pattern)21.1 Java代码实例21.2 生活实例(代码表示)22. 模板方法模式(Template Method Pattern)22.1 Java代码实例22.2 生活实例(代码表示)
一、OOP——UML——JAVA
1. 类(Class)和对象(Object)
2. 超类和子类
ps:超类也叫父类
3. 面向对象的内容
3.1 抽象Abstraction
- 在特定环境中模拟真实物体的属性和行为
- 下图中根据不同情景抽象飞机这个类,前者注重飞机飞行属性,后者注重飞机座位的问题
3.2 封装Encapsulation
- 向他人隐藏对象部分状态和行为,并提供一套有限的接口(Interface)
- 区分接口与抽象类
- 接口(Interface):
- 接口是一种完全抽象的类,表示一组行为的规范(即方法的声明),类通过实现接口来“承诺”提供这些行为的具体实现。
- 接口通常用于表示类之间的“能力”,即类能做什么,而不关心它们如何做。
- 只能有常量(
public static final
),不能有实例变量。接口中的成员变量默认是public static final
,即常量。 - 一个类可以实现多个接口(Java 支持多重接口实现)。接口用于定义一组行为的标准,类通过实现接口来获得这些行为。
- 接口是通过
implements
关键字来实现的。 - 抽象类(Abstract Class):
- 抽象类是一个不能实例化的类,它可以包含已实现的方法、未实现的方法(抽象方法)和字段。抽象类可以提供部分实现,并留给子类去实现剩余部分。
- 抽象类通常用于表示一个“类”的通用概念,类的公共行为可以放在抽象类中,子类可以继承并扩展这些行为。
- 可以包含实例变量,且可以有任何访问修饰符,可以是普通字段(
private
、protected
或public
),不需要是常量。 - 一个类只能继承一个抽象类,因为 Java 不支持多重继承(避免了“钻石问题”)。抽象类用于提供通用的行为,而子类可以继承这些行为并进行扩展。
- 抽象类是通过
extends
关键字来继承的。 - 总结:
特点 | 接口 (Interface) | 抽象类 (Abstract Class) |
方法实现 | 只能有抽象方法(Java 8 之后可有默认方法) | 可以有抽象方法和具体方法 |
成员变量 | 只能有常量(public static final) | 可以有实例变量 |
构造方法 | 不能有构造方法 | 可以有构造方法 |
继承与实现 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 |
多重继承 | 支持多重接口继承 | 不支持多重继承 |
访问修饰符 | 默认是 public | 可以是 public 、protected 或 private |
适用场景 | 定义不相关类的共同功能 | 定义相关类的通用特性并提供部分实现 |
- 分为Public(+)、Private(-)、Protected(#)三种,后续会给出对比
- 下图中,对于Airport类中对于
origin
、destination
、passengers
提供一个fly
接口,不同飞机类型提供不同的实现方法
3.3 继承Inheritance
- 主要好处:代码重用
3.4 多态Polymorphism
- 采用多种方式执行一个动作
- 检测对象真是类别并调用其实现的机制
4. 类图中常用符号
- 详细解释:类中常见符号
在面向对象设计中,依赖、关联、聚合、组合、实现和继承是常见的关系类型。每种关系具有不同的含义和适用场景。以下是这些关系的判断标准以及实际例子。
4.1 依赖(Dependency)
依赖是指一个类使用另一个类的方法或属性。通常发生在方法参数传递或临时对象的创建上。依赖关系一般是弱的,表示一个类依赖另一个类的功能,但不长期拥有它。
判断标准:
- 一个类在方法中使用了另一个类的对象,通常是作为参数或局部变量。
- 类之间的关系是临时的,表示一种“使用”关系。
示例:
在这个例子中,
Car
类依赖 Engine
类的功能,但这种关系是临时的,Car
只是在执行某个方法时使用 Engine
,并不持有 Engine
对象。4.2 关联(Association)
关联是类之间的较弱关系,表示对象之间的联系。关联关系一般是双向的,一个类持有另一个类的引用。关联可以是单向或双向的。
判断标准:
- 类之间通过成员变量持有对方的引用。
- 关联表示类之间的长期关系,通常存在某种“拥有”或“连接”关系。
示例:
在这个例子中,
School
类持有 Student
类的引用,表示学校和学生之间的关联关系。4.3 聚合(Aggregation)
聚合是一种特殊的关联关系,表示“整体-部分”的关系,且部分对象可以独立于整体存在。聚合表示的是一种拥有关系,但子对象可以在整体对象生命周期之外存在。
判断标准:
- 一方是整体,另一方是部分。
- 部分对象可以独立于整体存在。
示例:
在这个例子中,
Department
类聚合了 Employee
类。Employee
可以独立于 Department
存在,即员工可以没有隶属于某个部门。4.4 组合(Composition)
组合是一种强关联的聚合关系,表示“整体-部分”的关系,且部分对象的生命周期依赖于整体。组合表示的是强拥有关系,部分对象不能独立于整体对象存在。
判断标准:
- 一方是整体,另一方是部分,且部分对象的生命周期由整体管理。
- 部分对象不能在整体对象销毁后存活。
示例:
在这个例子中,
House
类包含 Room
,而且 Room
不能独立于 House
存在。如果房子被销毁,房间也随之消失,这就是组合关系。4.5 实现(Implementation)
实现是指一个类实现一个接口,表示类与接口之间的关系。实现关系表示类的行为约定,即类承诺提供接口中定义的功能。
判断标准:
- 一个类实现一个接口,表示它提供了接口声明的方法的实现。
- 接口定义了行为,类提供了具体的实现。
示例:
在这个例子中,
Dog
类实现了 Animal
接口,表示 Dog
类提供了接口 Animal
中 speak
方法的具体实现。4.6 继承(Inheritance)
继承表示类与类之间的关系,表示一个类是另一个类的特殊化。子类继承父类的属性和方法,并可以扩展或重写父类的行为。
判断标准:
- 子类继承父类,表示子类是父类的一种特殊化。
- 子类获得父类的属性和行为。
示例:
在这个例子中,
Dog
类继承了 Animal
类,表示 Dog
是 Animal
的一种特殊化。Dog
继承了 Animal
的 eat
方法,并增加了 bark
方法。4.7 总结
关系 | 描述 | 例子 |
依赖 | 一个类临时使用另一个类的功能 | Car 类依赖 Engine 类启动引擎 |
关联 | 类之间有长期的联系,通常通过引用 | School 类与 Student 类关联 |
聚合 | 整体和部分关系,部分可独立存在 | Department 类聚合 Employee 类 |
组合 | 强依赖关系,部分不能独立存在 | House 类组合 Room 类 |
实现 | 类实现接口,提供具体功能 | Dog 类实现 Animal 接口 |
继承 | 子类继承父类的属性和行为 | Dog 类继承 Animal 类 |
5. Java中的OOP
- 类声明
- 创建对象
6. Java的OOP访问控制
7. Java的OOP
7.1 继承
- 类可以派生自其他类,继承字段与方法,原则遵循上述表格
- 超类与子类
- 超类(基类/父类)
- 子类(派生类/扩展类/子类)
- 每个类都有且仅有一个直接超类(单一继承,不支持多重继承)
- 没有超类的Object除外
- 子类继承其超类的所有成员(字段、方法、嵌套类)
- 嵌套类
嵌套类是指定义在另一个类内部的类。在 Java 中,嵌套类是一种将逻辑上关联的类组织在一起的方式,从而更容易维护和理解代码。
7.2 子类中的功能
按原样使用、替换、隐藏或补充继承的成员
- 继承成员的使用方式
- 使用继承的成员: 可以直接使用父类已经定义的成员(字段、方法等)。
- 替换继承的成员: 可以在子类中重写父类的方法或字段,替换掉父类的定义。
- 隐藏继承的成员: 子类中可以定义一个与父类同名的字段或方法,从而隐藏父类的成员。
- 补充继承的成员: 子类可以增加新的字段或方法,不会影响父类的成员
PS:子类继承的父类的private成员是不可以直接访问的,但是存在于子类中
- 在子类中声明与父类相同名称的字段(隐藏字段)
- 不推荐这样做,因为这会隐藏父类中的成员,可能导致代码难以理解和维护。
示例:
- 在子类中重写父类的实例方法(覆盖方法)
- 子类可以重写父类的方法,提供不同的实现,这叫做方法的“覆盖”。
示例:
- 在子类中声明与父类相同名称的静态方法(隐藏静态方法)
- 如果在子类中声明与父类相同签名的静态方法,则会隐藏父类的静态方法,而不是重写它。静态方法不能像实例方法一样被“覆盖”。
示例:
- 在子类中编写构造方法并调用父类构造方法
- 子类构造方法可以显式地调用父类的构造方法来初始化父类的成员变量。子类构造方法通过
super()
关键字调用父类构造方法。如果父类没有无参构造函数,则子类必须调用父类的带参构造函数。
示例:
7.3 抽象方法和最终方法/类
- 抽象类是一个被声明为抽象的类:可以包含也可以不包含抽象方法
- 抽象方法是一种没有实现的方法
- 最终方法和类:构造函数调用的方法一般应声明为最终方法
7.4 接口
1. 接口是契约(Interfaces are Contracts)
- 接口定义了一组规则或行为标准,任何实现这个接口的类都必须遵守这些规则。
- 它确保实现类提供接口中定义的所有方法,从而实现类与接口之间形成了一种“契约”关系。
类比: 接口就像一个协议或合同,它只规定“应该做什么”(方法的定义),但不关心“如何做”(方法的实现)。
2. 接口是引用类型
接口是 Java 的一种引用类型,用于定义类可以实现的行为。它有以下特点:
- 包含的内容:
- 常量:接口中定义的变量默认是
public static final
,即常量。 - 方法签名:只定义方法的名称、参数和返回值类型,而没有方法体。
- 默认方法:从 Java 8 开始,接口可以包含
default
方法,即带有具体实现的方法。 - 静态方法:接口可以包含
static
方法,静态方法只能通过接口本身调用。 - 嵌套类型:接口可以包含嵌套类、接口或枚举。
示例:
3. 接口不能被实例化
接口不能直接创建实例,因为它没有任何实现(方法体)。它只能通过实现接口的类来实例化对象。
示例:
4. 接口可以被类实现或被其他接口扩展
- 实现(Implemented by classes): 一个类可以使用
implements
关键字实现接口,并提供接口中定义方法的具体实现。
- 扩展(Extended by other interfaces): 一个接口可以使用
extends
关键字继承另一个接口,从而形成接口的继承层次。
示例:
5. 接口的语法组成
接口的语法包括以下部分:
- 修饰符:
public
或abstract
。
- 关键字:
interface
。
- 接口名称: 符合 Java 的命名规则。
- 父接口列表(可选): 用逗号分隔的父接口列表。
- 接口体: 包括抽象方法、默认方法、静态方法、常量和嵌套类型。
语法结构:
6. 接口主体可以包含的内容
(1)抽象方法
- 定义接口的核心行为,没有实现,由实现类提供具体实现。
- 默认是
public abstract
,即使省略关键字也是如此。
(2)默认方法
- 从 Java 8 开始,接口可以包含具体实现的方法,称为默认方法(
default
)。
- 用于在接口中添加新方法,而不破坏现有实现类的兼容性。
示例:
(3)静态方法
- 静态方法只能通过接口名称调用,不能通过实现类或实例调用。
- 用于定义与接口相关的工具方法。
示例:
(4)常量
- 接口中的变量默认是
public static final
。
- 必须在定义时初始化,不能修改。
(5)嵌套类型
- 接口可以包含嵌套的类、接口或枚举,通常用于将逻辑相关的类型组织在一起。
7. 接口的设计优势
- 解耦: 接口为类之间提供了一种松耦合的方式。
- 多态性: 通过接口实现类的多态行为。
- 多实现: 一个类可以实现多个接口,弥补了 Java 不支持多继承的限制。
- 灵活性: 接口允许在不修改类的情况下扩展功能(通过添加默认方法)。
总结
特性 | 描述 |
定义 | 接口定义一组行为规范(契约)。 |
包含内容 | 常量、方法签名、默认方法、静态方法和嵌套类型。 |
实例化 | 接口不能直接实例化,只能通过实现类来实例化。 |
继承关系 | 接口可以被类实现( implements )或被其他接口扩展(extends )。 |
方法类型 | 包含抽象方法、默认方法和静态方法。 |
设计意义 | 提供松耦合、多态性和灵活的代码结构。 |
7.5 实现和使用接口
- 在类声明中包含
implements
子句
在 Java 中,类通过
implements
关键字来声明它实现了一个或多个接口。每个接口都定义了一组方法,类必须提供这些方法的具体实现。如果类实现了多个接口,它可以在 implements
子句中列出多个接口,接口之间用逗号分隔。语法:
示例:
说明:
Dog
类实现了两个接口Animal
和Pet
,并且为它们定义了必要的eat
和play
方法。
- 一个类可以实现多个接口,这是 Java 提供的多重继承机制之一。
- 接口类型的引用变量
如果你定义了一个接口类型的引用变量,那么它只能引用实现了该接口的类的对象。也就是说,接口类型的变量可以指向任何实现了该接口的类的实例。
- 接口类型的引用 可以指向任何实现了该接口的类的实例。
- 如果你尝试将一个没有实现该接口的类的对象赋给接口类型的变量,编译器会报错。
示例:
说明:
myAnimal
是Animal
类型的引用变量,它可以指向任何实现了Animal
接口的对象。这里,我们先将Dog
对象赋值给myAnimal
,然后再将Cat
对象赋值给它。
- 通过接口类型的引用变量,我们可以实现 多态,即不同的实现类通过相同的接口引用来调用不同的实现方法。
总结
- 实现多个接口: 通过
implements
子句,类可以实现多个接口。如果类实现了多个接口,必须实现这些接口中所有的抽象方法。
- 接口引用变量: 如果一个引用变量的类型是接口,那么它只能指向实现该接口的类的实例。通过接口类型的引用变量,可以实现多态,使得代码更加灵活和可扩展。
7.6 接口与抽象类
相似点与区别
- 考虑使用抽象类(Abstract Classes)时:
- 当你希望在多个密切相关的类之间共享代码时 抽象类适合在具有相似功能的类之间共享实现代码。例如,一些方法可以在抽象类中实现,而子类则可以继承并复用这些实现。
- 当你预计扩展抽象类的类有许多公共方法或字段,或需要使用公共之外的访问修饰符时 如果你有多个类需要共享一些公共字段或方法,且这些字段或方法的访问级别需要控制(如
protected
、private
等),则抽象类可以提供比接口更好的封装能力。 - 当你希望声明非静态或非最终字段时 抽象类允许声明实例字段,这些字段可以是可变的,且不需要是
final
(即可以修改)。这在需要存储对象状态时非常有用。
- 考虑使用接口(Interfaces)时:
- 当你预计不相关的类将会实现你的接口时 接口可以跨多个类提供统一的行为规范,适用于那些没有直接继承关系的类。例如,接口适用于不同类别的类(如
Car
类和Robot
类)都实现某种共同行为。 - 当你希望指定某个数据类型的行为,但不关心具体谁来实现这些行为时 接口专注于定义类应该实现什么行为,而不关心实现这些行为的具体类。例如,接口可以定义一个数据类型应该支持的操作(如
Serializable
接口定义了对象序列化的行为),但无需关心哪一个类来实现这些行为。 - 当你希望利用多重继承时 Java 不支持类的多继承,但接口允许一个类实现多个接口,从而实现多重继承的效果。这使得类能够同时继承多个不同接口的行为规范。
总结:
- 抽象类: 适用于类之间有共同代码和字段,且需要访问修饰符控制的场景,允许实例字段和方法的实现。
- 接口: 适用于不相关类之间共享行为的场景,特别是当你希望指定行为而不关心具体实现时,或当需要多重继承时。
二、软件设计原则
1. 优秀设计的特点
1.1 代码重复使用
- 挑战:组件之间紧耦合,依赖于具体类而非接口,操作硬编码
- 解决方案:设计模式
- 埃里希·伽马提出的三个重用层次:
- 最低层次:类
- 最高层次:框架
- 中间层次:设计模式
1.2 可扩展性
- 变化是程序员生活中唯一不变的事情
2. 良好设计原则
2.1 封装不同内容
- 识别应用中会变化的部分,并将其与保持不变的部分分离
- 主要目标:最小化变化带来的影响
- 将变化的程序部分隔离到独立模块中,保护其他部分
- 在方法层次上的封装(Encapsulation on a method level)
- 在类层次上的封装(Encapsulation on a class level)
2.2 面向接口编程,而不是面向实现
- 换句话说,依赖于抽象,而不是具体类
- 如果可以在不破坏现有代码的情况下轻松扩展设计,那么这个设计就足够灵活
- 一种可能的方法:
- 确定一个对象需要另一个对象的什么:它执行哪些方法?
- 将这些方法描述为一个新的接口(interface)或抽象类(abstract class)。
- 使作为依赖的类实现这个接口。
- 使第二个类依赖于这个接口,而不是依赖于具体类。
2.3 优先使用组合(Composition)而不是继承(Inheritance)
- 继承的挑战:
- 子类无法缩小父类的接口
- 重写方法时,需要确保新行为与基础行为兼容
- 继承破坏了父类的封装性
- 子类与父类之间紧密耦合
- 通过继承复用代码可能会导致创建平行的继承层次结构
3. SOLID原则
3.1 SOLID 1: 单一责任原则(Single Responsibility Principle)
- 一个类应该只有一个改变的理由
- 尽量让每个类只负责单一功能的一部分,并将该责任完全封装起来
- 主要目标:减少复杂性,降低风险
3.2 SOLID 2: 开放/封闭原则(Open/Closed Principle)
- 类应该对扩展开放,对修改封闭
- 在实现新特性时,确保现有代码不被破坏
- 一个类是开放的,如果它可以通过子类进行扩展
- 一个类是封闭的,如果它已经完全准备好供其他人使用
- 一个类可以同时既是开放的(用于扩展),又是封闭的(用于修改)
- 并非所有变化都需要应用该原则
3.3 SOLID 3: 里氏替换原则(Liskov Substitution Principle)
- 在扩展一个类时,确保可以将子类的对象替代父类的对象使用,而不会破坏客户端代码
- 子类应该与父类的行为保持兼容
- 重写方法时,应该扩展基础行为,而不是完全替换成其他行为
- 在开发库和框架时尤为关键
- 一组检查:
- (a) 子类方法中的参数类型应该与父类方法中的参数类型匹配,或者比父类参数类型更加抽象。
- 示例:Base: feed(Cat c) =>
- Good: feed(Animal c)
- Bad: feed(BengalCat c)
- (b) 子类方法中的返回类型应该与父类方法中的返回类型匹配,或者是父类返回类型的子类型。
- 示例:Base: adoptCat(): Cat =>
- Good: adoptCat(): BengalCat
- Bad: adoptCat(): Animal
- (c) 异常类型应该匹配或是父类方法能够抛出的异常类型的子类型。
- 大多数现代编程语言都内建了这一规则
- (d) 子类不应增强父类方法的前置条件。
- 示例:一个方法有一个 int 类型的参数
- (e) 子类不应削弱父类方法的后置条件。
- 示例:一个方法与数据库交互
- (f) 父类的不变式必须得到保留(所有规则中最不正式的一个)
- 不变式:对象在什么条件下是合理的
- 扩展类的最安全方式:引入新的字段和方法
- (g) 子类不应更改父类私有字段的值
3.4 SOLID 4: 接口隔离原则(Interface Segregation Principle)
- 客户端不应该被迫依赖它们不使用的方法
- 使接口足够窄,以便客户端类不需要实现它们不需要的行为
- 将“臃肿”的接口拆分成更精细和具体的接口
3.5 SOLID 5: 依赖倒置原则(Dependency Inversion Principle)
- 高层类不应该依赖于低层类,二者都应该依赖于抽象
- 低层类与高层类
- 问题:业务逻辑类往往会依赖于原始的低层类
- 建议:改变依赖的方向
- 方法:
- 为高层类所依赖的低层操作描述接口,最好以业务术语表达
- 使高层类依赖于这些接口,从而实现较软的依赖
- 低层类实现这些接口,依赖于业务逻辑层
三、创建型模式
1. Factory Method Pattern
- Intent
- Define an interface for creating objects, allowing subclasses to alter the type of objects created.
- Avoid tight coupling between the client code and the concrete products.
- Implementation Steps
- Ensure all products follow a common interface.
- Add a factory method in the base class with a return type matching the product interface.
- Replace direct object instantiation (
new
) with calls to the factory method. - Override the factory method in subclasses to specify different product types.
- If needed, use parameters to control product creation within the factory method.
- Application Scenarios
- Situations where the exact type of object to create is not known until runtime.
- Frameworks or libraries that need extensible components.
- Resource-intensive systems that reuse objects.
- Key Roles
- Creator: Contains the factory method.
- Concrete Creator: Subclasses implementing the factory method.
- Product: Common interface for all objects created.
- Concrete Product: Specific implementations of the product.
- Pros and Cons
- Pros:
- Supports the Open/Closed Principle (extensible without modification).
- Reduces coupling between creator and concrete products.
- Complies with Single Responsibility Principle.
- Cons:
- Increased complexity due to numerous subclasses.
2. Abstract Factory Pattern
- Intent
- Provide an interface to create families of related or dependent objects without specifying their concrete classes.
- Ensure the products created are compatible with each other.
- Implementation Steps
- Map out product types and their variants.
- Declare interfaces for product types and implement these in concrete classes.
- Define an abstract factory interface with creation methods for each product type.
- Implement concrete factories for each product variant.
- Initialize the appropriate factory and use it for object creation.
- Application Scenarios
- Applications that need to work with multiple families of related products (e.g., GUI toolkits with OS-specific elements).
- Code that requires consistent product combinations.
- Future extensibility for new product families or variants.
- Key Roles
- Abstract Factory: Declares creation methods for all product types.
- Concrete Factory: Implements creation methods for specific product variants.
- Abstract Product: Common interface for a family of products.
- Concrete Product: Specific implementations for different variants.
- Pros and Cons
- Pros:
- Ensures compatibility within product families.
- Complies with Single Responsibility Principle and Open/Closed Principle.
- Reduces coupling between products and client code.
- Cons:
- Increased complexity due to additional interfaces and classes.
3. Builder Pattern
- Intent
- Separate the construction of a complex object from its representation, allowing the same construction process to create different representations.
- Avoid large constructors ("telescoping constructors") by building objects step by step.
- Implementation Steps
- Define construction steps in a base builder interface.
- Create concrete builder classes for different product representations, implementing the interface.
- Optionally, create a director class to control the construction process.
- The client creates both the builder and, if used, the director.
- Fetch the constructed product from the builder or the director, depending on the design.
- Application Scenarios
- Construct complex objects with many parts and nested objects.
- Create different representations of an object with similar construction processes.
- Build composite trees or other hierarchical structures.
- Key Roles
- Builder Interface: Declares construction steps.
- Concrete Builder: Implements steps for specific products.
- Director (optional): Controls the order of building steps.
- Client: Initiates and orchestrates the building process.
- Pros and Cons
- Pros:
- Supports incremental and flexible object construction.
- Encourages code reuse for similar construction processes.
- Complies with the Single Responsibility Principle.
- Cons:
- Increases complexity due to additional interfaces and classes.
4. Prototype Pattern
- Intent
- Create new objects by copying existing ones, without being tied to their concrete classes.
- Delegate the cloning process to the actual objects being cloned.
- Implementation Steps
- Define a prototype interface with a
clone()
method. - Implement
clone()
in concrete classes, potentially using a copy constructor. - Create a prototype registry (optional) to manage frequently used prototypes.
- Application Scenarios
- Avoid dependencies on the concrete classes of objects to be copied.
- Reduce subclass proliferation by cloning prototypes instead of creating new subclasses.
- Simplify the creation of complex objects by cloning pre-configured instances.
- Key Roles
- Prototype Interface: Declares the cloning method.
- Concrete Prototype: Implements the cloning logic.
- Prototype Registry (optional): Manages reusable prototype instances.
- Pros and Cons
- Pros:
- Decouples cloning from the object's concrete class.
- Reduces repetitive initialization code by reusing prototypes.
- Offers an alternative to inheritance for configuration presets.
- Cons:
- Complex to clone objects with circular references.
5. Singleton Pattern
- Intent
- Ensure a class has only one instance and provide a global access point to it.
- Control access to shared resources or ensure consistent state management.
- Implementation Steps
- Declare a private static field to store the singleton instance.
- Create a public static method for accessing the instance.
- Use lazy initialization to create the instance only when needed.
- Mark the constructor as private to prevent external instantiation.
- Replace all direct constructor calls in client code with calls to the static method.
- Application Scenarios
- When exactly one instance of a class is required, such as managing a configuration file, logging system, or thread pool.
- When you need controlled access to shared resources without using global variables.
- Key Roles
- Singleton Class: Ensures a single instance and provides a global access method.
- Static Method: Responsible for returning or creating the singleton instance.
- Pros and Cons
- Pros:
- Guarantees only one instance exists.
- Provides a global access point to the instance.
- Supports lazy initialization, saving resources if the instance is not immediately needed.
- Cons:
- Violates the Single Responsibility Principle by handling both instance creation and its business logic.
- Can lead to tight coupling and hidden dependencies between components.
- Requires additional care in multithreaded environments.
- Hard to test due to reliance on a single instance.
- Thread-Safety in Singleton
- Problem: If multiple threads access the
getInstance()
method simultaneously, they may create multiple instances. - Solutions:
- Use synchronized blocks or methods to prevent race conditions.
- Apply double-checked locking for better performance.
- Use a static inner class to defer initialization until needed, ensuring thread safety without explicit synchronization.
- Use an enum singleton, which is inherently thread-safe and serialization-safe.
四、结构型模式
1. Adapter Pattern
- Intent
- Enable incompatible interfaces to work together by providing a middle-layer class as a translator.
- Allow legacy or third-party classes to integrate seamlessly with new systems.
- Implementation Steps
- Identify classes with incompatible interfaces (e.g., legacy or third-party services).
- Declare a client interface to describe how the client communicates with the service.
- Create an adapter class implementing the client interface.
- Use the adapter class to delegate calls to the service object, converting data or methods as needed.
- Ensure the client interacts with the adapter only via the client interface.
- Application Scenarios
- When using legacy or third-party classes whose interfaces are incompatible with your code.
- When extending existing classes is not feasible or ideal.
- When adding new functionality to multiple subclasses without modifying their parent class.
- Key Roles
- Client: The code that needs to work with the service.
- Target Interface: The interface expected by the client.
- Adapter: Converts the target interface to the service interface.
- Adaptee (Service): The existing class being adapted.
- Pros and Cons
- Pros:
- Complies with Single Responsibility Principle by separating data conversion logic.
- Supports the Open/Closed Principle by allowing new adapters without modifying existing code.
- Cons:
- Adds complexity by introducing additional classes.
2. Bridge Pattern
- Intent
- Decouple abstraction from its implementation, allowing both to vary independently.
- Avoid explosion of subclasses when extending a class in multiple dimensions.
- Implementation Steps
- Identify orthogonal dimensions in your class hierarchy (e.g., abstraction and implementation).
- Define a base abstraction class with methods that rely on operations defined in an implementation interface.
- Create concrete implementations adhering to the implementation interface.
- Add a reference field in the abstraction class to link it with an implementation object.
- Extend the abstraction and implementation hierarchies independently as needed.
- Application Scenarios
- When a class needs to evolve along two or more dimensions (e.g., shape types and colors).
- When you want to separate high-level logic from platform-specific details.
- When you need to switch implementations at runtime.
- Key Roles
- Abstraction: High-level control logic, delegating low-level tasks.
- Refined Abstraction: Specialized versions of the abstraction.
- Implementation: Low-level platform-specific details.
- Concrete Implementation: Specific implementations of the interface.
- Pros and Cons
- Pros:
- Supports Open/Closed Principle, allowing extensions independently.
- Complies with Single Responsibility Principle, separating high-level logic from implementation details.
- Facilitates platform-independent classes and runtime implementation swapping.
- Cons:
- Increases code complexity when applied to highly cohesive classes.
3. Composite Pattern
- Intent
- Compose objects into tree structures to represent part-whole hierarchies.
- Allow clients to treat individual objects and compositions of objects uniformly.
- Implementation Steps
- Represent your application’s core model as a tree structure with simple elements (leaves) and containers (composites).
- Declare a common interface for all components, defining methods used both by simple and composite objects.
- Create classes for simple elements and implement the interface.
- Create a container class to represent composites, storing references to child elements (both leaves and composites).
- Allow adding/removing child elements in the container class, without violating the Interface Segregation Principle.
- Application Scenarios
- When dealing with tree-like data structures, such as file systems or graphical editors.
- When the client code should treat simple and composite objects in the same way.
- Key Roles
- Component: The interface for all objects in the composition.
- Leaf: A simple element that does not have children.
- Composite: A container that stores children and implements methods to operate recursively on them.
- Pros and Cons
- Pros:
- Simplifies working with complex tree structures.
- Complies with the Open/Closed Principle for extending new elements.
- Cons:
- Can lead to overgeneralization of the component interface.
4. Decorator Pattern
- Intent
- Attach additional responsibilities to objects dynamically, without modifying their structure.
- An alternative to subclassing for extending functionality.
- Implementation Steps
- Identify the primary component to decorate and its behaviors.
- Create a component interface shared by the base object and decorators.
- Implement the base component class and define core behaviors.
- Create a base decorator class with a reference to the component, delegating method calls.
- Extend the decorator class for additional responsibilities, modifying behavior before/after delegation.
- Compose decorators dynamically in the client code.
- Application Scenarios
- When you need to add/remove responsibilities to objects at runtime.
- When subclassing is impractical (e.g., too many subclasses or the base class is
final
).
- Key Roles
- Component: The interface defining methods for the primary object and decorators.
- Concrete Component: The primary object being decorated.
- Decorator: A base class wrapping the component, allowing extended functionality.
- Concrete Decorators: Implement specific additional behaviors.
- Pros and Cons
- Pros:
- Adds/removes behaviors at runtime.
- Avoids subclass explosion.
- Complies with Single Responsibility Principle.
- Cons:
- Complex to manage a stack of decorators.
- Behavior depends on decorator order.
5. Facade Pattern
- Intent
- Provide a simplified interface to a complex subsystem.
- Hide subsystem complexity, offering only the necessary functionality.
- Implementation Steps
- Identify parts of the subsystem that can be abstracted into a simpler interface.
- Define a facade class that encapsulates subsystem complexities and manages object lifecycle.
- Redirect client calls from the facade to the appropriate subsystem components.
- Keep the client code dependent only on the facade.
- If the facade grows too large, divide it into smaller facades for different subsystem parts.
- Application Scenarios
- When managing large, complex subsystems with many interdependent parts.
- To isolate subsystem changes and reduce coupling.
- To provide a clearer structure for multi-layered applications.
- Key Roles
- Facade: Simplifies access to a subsystem and delegates requests to its components.
- Subsystem: Contains the complex logic hidden by the facade.
- Pros and Cons
- Pros:
- Reduces subsystem complexity for the client.
- Decouples client code from subsystem details.
- Cons:
- Risks becoming a "god object" tightly coupled to all subsystem components.
6. Flyweight Pattern
- Intent
- Minimize memory usage by sharing as much data as possible among similar objects.
- Separate intrinsic state (shared data) from extrinsic state (unique, contextual data).
- Implementation Steps
- Identify the fields that can be categorized as intrinsic (shared) and extrinsic (unique).
- Keep intrinsic fields immutable and within the flyweight class.
- Replace extrinsic fields with parameters in the flyweight methods.
- Optionally, create a factory class to manage and reuse flyweight instances.
- Store extrinsic data separately, possibly in a context class.
- Application Scenarios
- When handling large numbers of objects that share many similarities.
- Useful in scenarios such as GUI element rendering, text character storage, or game particle systems.
- Key Roles
- Flyweight: Contains intrinsic state and provides methods requiring extrinsic state as arguments.
- Flyweight Factory: Manages shared flyweight instances.
- Client: Maintains or calculates extrinsic state.
- Pros and Cons
- Pros:
- Significant memory savings for large-scale object creation.
- Cons:
- Increased code complexity.
- May trade memory savings for higher CPU usage when recalculating extrinsic state.
7. Proxy Pattern
- Intent
- Provide a substitute or placeholder for another object to control access to it.
- Allow additional functionality (e.g., logging, caching) to be performed before/after accessing the object.
- Implementation Steps
- Define a service interface if one doesn’t already exist.
- Create a proxy class that implements the service interface and holds a reference to the real service object.
- Implement proxy methods to delegate calls to the service object, adding any pre/post logic.
- Optionally, implement lazy initialization for the service object within the proxy.
- Use a factory or builder to decide whether the client gets a proxy or the real service object.
- Application Scenarios
- Lazy Initialization (Virtual Proxy): Defer initialization of heavy objects.
- Access Control (Protection Proxy): Restrict access based on user roles.
- Remote Proxy: Represent remote objects in local systems.
- Logging Proxy: Keep a log of requests.
- Caching Proxy: Cache results to improve performance.
- Smart Reference Proxy: Automatically manage the lifecycle of a service object.
- Key Roles
- Service Interface: Common interface for the real service and proxy.
- Real Service: The original object the proxy represents.
- Proxy: Manages access to the real service.
- Pros and Cons
- Pros:
- Controls service object lifecycle and access transparently.
- Adheres to Open/Closed Principle by adding proxies without modifying the original service.
- Cons:
- Adds complexity to the codebase.
- Can introduce latency when using the proxy.
五、行为型模式
1. Chain of Responsibility Pattern
- Intent
- Pass requests along a chain of handlers where each handler decides whether to process the request or pass it to the next handler.
- Decouple the sender of a request from its receiver(s).
- Implementation Steps
- Define a handler interface with a method to handle requests.
- Optionally, create an abstract base handler class with default behaviors, such as forwarding requests to the next handler.
- Implement concrete handlers for specific request types.
- Link handlers into a chain (can be static or dynamic).
- Trigger the request from any handler in the chain.
- Application Scenarios
- When multiple handlers can process a request and the handler sequence may change dynamically.
- When you need to execute several handlers in a specific order.
- Examples include validation chains, logging pipelines, and event bubbling in GUIs.
- Key Roles
- Handler Interface: Declares the method for processing requests.
- Concrete Handlers: Implement request processing and chain forwarding logic.
- Client: Configures and initiates the chain of handlers.
- Pros and Cons
- Pros:
- Complies with Single Responsibility Principle by separating request processing from request forwarding.
- Supports Open/Closed Principle for adding new handlers.
- Flexible runtime configuration.
- Cons:
- Risk of unhandled requests if no handler processes them.
2. Command Pattern
- Intent
- Encapsulate requests as objects, allowing parameterization, queuing, logging, and undo/redo functionality.
- Decouple the object invoking the operation from the object performing it.
- Implementation Steps
- Define a command interface with a method for executing operations.
- Implement concrete command classes for specific operations, associating them with receivers.
- Modify sender classes to use commands instead of invoking operations directly on receivers.
- Configure command objects with required arguments and associate them with senders and receivers.
- Optionally, implement undo/redo functionality by storing operation history and states.
- Application Scenarios
- When operations need to be parameterized or queued (e.g., task scheduling).
- For implementing undo/redo functionality in applications.
- When logging or tracking the history of operations is required.
- Key Roles
- Command Interface: Declares the method for executing operations.
- Concrete Commands: Encapsulate specific operations and their arguments.
- Invoker (Sender): Initiates command execution.
- Receiver: Performs the actual operations.
- Client: Configures commands, receivers, and invokers.
- Pros and Cons
- Pros:
- Decouples senders from receivers, adhering to the Single Responsibility Principle.
- Supports Open/Closed Principle by enabling new commands without modifying existing code.
- Enables undo/redo and deferred execution.
- Cons:
- Increased code complexity due to the additional command layer.
3. Iterator Pattern
- Intent
- Provide a way to access elements of a collection sequentially without exposing its underlying representation.
- Decouple traversal logic from the collection's implementation.
- Implementation Steps
- Define an iterator interface with methods for fetching elements (e.g.,
next
,hasNext
). - Create concrete iterator classes for specific collections, linking the iterator to the collection instance.
- Implement a collection interface with a method for returning iterators.
- Implement concrete collections that return iterator instances.
- Use the iterator in the client to traverse the collection.
- Application Scenarios
- When a collection has a complex structure that should be hidden from clients.
- To support multiple traversal algorithms or simultaneous traversals of the same collection.
- When the collection's data structure may change without affecting traversal logic.
- Key Roles
- Iterator Interface: Declares methods for traversal.
- Concrete Iterator: Implements traversal logic for specific collections.
- Collection Interface: Declares methods for obtaining iterators.
- Concrete Collection: Implements methods to create iterator instances.
- Pros and Cons
- Pros:
- Separates traversal logic from collection classes (Single Responsibility Principle).
- Supports new collection and iterator types without modifying existing code (Open/Closed Principle).
- Enables parallel and delayed iteration.
- Cons:
- Can be overkill for simple collections.
- May reduce efficiency compared to direct access.
4. Mediator Pattern
- Intent
- Reduce direct dependencies between communicating components by introducing a mediator.
- Encapsulate how components interact, promoting loose coupling.
- Implementation Steps
- Identify tightly coupled classes that would benefit from decoupling.
- Define a mediator interface for communication protocols.
- Implement a concrete mediator class, referencing all components.
- Modify components to communicate via the mediator rather than directly.
- Optionally, allow the mediator to manage the lifecycle of components.
- Application Scenarios
- When multiple components have direct and complex interactions that make the system hard to modify.
- To facilitate component reuse by isolating dependencies.
- To redefine component interactions by introducing new mediators.
- Key Roles
- Mediator Interface: Defines the communication protocol.
- Concrete Mediator: Implements coordination logic and keeps references to components.
- Components: Store references to the mediator and notify it of events.
- Pros and Cons
- Pros:
- Reduces coupling between components (Single Responsibility Principle).
- Simplifies component reuse.
- Easy to introduce new mediators to modify component interactions (Open/Closed Principle).
- Cons:
- May grow into a "God Object" managing too much logic.
5. Memento Pattern
- Intent
- Capture and externalize an object’s internal state without violating encapsulation, allowing the object to be restored to this state later.
- Commonly used for undo/redo functionality.
- Implementation Steps
- Identify the class as the Originator responsible for creating and restoring state snapshots.
- Create a Memento class with fields mirroring the originator’s state; ensure it is immutable.
- Add methods to the originator for creating and restoring mementos.
- Optionally nest the memento class inside the originator for stricter encapsulation or use an intermediate interface to restrict access.
- Implement a Caretaker class to manage memento storage and history.
- Application Scenarios
- When undo/redo operations are needed (e.g., text editors, transactions).
- To store snapshots of an object’s state, including private fields, without violating encapsulation.
- Key Roles
- Originator: Produces and restores state snapshots.
- Memento: Stores the state; typically immutable and hidden from other objects.
- Caretaker: Maintains a history of mementos, responsible for saving/restoring states.
- Pros and Cons
- Pros:
- Maintains encapsulation by avoiding direct access to the object’s state.
- Decouples state history management from the originator.
- Cons:
- High memory usage for storing multiple snapshots.
- Caretakers need to manage memento lifecycle carefully to avoid obsolete states.
6. Observer Pattern
- Intent
- Define a one-to-many dependency where multiple objects (subscribers) are notified of changes to another object (publisher).
- Allows dynamic and runtime registration/deregistration of subscribers.
- Implementation Steps
- Identify the Publisher as the object whose state changes need to be observed.
- Define a Subscriber Interface with a notification method.
- Implement the publisher interface to manage a subscription list (add/remove subscribers).
- Create concrete publisher and subscriber classes.
- Notify all subscribers whenever the publisher’s state changes.
- Application Scenarios
- When multiple objects need to be updated automatically when the state of another object changes.
- Suitable for event-driven systems, GUI frameworks, or real-time updates.
- Key Roles
- Publisher: Maintains a list of subscribers and notifies them of state changes.
- Subscriber: Implements the notification interface and acts on updates.
- Concrete Publisher and Subscriber: Provide specific implementations.
- Pros and Cons
- Pros:
- Promotes Open/Closed Principle, allowing new subscribers without modifying the publisher.
- Enables dynamic runtime relationships between objects.
- Cons:
- Subscribers are notified in no guaranteed order.
- Potentially inefficient if there are numerous subscribers.
7. State Pattern
- Intent
- Allow an object to alter its behavior when its internal state changes.
- Eliminate complex conditional logic for state-dependent behavior.
- Implementation Steps
- Identify the context class containing state-dependent behavior.
- Define a state interface with methods for state-specific behavior.
- Create concrete state classes, implementing the state interface.
- Add a reference to a state object in the context and a setter for switching states.
- Replace conditionals in the context by delegating tasks to the current state object.
- Application Scenarios
- When an object’s behavior depends on its state and requires frequent changes.
- To simplify code polluted with state-dependent conditionals.
- Key Roles
- Context: Maintains the current state and delegates state-dependent tasks.
- State Interface: Declares methods for state-specific behaviors.
- Concrete States: Implement state-specific behavior and transitions.
- Pros and Cons
- Pros:
- Promotes Single Responsibility Principle by isolating state-related code.
- Supports Open/Closed Principle for adding new states.
- Cons:
- Overhead if the state machine has few states or rarely changes.
8. Strategy Pattern
- Intent
- Define a family of algorithms, encapsulate them in separate classes, and make them interchangeable.
- Decouple algorithm selection from its usage.
- Implementation Steps
- Identify algorithms prone to change in a context class.
- Define a strategy interface for interchangeable algorithms.
- Create concrete strategy classes implementing different algorithms.
- Add a reference to a strategy object in the context and provide a setter for switching strategies.
- Clients configure the context with a suitable strategy.
- Application Scenarios
- When you need multiple variations of an algorithm.
- To avoid conditionals by encapsulating algorithm variations.
- Key Roles
- Context: Holds a reference to a strategy and delegates algorithm execution.
- Strategy Interface: Declares methods for algorithms.
- Concrete Strategies: Implement specific algorithms.
- Pros and Cons
- Pros:
- Supports runtime algorithm switching.
- Promotes Open/Closed Principle for new strategies.
- Cons:
- Adds complexity if algorithms rarely change.
- Clients must understand strategy differences.
9. Template Method Pattern
- Intent
- Define the skeleton of an algorithm in a superclass, allowing subclasses to override specific steps without altering the algorithm’s structure.
- Implementation Steps
- Analyze the algorithm and break it into steps.
- Create an abstract class with a template method defining the algorithm structure.
- Declare abstract methods for steps to be overridden by subclasses.
- Provide default implementations for optional steps.
- Implement specific variations in concrete subclasses.
- Application Scenarios
- When several classes have similar algorithms but differ in some steps.
- To enforce a consistent structure across algorithm implementations.
- Key Roles
- Abstract Class: Defines the template method and abstract/optional steps.
- Concrete Classes: Override specific steps without altering the algorithm structure.
- Pros and Cons
- Pros:
- Simplifies maintenance by centralizing common code in the superclass.
- Allows extending specific steps without affecting the algorithm structure.
- Cons:
- May violate Liskov Substitution Principle if subclasses suppress default steps.
- Template methods with many steps can become difficult to maintain.
10. Visitor Pattern
- Intent
- Represent an operation to be performed on elements of an object structure, without modifying the classes of the elements.
- Enable adding new operations without changing the existing object structure.
- Implementation Steps
- Define a Visitor Interface with a method for each type of element in the structure.
- Create Concrete Visitor Classes, implementing the visitor interface, with specific behaviors for each element type.
- Define an Element Interface with an
accept(visitor)
method, allowing visitors to interact with the element. - Implement Concrete Element Classes, which implement the
accept(visitor)
method to delegate calls to the visitor. - In the client, use visitors to perform operations on elements by passing them through the
accept(visitor)
method.
- Application Scenarios
- When operations on an object structure need to be defined and extended independently of the element classes.
- To reduce clutter in element classes by moving auxiliary behaviors to visitors.
- For operations that apply differently to objects in a class hierarchy.
- Key Roles
- Visitor Interface: Declares visiting methods for each element type.
- Concrete Visitor: Implements specific operations for each element.
- Element Interface: Declares an
accept(visitor)
method. - Concrete Element: Implements
accept(visitor)
to delegate the visitor call. - Client: Manages the object structure and applies visitors to its elements.
- Pros and Cons
- Pros:
- Complies with Open/Closed Principle by enabling new behaviors without modifying element classes.
- Promotes Single Responsibility Principle by isolating auxiliary behaviors into visitor classes.
- Visitors can accumulate data or state while traversing elements.
- Cons:
- Adding new element classes requires updating all visitors.
- Accessing private fields or methods in elements may require breaking encapsulation or using workarounds.
- Example Use Cases
- XML Export: Visitors can traverse a data structure and export it to XML format, implementing specific formatting for each type of data.
- Composite Trees: Use visitors to execute operations over a tree structure, processing each node type differently.
- Analytics: Perform aggregated operations, such as statistics or summaries, on elements of an object graph.
Comparison
- Factory Method focuses on creating a single product, with subclasses deciding the product type.
- Abstract Factory manages the creation of families of related products, ensuring compatibility.
Comparison
- Builder focuses on constructing complex objects step by step, allowing variations in the construction process.
- Prototype emphasizes creating new objects by duplicating existing ones, reducing the need for repeated initialization or subclassing.
Comparison
- Adapter focuses on converting an interface to be compatible with another.
- Bridge separates abstraction and implementation to avoid tightly coupling them, enabling independent changes.
Comparison
- Composite handles tree structures and ensures uniformity in treating individual and composite objects.
- Decorator adds responsibilities dynamically without altering the primary object's structure.
- Facade simplifies interaction with a complex subsystem by hiding its internal details.
Comparison
- Flyweight optimizes memory usage by sharing data across similar objects.
- Proxy provides controlled and enhanced access to an object, often managing its lifecycle or adding extra functionality.
Comparison
- Chain of Responsibility: Focuses on request handling where each handler has the option to process or forward the request.
- Command: Encapsulates requests as objects, enabling decoupled, flexible, and reusable operations.
Comparison
- Iterator: Focuses on decoupling the traversal logic from collection structures.
- Mediator: Decouples communication between components by centralizing it in a mediator.
Comparison
- Memento: Focuses on saving and restoring an object’s state, enabling undo/redo functionality.
- Observer: Focuses on notifying multiple objects about changes in a publisher’s state dynamically.
Comparison
- State: Manages dynamic behavior changes based on internal states.
- Strategy: Provides interchangeable algorithms, focusing on encapsulating variations.
- Template Method: Encapsulates an algorithm’s structure in a superclass, focusing on defining steps.
Comparison and Combinations
- Visitor vs. Command: Visitor is a more powerful version of Command when multiple behaviors on multiple objects are needed.
- Visitor + Composite: Efficiently operate on all nodes of a composite tree structure.
- Visitor + Iterator: Use an iterator to traverse the structure and apply the visitor’s operations.
辅助中文理解:
- 工厂方法模式主要用于延迟对象的具体实例化过程,并让子类决定产品的类型。它通过工厂方法避免了直接使用构造函数,提高了代码的扩展性和灵活性。
- 抽象工厂模式则是处理多个相关产品的创建问题,通过提供抽象工厂接口确保创建的产品族内部一致,同时隐藏具体产品类。
- 建造者模式: 适用于创建复杂对象,通过将构建过程分离为多个步骤,避免长构造函数带来的复杂性,并允许不同的表示方式。
- 原型模式: 通过克隆已有对象快速创建新对象,避免依赖具体类,减少重复代码,同时简化复杂对象的生成过程。
- 单例模式的核心目标是确保某个类只有一个实例,并为该实例提供一个全局访问点,常用于管理配置文件、日志系统等场景。单例模式通过将构造函数设置为私有,并结合静态方法实现实例的延迟初始化,但需要注意线程安全问题。单例模式的缺点在于可能导致组件之间的紧耦合、测试困难以及违反单一职责原则。
- 适配器模式: 将一个类的接口转换为客户端期望的接口,用于集成不兼容的旧系统或第三方类。适配器模式的核心在于数据或方法的转换和封装。
- 桥接模式: 将抽象与实现分离,使两者可以独立变化。它主要用于减少因多维度扩展而导致的子类爆炸问题,通过组合代替继承来实现灵活性。
- 组合模式: 用于表示部分-整体结构,让客户端统一操作简单和复杂对象,常用于树形结构如文件系统。
- 装饰器模式: 动态为对象添加功能,而无需修改类结构,常用于扩展行为或责任链设计。
- 外观模式: 为复杂系统提供简化接口,隐藏系统内部实现细节,降低客户端与系统的耦合度。
- 享元模式: 通过将对象分为共享的内在状态和独立的外在状态,减少内存占用,适合大量相似对象的场景,如文本编辑器中的字符表示。
- 代理模式: 为对象提供替代方案以控制访问,可以在访问前后添加功能(如日志记录或缓存),适合控制复杂对象的生命周期或延迟加载。
- 责任链模式: 将请求沿链路传递,由链中的每个处理者决定是否处理请求或将其传递下去,常用于验证、日志记录和事件处理。
- 命令模式: 将请求封装为对象,使操作可以被参数化、排队、记录甚至撤销/重做,适合任务调度和复杂操作管理场景。
- 迭代器模式: 用于遍历集合数据,隐藏集合的内部表示,通过抽象的迭代器提供访问方法,常用于支持复杂或多种遍历方式。
- 中介者模式: 通过引入中介者集中管理组件间的交互,减少组件间的直接依赖,提升代码的解耦性和复用性。
- 备忘录模式: 通过捕获对象的状态快照,并允许在不破坏封装性的前提下恢复状态,适用于撤销/重做等功能。
- 观察者模式: 建立发布者与订阅者之间的动态一对多依赖关系,常用于事件驱动系统,如GUI更新。
- 状态模式: 用于管理对象在不同状态下的行为变化,常见于需要频繁切换状态的复杂系统。
- 策略模式: 将算法封装在独立类中,允许动态替换,适合需要多种算法实现的场景。
- 模板方法模式: 定义算法的通用结构,将具体步骤延迟到子类实现,适用于多个类存在相似算法的情况。
- 访问者模式: 将操作封装在独立的类中,使得在不修改元素类的情况下,可以添加新的操作,特别适用于复杂对象结构的处理。
六、代码理解
1. 工厂方法模式(Factory Method Pattern)
1.1 Java代码实例
需求:
假设我们有一个交通工具的系统,可以生成不同类型的交通工具(例如:汽车、火车、飞机)。使用工厂方法模式,我们通过不同的工厂类来创建不同类型的交通工具。
代码实现:
角色解读:
- Transport(抽象产品角色): 定义了一个
deliver()
方法,所有的具体交通工具类都实现该接口。
- Car、Train、Airplane(具体产品角色): 实现了
Transport
接口,分别代表了不同的交通工具(汽车、火车、飞机)。
- TransportFactory(抽象工厂角色): 定义了一个工厂方法
createTransport()
,该方法返回Transport
类型的对象。
- CarFactory、TrainFactory、AirplaneFactory(具体工厂角色): 实现了
TransportFactory
类,具体负责创建不同的交通工具对象。
- Client(客户端角色): 客户端通过工厂类获取不同的交通工具实例,并调用它们的
deliver()
方法。
1.2 生活实例(代码表示)
需求:
我们可以将工厂方法模式应用于餐厅订单系统。餐厅有不同的菜系,每个菜系通过相应的工厂来制作食物。比如中餐、意大利餐和快餐。
代码实现:
角色解读:
- Food(抽象产品角色): 定义了一个
serve()
方法,所有的具体食物类都实现该接口。
- ChineseFood、ItalianFood、FastFood(具体产品角色): 实现了
Food
接口,分别代表了中餐、意大利餐和快餐。
- FoodFactory(抽象工厂角色): 定义了一个工厂方法
createFood()
,该方法返回Food
类型的对象。
- ChineseFoodFactory、ItalianFoodFactory、FastFoodFactory(具体工厂角色): 实现了
FoodFactory
类,具体负责制作不同类型的食物。
- RestaurantClient(客户端角色): 客户端通过工厂类获取不同的食物实例,并调用它们的
serve()
方法。
2. 抽象工厂模式(Abstract Factory Pattern)
2.1 Java代码实例
需求:
假设我们需要创建一组相关的家具(如:椅子、沙发、咖啡桌)。不同的家具系列(如:现代、复古)会有不同的样式。使用抽象工厂模式,我们可以创建多个家具工厂,每个工厂负责创建一种家具系列。
代码实现:
角色解读:
- Chair 和 Sofa(抽象产品角色): 定义了家具类的共同接口,表示所有家具的共性行为。
- ModernChair、VictorianChair(具体产品角色): 实现了
Chair
接口,分别代表现代和复古风格的椅子。
- ModernSofa、VictorianSofa(具体产品角色): 实现了
Sofa
接口,分别代表现代和复古风格的沙发。
- FurnitureFactory(抽象工厂角色): 定义了一个抽象工厂接口,负责创建一系列相关的家具(椅子、沙发)。
- ModernFurnitureFactory、VictorianFurnitureFactory(具体工厂角色): 实现了
FurnitureFactory
接口,分别负责创建现代和复古风格的家具。
- FurnitureShop(客户端角色): 客户端通过工厂类获取不同风格的家具实例,并使用这些家具。
2.2 生活实例(代码表示)
需求:
假设你经营一个餐厅,并且有不同风格的餐具(如:现代餐具、复古餐具)。每种风格的餐具包含刀、叉和盘子。你希望能够根据不同的餐具风格创建完整的一套餐具。
代码实现:
角色解读:
- Knife、Fork、Plate(抽象产品角色): 定义了餐具类的共同接口,表示所有餐具的共性行为。
- ModernKnife、VictorianKnife、ModernFork、VictorianFork、ModernPlate、VictorianPlate(具体产品角色): 实现了餐具接口,分别代表不同风格的刀、叉和盘子。
- DinnerwareFactory(抽象工厂角色): 定义了一个工厂接口,负责创建一系列相关的餐具(刀、叉、盘子)。
- ModernDinnerwareFactory、VictorianDinnerwareFactory(具体工厂角色): 实现了
DinnerwareFactory
接口,分别负责创建现代和复古风格的餐具。
- Restaurant(客户端角色): 客户端通过工厂类获取不同风格的餐具实例,并使用这些餐具。
3. 建造者模式(Builder Pattern)
3.1 Java代码实例
需求:
假设我们正在构建一个复杂的“电脑”对象。一个电脑包含多个部件(如CPU、内存、硬盘、显示器等),这些部件可以有不同的配置。使用建造者模式,可以分步骤地构建一个复杂的电脑对象。
代码实现:
角色解读:
- Computer(产品角色): 这是最终构建出来的产品,包含多个部件(CPU、内存、硬盘、显示器)。
- ComputerBuilder(抽象建造者角色): 定义了构建电脑的步骤,包含创建、设置和返回最终产品的抽象方法。
- HighEndComputerBuilder、MidRangeComputerBuilder(具体建造者角色): 实现了
ComputerBuilder
接口,提供不同配置的具体建造过程。
- Director(指挥者角色): 负责指挥建造过程,决定使用哪个建造者来构建最终的产品。
3.2 生活实例(代码表示)
需求:
我们可以将建造者模式应用于建筑房屋的过程。房屋的建造包含多个步骤,如建立基础、搭建墙壁、安装屋顶、安装门窗等,不同的房屋可能有不同的配置(如现代房屋、乡村风格房屋等)。
代码实现:
角色解读:
- House(产品角色): 这是最终构建出来的房屋对象,包含基础、墙壁、屋顶和门窗等部件。
- HouseBuilder(抽象建造者角色): 定义了建造房屋的步骤,包括建造基础、墙壁、屋顶和门窗。
- ModernHouseBuilder、CountryHouseBuilder(具体建造者角色): 实现了
HouseBuilder
接口,分别负责建造现代风格和乡村风格的房屋。
- HouseDirector(指挥者角色): 负责指挥建造过程,决定使用哪个建造者来构建最终的房屋。
4. 原型模式(Prototype Pattern)
4.1 Java代码实例
需求:
假设我们需要创建一个图形编辑器,允许用户复制已有的图形对象(如:圆形、矩形等)。使用原型模式,我们可以通过克隆已有图形对象来创建新对象,而不需要重新实例化。
代码实现:
角色解读:
- Shape(原型角色): 定义了所有图形的公共接口,其中包括
draw()
方法用于绘制图形,以及clone()
方法用于克隆图形。
- Circle、Rectangle(具体原型角色): 实现了
Shape
接口,并提供了具体的图形实现。它们支持克隆功能,通过clone()
方法返回自身的副本。
- ShapeManager(管理角色): 负责管理图形的注册和克隆。它保存图形的副本并提供克隆服务。
- Client(客户端角色): 客户端通过管理器获取克隆的图形实例。
4.2 生活实例(代码表示)
需求:
假设你是一个工厂老板,工厂里生产各种不同类型的手机(如:智能手机、功能手机等)。你希望能够根据现有的手机型号来快速生产新的手机,而不需要每次都重新制造一个全新的手机。
代码实现:
角色解读:
- Phone(原型角色): 定义了所有手机的公共接口,其中包括
call()
方法用于拨打电话,以及clone()
方法用于克隆手机。
- Smartphone、FeaturePhone(具体原型角色): 实现了
Phone
接口,分别代表智能手机和功能手机,并提供了克隆功能,通过clone()
方法返回自身的副本。
- PhoneManager(管理角色): 负责管理手机的注册和克隆。它保存手机的副本并提供克隆服务。
- Client(客户端角色): 客户端通过管理器获取克隆的手机实例,并使用这些手机。
5. 单例模式(Singleton Pattern)
5.1 Java代码实例
需求:
假设我们有一个全局配置管理类,它在整个程序中只需要有一个实例。为了确保系统只创建一个配置实例,我们可以使用单例模式。
代码实现:
角色解读:
- ConfigManager(单例类): 负责创建唯一实例的类,提供全局访问点。它有一个私有的构造函数和一个静态的
getInstance()
方法来确保只创建一个实例。
- getInstance()(访问方法): 用于获取唯一的实例,并确保在多线程环境下创建实例时的线程安全。通过双重检查锁定来避免重复创建。
- Client(客户端角色): 客户端通过
ConfigManager.getInstance()
获取唯一的配置实例,确保全局只有一个配置管理器实例。
5.2 生活实例(代码表示)
需求:
假设我们有一个企业的资源池管理系统,系统中只允许有一个资源池实例,所有的资源都由这个唯一的池来管理。我们可以通过单例模式来确保系统中只有一个资源池。
代码实现:
角色解读:
- ResourcePool(单例类): 负责管理资源池的唯一实例,提供全局访问点。它有一个私有的构造函数和一个静态的
getInstance()
方法来确保只有一个实例存在。
- getInstance()(访问方法): 用于获取唯一的资源池实例,确保线程安全,并通过双重检查锁定来避免重复创建。
- Client(客户端角色): 客户端通过
ResourcePool.getInstance()
方法获取唯一的资源池实例,确保系统只有一个资源池。
6. 适配器模式(Adapter Pattern)
6.1 Java代码实例
需求:
假设我们有一个旧的
LegacySystem
类,它通过oldMethod()
方法提供服务。现在我们有一个新的NewSystem
类,它通过newMethod()
方法提供服务。为了让旧系统能够在新系统中继续工作,我们使用适配器模式,创建一个Adapter
类,将oldMethod()
方法适配为newMethod()
方法。代码实现:
角色解读:
- NewSystem(目标接口角色): 定义了新系统应该提供的方法(
newMethod()
)。
- LegacySystem(源角色): 定义了旧系统的方法(
oldMethod()
),需要通过适配器来与新系统兼容。
- Adapter(适配器角色): 实现了
NewSystem
接口,并将旧系统的oldMethod()
适配为newMethod()
,使旧系统能够在新环境中使用。
- Client(客户端角色): 客户端通过适配器使用新系统接口,而不需要关心旧系统的实现细节。
6.2 生活实例(代码表示)
需求:
假设你在一个图书馆里,有两种方式来获取书籍:一种是通过
searchByTitle
方法根据书名搜索书籍,另一种是通过searchByAuthor
方法根据作者名字搜索书籍。如果图书馆管理系统只提供searchByTitle
方法,而你需要一个searchByAuthor
方法,你可以创建一个适配器来适配这两种方式。代码实现:
角色解读:
- BookSearchByTitle(目标接口角色): 定义了客户端需要使用的接口方法(
searchByTitle()
)。
- BookSearchByAuthor(源接口角色): 提供了按照作者名字搜索书籍的方法(
searchByAuthor()
)。
- Library(具体类角色): 实现了
BookSearchByAuthor
接口,提供了按作者名字搜索书籍的具体实现。
- SearchAdapter(适配器角色): 实现了
BookSearchByTitle
接口,并通过适配器的searchByTitle()
方法,调用源接口BookSearchByAuthor
的searchByAuthor()
方法。
- Client(客户端角色): 客户端通过
BookSearchByTitle
接口进行书籍搜索,而不需要直接使用BookSearchByAuthor
接口。
7. 桥接模式(Bridge Pattern)
7.1 Java代码实例
需求:
假设我们有一个图形绘制系统,需要同时支持不同的形状(如:圆形、矩形)和不同的颜色(如:红色、蓝色)。为了避免产生大量的子类(如:
RedCircle
, BlueCircle
, RedRectangle
等),我们使用桥接模式,将形状和颜色的变化分开,使用桥接将它们组合在一起。代码实现:
角色解读:
- Shape(抽象桥接角色): 定义了所有形状的共性行为,持有一个
Color
对象,用于绘制形状。
- Circle、Rectangle(具体桥接角色): 实现了
Shape
类,提供具体的形状(圆形、矩形)实现。它们依赖于Color
接口来决定形状的颜色。
- Color(实现者接口角色): 定义了所有颜色的共性行为,具体实现类如
Red
和Blue
提供了不同颜色的绘制方法。
- Red、Blue(具体实现角色): 实现了
Color
接口,定义了具体的颜色实现,负责绘制红色或蓝色。
- Client(客户端角色): 客户端通过组合具体形状和具体颜色,生成不同的形状和颜色组合,并绘制出来。
7.2 生活实例(代码表示)
需求:
假设我们要设计一个电视遥控器系统,遥控器有不同的按钮类型(如:音量按钮、频道按钮),且每个按钮有不同的操作(如:增加音量、减小音量)。我们希望将按钮的类型与操作分开管理,并能够动态切换。
代码实现:
角色解读:
- Button(抽象桥接角色): 定义了按钮的共性行为(
press()
方法)。
- VolumeButton、ChannelButton(具体桥接角色): 实现了
Button
接口,分别代表音量按钮和频道按钮。它们依赖于VolumeControl
和ChannelControl
接口来执行不同的操作。
- VolumeControl(实现者接口角色): 定义了音量调节的共性行为,具体实现类如
IncreaseVolume
和DecreaseVolume
提供了具体的音量操作。
- ChannelControl(实现者接口角色): 定义了频道切换的共性行为,具体实现类如
NextChannel
和PreviousChannel
提供了具体的频道切换操作。
- IncreaseVolume、DecreaseVolume、NextChannel、PreviousChannel(具体实现角色): 实现了
VolumeControl
和ChannelControl
接口,提供了具体的操作逻辑。
- RemoteControl(客户端角色): 客户端通过组合具体按钮和具体操作来动态切换按钮的行为。
8. 组合模式(Composite Pattern)
8.1 Java代码实例
需求:
假设我们在实现一个文件系统,文件系统中包含文件和文件夹。文件夹可以包含多个文件或子文件夹,文件则是最基本的元素。使用组合模式可以让我们把文件和文件夹统一看作一个文件系统元素,方便我们操作整个文件系统。
代码实现:
角色解读:
- FileSystemElement(组件角色): 定义了文件系统中所有元素的公共接口,包含一个
display()
方法用于显示名称。
- File(叶子节点角色): 继承自
FileSystemElement
类,代表文件,文件没有子元素,因此只实现了display()
方法。
- Folder(容器节点角色): 继承自
FileSystemElement
类,代表文件夹,文件夹可以包含多个文件或子文件夹(即其他FileSystemElement
对象)。文件夹实现了add()
和remove()
方法,用于管理文件或子文件夹,并通过display()
方法显示所有子元素。
- Client(客户端角色): 客户端通过
Folder
类来管理文件和子文件夹,展示了如何通过组合的方式构建一个复杂的文件系统,并调用display()
方法来显示文件系统结构。
8.2 生活实例(代码表示)
需求:
假设你正在设计一个组织结构,组织结构中包括员工和经理,经理可以管理多个员工。每个员工或经理都有一个共同的属性——名字,且可以显示自己的信息。我们希望用组合模式来表示这种层级关系。
代码实现:
角色解读:
- Employee(组件角色): 定义了员工的公共接口,包含一个
showDetails()
方法用于显示员工的信息。
- Staff(叶子节点角色): 实现了
Employee
接口,代表普通员工,员工没有子成员,因此只实现了showDetails()
方法。
- Manager(容器节点角色): 实现了
Employee
接口,代表经理,经理可以管理多个员工。经理类实现了addTeamMember()
和removeTeamMember()
方法,用于管理其团队成员,并通过showDetails()
方法显示团队成员的详细信息。
- Client(客户端角色): 客户端通过
Manager
类来管理员工和经理,通过showDetails()
方法展示整个组织结构。
9.装饰器模式(Decorator Pattern)
9.1 Java代码实例
需求:
假设我们有一个图形绘制系统,基本的形状有圆形和矩形。现在,我们希望能够动态地为这些形状添加装饰功能(如:设置边框、填充颜色等),而不修改原有的图形类。通过装饰器模式,我们可以在运行时给图形对象添加额外的功能。
代码实现:
角色解读:
- Shape(组件角色): 定义了图形的共性行为,所有图形类都实现了
draw()
方法。
- Circle、Rectangle(具体组件角色): 实现了
Shape
接口,代表具体的图形(圆形和矩形)。
- ShapeDecorator(装饰器抽象角色): 继承自
Shape
,包装了一个Shape
对象,并将draw()
方法委托给被装饰的Shape
对象。装饰器类可以动态地给对象添加额外的功能。
- RedBorderDecorator、ColorFillDecorator(具体装饰器角色): 具体的装饰器类,分别为图形添加红色边框和填充颜色。每个装饰器类都可以在
draw()
方法中添加额外的功能。
- Client(客户端角色): 客户端通过装饰器动态地为图形对象添加不同的功能,而不修改原始图形类的代码。
9.2 生活实例(代码表示)
需求:
假设你在设计一个饮品系统。基础饮品有咖啡和茶,但是你可以为这些饮品添加不同的配料(如:糖、奶)。通过装饰器模式,我们可以在不修改原有饮品类的情况下,动态地为饮品添加额外的配料。
代码实现:
角色解读:
- Beverage(组件角色): 定义了饮品的共性行为,所有饮品类都实现了
prepare()
方法。
- Coffee、Tea(具体组件角色): 实现了
Beverage
接口,代表具体的饮品(咖啡和茶)。
- BeverageDecorator(装饰器抽象角色): 继承自
Beverage
,包装了一个Beverage
对象,并将prepare()
方法委托给被装饰的Beverage
对象。装饰器类可以动态地为对象添加额外的功能。
- SugarDecorator、MilkDecorator(具体装饰器角色): 具体的装饰器类,分别为饮品添加糖和奶。每个装饰器类都可以在
prepare()
方法中添加额外的功能。
- Client(客户端角色): 客户端通过装饰器动态地为饮品对象添加不同的配料,而不修改原始饮品类的代码。
10.外观模式(Facade Pattern)
10.1 Java代码实例
需求:
假设我们有一个复杂的子系统,包含多个模块(如:文件读取模块、数据解析模块、报告生成模块)。现在,我们希望提供一个简化的接口,使得客户端只需要与外观类交互,避免直接与多个子系统交互。
代码实现:
角色解读:
- FileReader、DataParser、ReportGenerator(子系统角色): 这三个类分别负责不同的操作,如文件读取、数据解析和报告生成。它们有各自的复杂实现,客户端不需要直接与它们交互。
- ReportFacade(外观类角色): 外观类提供了一个简化的接口,将多个子系统操作封装在一起。客户端只需要调用
generateReport()
方法,外观类会按顺序调用子系统中的各个方法。
- Client(客户端角色): 客户端通过外观类与复杂的子系统交互,不需要关心子系统的实现细节,简化了与多个模块的交互。
10.2 生活实例(代码表示)
需求:
假设我们有一个家庭影院系统,系统包含多个部件(如:电视、音响、DVD播放器)。现在,我们希望提供一个简化的控制接口,用户只需要按下一个按钮,系统就能自动启动并播放电影。我们通过外观模式将这些复杂的操作封装起来。
代码实现:
角色解读:
- Television、SoundSystem、DVDPlayer(子系统角色): 这些类分别代表家庭影院系统中的不同设备,提供了打开、关闭、调整音量和播放电影等功能。它们有各自复杂的操作,客户端无需直接操作这些设备。
- HomeTheaterFacade(外观类角色): 外观类提供了一个简化的接口,封装了多个子系统的操作。客户端只需调用
watchMovie()
方法,外观类会自动启动电视、音响和DVD播放器并播放电影;调用endMovie()
方法,外观类会自动关闭所有设备。
- Client(客户端角色): 客户端通过外观类与复杂的家庭影院系统交互,不需要关心各个设备的操作细节,简化了操作过程。
11. 享元模式(Flyweight Pattern)
11.1 Java代码实例
需求:
假设我们正在开发一个文本编辑器,编辑器中每个字符都是一个对象。为了节省内存,我们可以使用享元模式,将相同的字符对象共享,而不是为每个字符创建独立的对象。比如多个字符“a”共享一个字符对象,多个字符“b”共享另一个字符对象。
代码实现:
角色解读:
- Character(享元角色): 定义了所有字符的共同行为,即显示字符的方法
display()
,并接收字体大小作为参数。
- ConcreteCharacter(具体享元角色): 实现了
Character
接口,表示具体的字符。每个ConcreteCharacter
对象持有一个字符内容(如:"a"
、"b"
等)。
- CharacterFactory(享元工厂角色): 负责管理字符对象的共享和复用。工厂类维护了一个
Map
,用于缓存已创建的字符对象,如果字符对象不存在,则创建并缓存它。
- Client(客户端角色): 客户端通过工厂获取字符对象,使用相同字符的多个实例时,它们共享同一个对象,从而节省内存。
11.2 生活实例(代码表示)
需求:
假设我们在设计一个图形绘制系统,需要绘制许多相同类型的图形(如:树木)。为了节省内存,我们可以使用享元模式,让相同的树木共享一个对象,只根据位置来区分。
代码实现:
角色解读:
- Tree(享元角色): 定义了所有树木的共同行为,即绘制树木的方法
draw()
,并接收位置作为参数。
- ConcreteTree(具体享元角色): 实现了
Tree
接口,表示具体的树木。每个ConcreteTree
对象持有树木的类型(如:"橡树"
、"松树"
等)。
- TreeFactory(享元工厂角色): 负责管理树木对象的共享和复用。工厂类维护了一个
Map
,用于缓存已创建的树木对象。如果树木对象不存在,则创建并缓存它。
- Client(客户端角色): 客户端通过工厂获取树木对象,多个相同类型的树木共享一个对象,从而节省内存。
12. 代理模式(Proxy Pattern)
12.1 Java代码实例
需求:
假设我们有一个图片显示系统。图片加载是一个耗时操作,当我们需要加载大尺寸图片时,我们希望在真正加载图片之前,先显示一个占位符。代理模式可以帮助我们在图片加载的过程中提供占位符,并延迟实际的图片加载过程。
代码实现:
角色解读:
- Image(主题接口角色): 定义了图片类的共同行为,包含
display()
方法。
- RealImage(真实主题角色): 实现了
Image
接口,代表真正的图片对象。它负责加载和显示图片。
- ProxyImage(代理角色): 实现了
Image
接口,用于延迟加载图片并控制对真实图片的访问。在第一次调用display()
时,代理类会加载真实图片,并在之后直接使用真实图片显示。
- Client(客户端角色): 客户端通过
ProxyImage
对象来访问图片,避免直接访问RealImage
,并实现了延迟加载的效果。
12.2 生活实例(代码表示)
需求:
假设我们有一个银行账户系统,银行账户包含存款和取款操作。为了避免直接访问银行账户,我们可以通过代理模式在取款操作前进行权限检查。例如,银行会使用代理对取款请求进行授权。
代码实现:
角色解读:
- BankAccount(主题接口角色): 定义了银行账户的共同行为,包含
withdraw()
和deposit()
方法。
- RealBankAccount(真实主题角色): 实现了
BankAccount
接口,代表真实的银行账户,负责实际的存款和取款操作。
- ProxyBankAccount(代理角色): 实现了
BankAccount
接口,在执行实际操作之前进行权限检查,只有授权用户才能进行取款操作。它通过代理模式控制对RealBankAccount
的访问。
- Client(客户端角色): 客户端通过
ProxyBankAccount
对象来访问银行账户,代理类负责权限验证和安全控制。
13. 责任链模式(Chain of Responsibility Pattern)
13.1 Java代码实例
需求:
假设我们有一个在线客服系统,客户的问题需要按照优先级进行处理。系统有多个客服代表,他们按顺序处理问题。如果某个代表无法处理问题,他们会将问题传递给下一个代表。我们希望使用责任链模式来实现这个逻辑。
代码实现:
角色解读:
- CustomerService(处理者角色): 定义了一个处理请求的方法
handleRequest()
,每个具体的客服代表都继承此类,并实现自己的请求处理逻辑。如果该代表无法处理请求,则将请求传递给下一个代表。
- JuniorSupport、IntermediateSupport、SeniorSupport(具体处理者角色): 分别代表不同级别的客服代表,它们实现了
handleRequest()
方法。根据问题的复杂度,决定是否处理请求,或者将请求传递给下一个处理者。
- Client(客户端角色): 客户端创建一条责任链,并通过责任链上的每个客服代表处理客户的问题。客户端无须关心具体的处理逻辑,只需提交问题即可。
13.2 生活实例(代码表示)
需求:
假设我们设计一个餐厅点餐系统,点餐时需要经过多个环节:前台接待员接单、厨师准备食物、服务员送餐。我们希望使用责任链模式,按顺序处理每个环节的工作。
代码实现:
角色解读:
- RestaurantStaff(处理者角色): 定义了一个处理请求的方法
handleOrder()
,每个具体的餐厅工作人员都继承此类,并实现自己的处理逻辑。如果该工作人员无法处理请求,则将请求传递给下一个工作人员。
- Receptionist、Chef、Waiter(具体处理者角色): 分别代表不同的餐厅工作人员,它们实现了
handleOrder()
方法,根据订单内容决定是否处理请求,或者将请求传递给下一个环节。
- Client(客户端角色): 客户端创建一条责任链,并通过责任链上的每个工作人员处理顾客的订单。客户端无需关心具体的处理环节,只需提交订单即可。
14. 命令模式(Command Pattern)
14.1 Java代码实例
需求:
假设我们有一个远程控制系统,其中有多个命令(如:开灯、关灯、调节音量等)。命令模式可以将请求封装为一个对象,从而让我们能够将请求调用者与实际执行请求的对象解耦。
代码实现:
角色解读:
- Command(命令接口角色): 定义了一个
execute()
方法,所有具体命令类都实现这个接口,以封装请求。
- LightOnCommand、LightOffCommand、VolumeUpCommand(具体命令角色): 实现了
Command
接口,封装了对特定接收者的请求(如:开灯、关灯、调高音量)。
- Light、AudioSystem(接收者角色): 这些类接收命令并执行实际操作,如灯的开启、关闭和音量的调节。
- RemoteControl(调用者角色):
RemoteControl
类持有一个命令对象并调用execute()
方法来执行命令。它将具体的请求与请求的执行解耦。
- Client(客户端角色): 客户端通过遥控器来设置命令,并执行按钮操作。客户端无须知道具体的请求处理逻辑,只需要关心命令的设置和执行。
14.2 生活实例(代码表示)
需求:
假设我们有一个电视遥控器系统,遥控器上有不同的按钮(如:开机、关机、调节音量等)。每个按钮对应一个命令,按下按钮时执行相应的操作。我们希望通过命令模式来解耦按钮和操作。
代码实现:
角色解读:
- TVCommand(命令接口角色): 定义了所有命令的共同行为,包含
execute()
方法。
- TVOnCommand、TVOffCommand、VolumeUpCommand(具体命令角色): 实现了
TVCommand
接口,封装了对电视的具体请求(如:开机、关机、调节音量)。
- Television(接收者角色): 电视类负责实际执行命令,执行开机、关机、音量调节等操作。
- RemoteControl(调用者角色): 遥控器类持有一个命令对象,并在按钮按下时调用
execute()
方法来执行命令。它将请求与命令的执行解耦。
- Client(客户端角色): 客户端通过遥控器设置命令并执行按钮操作,遥控器将具体操作通过命令传递给电视。
15. 迭代器模式(Iterator Pattern)
15.1 Java代码实例
需求:
假设我们有一个自定义的集合类,其中存储了多个元素(如:字符串)。我们希望能够遍历集合中的所有元素,而不暴露集合的内部实现。使用迭代器模式,我们可以通过迭代器来遍历集合。
代码实现:
角色解读:
- Iterator(迭代器角色): 定义了遍历集合的方法,包含
hasNext()
和next()
方法,用于判断是否有下一个元素并返回下一个元素。
- Collection(集合角色): 定义了集合的共同行为,包括创建迭代器的方法
createIterator()
。
- StringCollection(具体集合角色): 实现了
Collection
接口,代表自定义的集合类,包含一个List
来存储元素。它提供了一个createIterator()
方法来创建迭代器。
- StringIterator(具体迭代器角色): 实现了
Iterator
接口,负责遍历StringCollection
中的元素,hasNext()
方法判断是否有下一个元素,next()
方法返回下一个元素。
- Client(客户端角色): 客户端通过迭代器对象遍历集合,避免直接访问集合的内部实现。
15.2 生活实例(代码表示)
需求:
假设你在设计一个书架管理系统,书架上有许多书籍。为了能够按顺序浏览书架上的书籍,我们可以使用迭代器模式来遍历书架上的所有书籍。
代码实现:
角色解读:
- BookIterator(迭代器角色): 定义了遍历书架的行为,包含
hasNext()
和next()
方法,用于判断是否有下一个书籍并返回下一个书籍。
- BookCollection(集合角色): 定义了书架的共同行为,包括创建迭代器的方法
createIterator()
。
- BookShelf(具体集合角色): 实现了
BookCollection
接口,代表书架,包含一个List
来存储书籍。它提供了一个createIterator()
方法来创建迭代器。
- BookShelfIterator(具体迭代器角色): 实现了
BookIterator
接口,负责遍历BookShelf
中的书籍,hasNext()
方法判断是否有下一个书籍,next()
方法返回下一个书籍。
- Client(客户端角色): 客户端通过迭代器对象浏览书架上的书籍,避免直接访问书架的内部实现。
16. 中介者模式(Mediator Pattern)
16.1 Java代码实例
需求:
假设我们有一个聊天应用程序,多个用户在聊天室中相互交流。如果每个用户都直接与其他用户进行通信,系统会变得非常复杂。使用中介者模式,我们可以通过一个“聊天室”中介者来管理所有用户之间的通信,简化各个用户之间的互动。
代码实现:
角色解读:
- ChatMediator(中介者角色): 定义了发送消息的方法
sendMessage()
和添加用户的方法addUser()
。它负责管理所有用户之间的消息传递。
- ChatRoom(具体中介者角色): 实现了
ChatMediator
接口,代表聊天室中介者,负责在聊天室内传递消息给其他用户,并管理聊天室中的所有用户。
- User(用户角色): 定义了用户的共同行为,包括发送消息和接收消息的方法。用户之间不直接通信,而是通过中介者来交换消息。
- ConcreteUser(具体用户角色): 实现了
User
类,代表具体的用户。每个用户都有一个与之关联的中介者,用户通过中介者发送和接收消息。
- Client(客户端角色): 客户端创建一个聊天室实例,向聊天室添加多个用户,并模拟用户之间的消息交流。
16.2 生活实例(代码表示)
需求:
假设我们有一个航空公司调度系统,多个航班的调度需要通过调度中心来进行管理。不同的航班可能需要调整时间或停机位,但他们之间不直接交互,而是通过调度中心来协调。
代码实现:
角色解读:
- FlightMediator(中介者角色): 定义了航班请求着陆和起飞的中介者行为。
- FlightControlTower(具体中介者角色): 实现了
FlightMediator
接口,代表航班调度中心,负责处理各个航班的着陆和起飞请求。
- Flight(航班角色): 定义了航班的共同行为,包括请求着陆和请求起飞的方法。航班不直接与其他航班交互,而是通过调度中心进行操作。
- ConcreteFlight(具体航班角色): 实现了
Flight
类,代表具体的航班。每个航班通过中介者与调度中心通信,进行着陆和起飞的请求。
- Client(客户端角色): 客户端创建航班调度中心和多个航班,并模拟航班之间的请求处理。
17. 备忘录模式(Memento Pattern)
17.1 Java代码实例
需求:
假设我们有一个文本编辑器,用户可以在编辑过程中随时保存当前的状态,并可以在之后恢复到某个保存的状态。我们希望通过备忘录模式来保存和恢复编辑器的状态,而不暴露编辑器的内部实现。
代码实现:
角色解读:
- Memento(备忘录角色): 用于保存对象的状态。它仅存储状态信息,并不包含行为,因此它不会干扰对象的业务逻辑。
- TextEditor(发起人角色): 保存和恢复其状态的对象。在此例中,
TextEditor
类是发起人,它具有状态text
,并能够创建备忘录来保存当前状态,恢复备忘录中的状态。
- Caretaker(管理者角色): 负责管理备忘录对象的存储和恢复。它不关心备忘录的内容,只负责保存和检索备忘录对象。
- Client(客户端角色): 客户端负责创建文本编辑器、保存和恢复状态。客户端通过
Caretaker
来管理备忘录对象,并在需要时恢复到某个特定的状态。
17.2 生活实例(代码表示)
需求:
假设我们设计一个图形编辑器,用户可以画出一些图形,并且可以在之后撤销或恢复某些操作。我们希望通过备忘录模式来保存和恢复图形的状态。
代码实现:
角色解读:
- ShapeMemento(备忘录角色): 保存图形的状态。它仅仅存储图形状态的信息,不会改变图形对象的行为。
- ShapeEditor(发起人角色): 图形编辑器类,负责管理图形的状态,保存状态并可以恢复到之前的状态。它有
setShapeState()
和getShapeState()
方法来设置和获取图形的状态。
- ShapeCaretaker(管理者角色): 负责管理图形的状态备忘录。它存储了多个备忘录对象,并允许恢复到某个特定的状态。
- Client(客户端角色): 客户端通过
ShapeEditor
和ShapeCaretaker
来保存和恢复图形状态。客户端创建图形、保存状态,并在需要时恢复图形的状态。
18. 观察者模式(Observer Pattern)
18.1 Java代码实例
需求:
假设我们有一个天气预报系统,多个显示设备(如手机、电脑、电视等)需要订阅天气数据的变化。当天气数据发生变化时,所有订阅设备都会接收到更新并显示新的天气信息。我们可以使用观察者模式来实现这一功能。
代码实现:
角色解读:
- Observer(观察者角色): 定义了
update()
方法,所有具体观察者类都实现该接口,用于接收被观察者的更新信息。
- WeatherSubject(被观察者角色): 定义了注册、移除和通知观察者的方法。它负责管理所有观察者,并在天气变化时通知所有观察者。
- WeatherData(具体被观察者角色): 实现了
WeatherSubject
接口,代表天气数据类。它持有一个天气条件的变量,并在天气变化时通知所有注册的观察者。
- PhoneDisplay、TVDisplay(具体观察者角色): 实现了
Observer
接口,分别代表手机和电视显示器。它们在接收到天气更新时会更新并显示最新的天气信息。
- Client(客户端角色): 客户端创建一个天气数据对象,并将多个显示设备(观察者)注册到这个对象上。当天气变化时,所有注册的观察者都会接收到更新。
18.2 生活实例(代码表示)
需求:
假设我们设计一个新闻发布系统,多个新闻订阅者(如报纸、电视台、网站等)需要订阅新闻数据的变化。当有新新闻发布时,所有订阅者都会接收到更新并显示最新新闻。
代码实现:
角色解读:
- NewsSubscriber(观察者角色): 定义了
update()
方法,所有具体的新闻订阅者(如报纸、电视台等)都实现该接口,用于接收新闻更新。
- NewsPublisher(被观察者角色): 定义了注册、移除和通知订阅者的方法。它负责管理所有订阅者,并在新闻变化时通知所有订阅者。
- NewsAgency(具体被观察者角色): 实现了
NewsPublisher
接口,代表新闻发布机构。它持有新闻内容并在新闻发布时通知所有注册的订阅者。
- Newspaper、Television(具体观察者角色): 实现了
NewsSubscriber
接口,代表具体的订阅者(如报纸、电视台)。它们在接收到新闻更新时更新并显示最新的新闻。
- Client(客户端角色): 客户端创建一个新闻发布机构对象,并将多个新闻订阅者(观察者)注册到这个对象上。当新闻发生变化时,所有注册的订阅者都会接收到更新。
19. 状态模式(State Pattern)
19.1 Java代码实例
需求:
假设我们有一个自动售货机,自动售货机的状态会随着操作的不同而变化,例如:待机状态、等待付款状态、商品已出状态等。我们希望通过状态模式来管理自动售货机的状态变化,使得每个状态的行为独立且易于扩展。
代码实现:
角色解读:
- VendingMachineState(状态接口角色): 定义了自动售货机各个状态下的行为(如:投币、退币、按按钮、发放商品等)。
- NoCoinState、HasCoinState、SoldState(具体状态角色): 实现了
VendingMachineState
接口,分别代表不同的状态(如:待机状态、已投币状态、商品已出状态)。每个状态类定义了在该状态下的具体行为。
- VendingMachine(上下文角色): 管理当前状态,并根据操作委托给具体状态类来执行不同的行为。它包含不同的状态对象,并在不同的状态之间进行切换。
- Client(客户端角色): 客户端通过
VendingMachine
对象操作自动售货机,自动售货机根据当前的状态来决定如何响应操作。
19.2 生活实例(代码表示)
需求:
假设我们有一个电梯系统,电梯有不同的状态(如:停止状态、上升状态、下降状态)。我们希望通过状态模式来管理电梯的状态变化,使得电梯在不同的状态下执行不同的操作。
代码实现:
角色解读:
- ElevatorState(状态接口角色): 定义了电梯状态下的共同行为,包括按钮操作、门的打开/关闭、以及电梯的移动等方法。
- StoppedState、MovingUpState、MovingDownState(具体状态角色): 实现了
ElevatorState
接口,分别代表电梯的停止状态、上升状态和下降状态。每个状态类定义了在该状态下的具体行为。
- Elevator(上下文角色): 管理当前状态,并根据操作委托给具体状态类来执行不同的行为。它包含不同的状态对象,并在不同的状态之间进行切换。
- Client(客户端角色): 客户端通过
Elevator
对象操作电梯,电梯根据当前的状态来决定如何响应操作。
20. 策略模式(Strategy Pattern)
20.1 Java代码实例
需求:
假设我们有一个购物系统,用户可以选择不同的支付方式(如:信用卡支付、支付宝支付、微信支付等)。为了避免在每种支付方式中写重复的代码,我们可以使用策略模式,将每种支付方式封装成独立的策略,并在运行时根据用户的选择来切换支付策略。
代码实现:
角色解读:
- PaymentStrategy(策略接口角色): 定义了所有支付方式的共同行为,即
pay()
方法,用于执行支付操作。
- CreditCardPayment、AlipayPayment、WeChatPayment(具体策略角色): 每个类实现了
PaymentStrategy
接口,代表一种支付方式。它们实现了pay()
方法,封装了具体的支付逻辑。
- ShoppingCart(上下文角色):
ShoppingCart
类持有一个PaymentStrategy
对象,并通过setPaymentStrategy()
方法动态选择支付策略。在checkout()
方法中调用当前支付策略的pay()
方法来完成支付。
- Client(客户端角色): 客户端通过
ShoppingCart
对象选择支付策略并执行支付。客户端无需关心具体的支付实现,只需切换策略即可。
20.2 生活实例(代码表示)
需求:
假设我们设计一个文本编辑器,编辑器可以使用不同的格式化策略来处理文本,例如:加粗、斜体、下划线等。我们希望通过策略模式来封装每种格式化方式,并允许用户选择不同的格式化策略。
代码实现:
角色解读:
- TextFormatStrategy(策略接口角色): 定义了所有文本格式化策略的共同行为,即
formatText()
方法,用于格式化文本。
- BoldTextFormat、ItalicTextFormat、UnderlineTextFormat(具体策略角色): 每个类实现了
TextFormatStrategy
接口,代表一种文本格式化策略。它们实现了formatText()
方法,封装了具体的文本格式化逻辑。
- TextEditor(上下文角色):
TextEditor
类持有一个TextFormatStrategy
对象,并通过setTextFormatStrategy()
方法动态选择文本格式化策略。在formatText()
方法中调用当前策略的formatText()
方法来完成文本格式化。
- Client(客户端角色): 客户端通过
TextEditor
对象选择文本格式化策略并执行格式化操作。客户端无需关心具体的格式化实现,只需切换策略即可。
21. 访问者模式(Visitor Pattern)
21.1 Java代码实例
需求:
假设我们有一个文件系统,文件系统中有不同类型的文件(如:文本文件、图像文件等)。我们希望通过访问者模式来实现对不同类型文件的操作,如计算文件的大小、显示文件的内容等。每个文件类型都有不同的操作,我们希望将这些操作封装在访问者中,而不是在文件类中实现。
代码实现:
角色解读:
- FileElement(元素接口角色): 定义了接受访问者的接口。所有具体的文件元素类(如:
TextFile
、ImageFile
)都需要实现该接口,并通过accept()
方法接受访问者。
- TextFile、ImageFile(具体元素角色): 实现了
FileElement
接口,分别表示文本文件和图像文件。它们定义了特定文件的属性(如文本内容、文件名等)。
- FileVisitor(访问者接口角色): 定义了对不同类型文件元素操作的方法,
visit(TextFile)
和visit(ImageFile)
分别处理文本文件和图像文件。
- FileOperationVisitor(具体访问者角色): 实现了
FileVisitor
接口,具体执行文件操作(如显示内容、计算文件大小等)。它定义了对每种文件类型的处理逻辑。
- Client(客户端角色): 客户端创建文件元素和访问者,并通过
accept()
方法让文件元素接受访问者进行操作。
21.2 生活实例(代码表示)
需求:
假设我们有一个公司员工管理系统,系统中有不同类型的员工(如:经理、开发人员、销售人员等)。我们希望通过访问者模式来为员工执行不同的操作(如:计算薪水、评估绩效等),而不在员工类中直接实现这些操作。
代码实现:
角色解读:
- Employee(元素接口角色): 定义了接受访问者的接口,所有具体员工类(如:
Manager
、Developer
)都实现该接口,并通过accept()
方法接受访问者。
- Manager、Developer(具体元素角色): 实现了
Employee
接口,分别表示经理和开发人员。每个员工类有自己的特性(如薪水),并接受访问者对其进行操作。
- EmployeeVisitor(访问者接口角色): 定义了对不同类型员工执行的操作方法。
visit(Manager)
和visit(Developer)
分别处理经理和开发人员。
- SalaryCalculatorVisitor(具体访问者角色): 实现了
EmployeeVisitor
接口,具体执行薪水计算操作。它对每个员工类型的薪水进行计算,并输出结果。
- Client(客户端角色): 客户端创建员工对象和访问者对象,通过
accept()
方法让员工接受访问者进行操作,计算员工的薪水。
22. 模板方法模式(Template Method Pattern)
22.1 Java代码实例
需求:
假设我们在设计一个咖啡和茶的制作过程。制作咖啡和茶的过程大致相同,都有一些共同的步骤(如:煮水、倒入杯中、加调料等),但每种饮品的具体步骤有所不同(如:咖啡需要加入咖啡粉,茶需要加入茶叶)。我们可以使用模板方法模式来定义一个共同的制作流程,并允许子类根据需要重写某些步骤。
代码实现:
角色解读:
- Beverage(抽象类角色): 定义了制作饮品的模板方法
prepareRecipe()
,并提供了一些公共步骤(如:烧水、倒入杯中)。某些步骤(如:冲泡、加调料)则由子类实现。
- Coffee、Tea(具体类角色): 继承自
Beverage
类,并实现了冲泡和加调料的具体步骤。每种饮品都有自己特定的冲泡和调料步骤。
- Client(客户端角色): 客户端通过
prepareRecipe()
方法执行饮品的制作流程,自动按照模板方法中的步骤进行操作,而不需要关心具体的实现。
22.2 生活实例(代码表示)
需求:
假设我们设计一个工作流程管理系统,系统中有多个工作流程(如:创建报告、处理请求)。这些流程有一些共同的步骤(如:初始化、处理、总结等),但每个流程的具体步骤不同。我们希望通过模板方法模式来定义这些流程的共同部分,并让子类决定具体的实现。
代码实现:
角色解读:
- Workflow(抽象类角色): 定义了工作流程的模板方法
executeWorkflow()
,并提供了一些公共步骤(如:初始化、总结)。某些具体步骤(如:处理)由子类实现。
- ReportWorkflow、RequestWorkflow(具体类角色): 继承自
Workflow
类,并实现了具体的处理步骤。每个流程有不同的处理步骤(如:创建报告、处理请求)。
- Client(客户端角色): 客户端通过
executeWorkflow()
方法执行工作流程,自动按照模板方法中的步骤进行操作,而不需要关心具体的实现。
- 作者:小H狂炫香菜
- 链接:https://hjwvip.top/14e01f2a87be808eac64fdb2e42d0247
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。