- 如何实现可修改性、可扩展性、灵活性
- 典型设计模式
- 策略模式
- 抽象⼯⼚模式
- 单件模式
- 迭代器模式
- 给定场景, 应⽤设计模式并写出代码
- 给出代码, 要求⽤设计模式改写
大量设计模式组合在一起形成一种生产性语言, 能够互相配合完成高质量的设计.
如何实现可修改性, 可扩展性, 灵活性
可修改性:
- 狭义可修改性 → 修改已有实现
- 可扩展性 → 对新的实现的扩展
- 灵活性 → 对实现的动态配置
实现: 将接口与实现分离
- 通过接口和实现该接口的类完成接口与实现的分离
- 灵活性更好
- 通过子类继承父类, 将父类的接口和子类的实现分离
- 子类不但继承了父类的接口还继承了父类的实现, 更方便重用, 但是减弱了灵活性. 可以通过改继承为组合来解决.
- 父类和子类存在共有接口的耦合性, 父类接口变化时子类接口也会变化
- 子类创建对象时就决定了其实现的选择, 无法再动态修改
再通过多态实现灵活性
JAY: 我觉得似乎对于通过接口和实现的方法, 在接口类的接口发生变化时, 也要修改实现类的接口, 为什么说它比继承方式更灵活?
ChatGPT: 你提的问题非常有见地. 我们来仔细分析这个说法, 并澄清其中的误区.
前提: 两种“接口与实现分离”的方式
- 继承方式(父类 + 子类)
- 父类定义接口(可能是抽象类, 也可能是带默认实现的基类)
- 子类通过 extends 继承, 并实现/重写接口
- 接口方式(interface + 实现类)
- 接口(interface)定义行为
- 实现类通过 implements 明确实现接口
你的疑问:
你认为:
如果接口变化(不管是抽象类的父类还是 interface), 子类或实现类都必须改, 为什么说“继承方式”耦合更高、灵活性更差?
这个质疑是合理的, 但关键在于:
二者虽然接口变更时实现类都需修改它们之间的耦合程度和运行时的灵活性
关键差异分析
✅ 1.耦合层级不同
- 继承方式:
子类继承父类, 不仅要遵循父类的接口约定, 还可能复用/依赖父类的实现逻辑.
→ 父类一旦改动实现(比如添加成员变量、改变默认行为), 子类很可能也会被波及.
- 接口方式:
实现类只依赖接口的方法签名, 接口本身不含实现.
→ 接口变动时, 只有方法签名变了才需要改, 实现类基本不受“内部实现逻辑”影响.
🔎 总结: 接口方式只耦合“方法声明”, 而继承耦合“声明 + 实现”.
✅ 2.运行时灵活性不同
- 继承方式:
子类在编译期已绑定到具体的父类, 实现关系不可变(尤其在强类型语言如 Java/C++ 中).
→ 创建对象时就已决定“使用哪个实现”.
- 接口方式:
只依赖接口类型, 实现类可以运行时替换(例如通过依赖注入、策略模式).
→ 可以根据配置、上下文或运行环境动态决定具体实现.
✅ 3.开放封闭原则角度
- 继承方式倾向于修改父类来扩展新行为;
- 接口方式更适合通过新增类(实现)来扩展, 父类接口不变.
✅ 回到你的问题结论
你说得没错: 不论是父类还是接口, 如果接口方法变了, 实现类都要改.
但之所以说“继承方式的灵活性更差”, 主要是因为以下几点:
方面 | 继承方式 | 接口方式 |
耦合程度 | 方法签名 + 实现耦合 | 仅方法签名耦合 |
运行时切换能力 | 编译期决定 | 可运行时替换 |
父类改动影响范围 | 改动接口或实现都可能影响子类 | 只改动接口签名才影响实现类 |
灵活扩展性 | 容易破坏封闭原则 | 易于通过新增实现扩展 |
✅ 总结一句话:
通过接口方式分离接口与实现, 更“解耦”、更“灵活”, 因为它避免了继承对实现的依赖, 并允许在运行时自由替换实现类.
继承方式在语法层面确实也能“分离接口与实现”, 但在解耦、可维护性、扩展性、运行时灵活性等方面, 确实不如接口方式.
典型设计模式
策略模式
策略模式解决做同一件事的策略的选择问题.

设计
- 把上下文和策略分割为不同的类, 每个类实现不同的职责. 上下文类负责满足需求, 策略类负责复杂策略的实现.
- 上下文类和策略类之间关系用组合比继承更合适, 满足里氏替换原则(n个子类可以在运行时灵活选择)
- 各种策略在具体策略类中提供, 上下文类拥有统一的策略接口, 策略和上下文独立, 策略的增减和修改都不会影响上下文类.
参与者
- 上下文(Context): 被配置了具体策略信息, 拥有Strategy对象的一个引用, 实现了一些方法以供Strategt访问其数据, 可能拥有实现其他需求职责的数据和方法
- 策略(Strategy): 声明了所支持策略的接口, 利用ConcreteStrategy中定义的实现
- 具体策略(ConcreteStrategy): 实现了Strategy声明的接口, 给出了具体的实现.
协作
- Context和Strategy协作完成整个算法. Context可能通过方法, 甚至将自己的引用传给Strategy, 供其访问自身数据. Strategy会在需要时访问Context的成员变量.
- Context将一些对它的请求转发给Strategy来实现. 客户创建ConcreteStrategy的对象, 然后传递给Context来灵活配置Strategy接口的具体实现(多态).
应用场景
- 当很多相关类只在行为的实现上不一样时
- 当需要同一个行为的不同实现(变体)时, 策略模式可以用于实现这些变体
- 算法需要用到一些数据, 而这些数据不应该被客户知道, 可以通过策略模式隐藏复杂的算法和数据接口
- 一个类定义了很多行为, 需要用switch或if-else来选择分支执行部分, 此时可以通过策略模式消除分支选择
抽象工厂模式
方法调用可以多态, 但是创建对象却无法多态(必须要创建一个明确的对象).
抽象工厂模式解决如何创建对象的问题.
- 简单⼯⼚: 使⽤⼀个⼯⼚对象, 通过if-else等⽅式实例化需要的对象
- ⼯⼚⽅法: ⼀个抽象⽅法creator(可以在原来的类中), 使⽤⼦类继承creator所在的类通过实现creator ⽅法来实例化需要的对象(实例化推迟到⼦类). 问题: 组合爆炸.
- 抽象⼯⼚: 为应对灵活性要求, 提供2套接口: ⼀是表现出稳定的⼯⼚⾏为的⼯⼚接口, ⼆是表现出稳定产品⾏为的产品接口. 从⽽, 实现了⼯⼚多态和产品多态.
需要的是产品组合. 有⼀个抽象⼯⼚, 该抽象⼯⼚有所有种类产品的create方法, 不同的产品组合拥有不同的具体⼯⼚(继承抽象⼯⼚, 实现create方法).
相应地, 有一个抽象产品, 有所有种类产品的使用方法, 不同的具体产品的使用方法实现不同(继承抽象产品).

设计
将一系列相关的或相互依赖的对象创建过程封装在一个接口中, 而无需指定它们具体的类. 它提供了一个接口, 用于创建相关或依赖对象的家族, 而无需明确指定具体类, 从而降低了使用对象的类与具体对象类之间的耦合.
客户通过抽象工厂接口提供的方法得到对象的实例, 再通过产品接口来灵活使用对应的产品.
参与者
- 抽象工厂: 声明了创建抽象产品对象的接口.
- 具体工厂: 实现抽象工厂接口, 负责创建具体产品实例.
- 抽象产品: 声明了一系列产品对象的接口.
- 具体产品: 定义了具体产品实例, 实现了抽象产品接口.
- 客户: 使用抽象工厂和抽象产品的类
协作
客户程序通过抽象工厂的接口创建产品. 具体工厂根据其实现创建一系列具体产品. 客户程序通常只需要与抽象接口交互, 而不需要关心具体产品的创建过程.
通常只创建一个具体工厂的实例. 创建不同的产品对象时应该选择不同的具体工厂.
抽象工厂将产品创建的视线转移到具体工厂类中(依赖倒置原则).
应用场景
- 当一个系统需要独立于其产品的创建、构成和表现时.
- 当一个系统要灵活配置拥有多个产品系列中的一个时.
- 当需要强调一系列相关对象的家族而不是单个产品时. 一个产品族的产品应该被一起使用.
- 当提供一个产品库, 而只想显示它们的接口而不是实现时.
抽象工厂模式示例:多平台UI组件库
我们来举一个软件开发中常见的例子:为不同的操作系统(Windows 和 macOS)开发一套用户界面(UI)组件库. 这些UI组件包括按钮(Button)和复选框(Checkbox).
在这个例子中, 我们的“产品家族”就是不同操作系统下的UI组件.
1. 抽象产品 (Abstract Products)
首先, 我们需要定义抽象的产品接口, 也就是我们的UI组件的通用行为.
2. 具体产品 (Concrete Products)
接下来, 我们实现针对不同操作系统的具体产品.
3. 抽象工厂 (Abstract Factory)
现在, 我们定义抽象工厂接口, 它声明了创建一系列抽象产品的方法.
4. 具体工厂 (Concrete Factories)
然后, 我们为每个具体的产品家族实现一个具体工厂.
5. 客户端代码 (Client Code)
客户端代码只与抽象工厂和抽象产品接口打交道, 而不需要关心具体的实现细节.
这个例子如何体现抽象工厂模式?
- 产品家族 (Product Family):我们的“产品家族”是UI组件, 具体来说, 就是Windows风格的Button和Checkbox, 以及macOS风格的Button和Checkbox.
- 无需指定具体类:在
Application
类中, 我们只依赖于GUIFactory
、Button
和Checkbox
这三个抽象接口. 当我们在main
方法中传入WindowsFactory
或MacOSFactory
时,Application
对象会在不改变自身代码的情况下, 创建出对应操作系统风格的UI组件.
- 创建一系列相关对象:
WindowsFactory
总是创建WindowsButton
和WindowsCheckbox
, 它们是属于同一个“Windows UI”家族的. 同样,MacOSFactory
创建的是“macOS UI”家族的成员. 确保了同一工厂生产出来的产品是相互兼容和一致的.
通过抽象工厂模式, 我们可以在运行时轻松切换整个产品家族的实现(例如, 从Windows UI切换到macOS UI), 而不需要修改客户端代码, 大大提高了系统的灵活性和可维护性.
单例模式
有些场景中, 在内存中只希望有唯一一个对象存在, 每次想得到该类的一个引用时都得到唯一的那个对象, 无论创建多少次该类的对象都是创建同一个对象.

设计
确保一个类只有一个实例, 并提供一个全局访问点来访问该实例. 它限制了类的实例化, 并确保所有对该类的请求都返回同一个实例.
实现: 让类的构造方法变为私有方法, 只允许外部通过静态的
getInstance
方法(即类方法)来获得该类型的对象的引用. 类中有一个成员变量uniqueInstance
, 为null
则说明尚未创建, 否则就对应唯一对象. 参与者
- 单例: 负责实现单件, 提供访问单件的接口
- 客户: 使用单例
协作
客户端通过调用单例类的
getInstance
方法来获取唯一的实例. 单例类负责确保只有在第一次调用时才创建实例, 后续调用都返回已存在的实例. 应用场景
- 当类只能有一个实例, 而且客户可以从一个众所周知的访问点访问它时.
- 当这个唯一实例应该通过子类化进行扩展, 并且客户端应该无需更改代码就能使用一个扩展的实例时.
- 需要严格控制实例数量, 例如数据库连接池、日志记录器或配置管理器.
迭代器模式
希望在不用知道具体聚合结构类型的情况下, 就能够遍历某个聚合结构.
避免聚合类实现的修改影响调用类.

设计
提供一种方法顺序访问一个聚合对象中的各个元素, 而又不需要暴露该对象的内部表示. 它将遍历聚合对象的职责从聚合对象本身中分离出来.
遍历的两个主要行为: 判断是否有下一个元素, 得到下一个元素.
故设计迭代器接口
hasNext()
, next()
. 只提供访问集合的方法, 屏蔽修改集合的方法, 实现值传递.
参与者
- 迭代器: 定义访问和遍历元素的接口, 通常包括
first()
,next()
,isDone()
,currentItem()
等操作.
- 具体迭代器: 实现迭代器接口, 维护遍历过程中的当前位置.
- 聚合: 聚合类的抽象接口, 其中包含创建相应迭代器对象的方法接口
CreateIterator
.
- 具体聚合: 实现聚合接口, 返回一个具体迭代器实例, 并负责存储集合中的元素. 一种聚合方式对应一种迭代器.
协作
客户通过聚合对象创建迭代器, 然后使用迭代器来遍历聚合中的元素. 迭代器在内部维护遍历状态, 并负责访问聚合中的每个元素. 聚合对象并不暴露其内部结构, 而是通过迭代器提供对元素的访问.
具体迭代器跟踪聚合中的当前对象, 并计算出待遍历的后继对象.
应用场景
- 当需要访问一个聚合对象的内容而又不暴露其内部表示时.
- 当需要支持聚合对象的多种遍历方式时.
- 当需要为遍历不同的聚合结构提供一个统一的接口时(多态迭代).
- 当聚合和遍历的职责需要分离时.