重构获得模式 Refactoring to Patterns
面向对象设计模式是"好的面向对象设计",所谓"好的面向对象设计"指的是那些可以满足"应对变化,提高复用"的设计。
现代软件设计的特征是"需求的频繁变化"。设计模式的要点是"寻找变化点,然后在变化点处应用设计模式,从而更好地应对需求的变化"。"什么时候、什么地点应用设计模式"比"理解设计模式结构本身"更为重要。
设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发倡导的"Refactoring to Patterns"是目前普遍公认的最好的获得设计模式的方法。
软件设计的复杂性
设计模式的原则
封装变化角度对设计模式的分类
组件协作:
Template Method
Strategy
Observer / Event
单一职责:
Decorator
Bridge
对象创建:
Factory Method
Abstract Factory
Prototype
Builder
对象性能:
Singleton
Flyweight
接口隔离:
Façade
Proxy
Mediator
Adapter
状态变化:
Memento
State
数据结构:
Composite
Iterator
Chain of Responsibility
行为变化:
Command
Visitor
领域对象:
Interpreter
重构的关键技法
>静态 → 动态
>早绑定 → 晚绑定
>继承 → 组合
>编译时依赖 → 运行时依赖
>紧耦合 → 松耦合
学完设计模式,其实你会发现这八个点其实只是在不同的侧面来说明同一个问题。
"组件协作"模式 :
现代软件专业分工之后的第一个结果是"框架与应用程序的划分","组件协作"模式通过晚期绑定,来实现框架与应用间的松耦合,是二者之间协作时常用的模式。>典型模式Template,MethodStrategy,Observer/Event
Template Method
动机 (Motivation)
- 在软件构建过程中,对于某一项任务,它常常有稳定的结构,但各个子步骤却有很多改变的需求,或者由于固有原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现
- 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤化或者晚期实现需求 ?
要点总结
- Template Method模式是一种非常基础性的设计模式象系统中有着大量的应用。用最简洁的机制(接口方法)为很多应用程序框架提供了灵活的扩展点,是代码复用实现的基本实现结构。
- 除了可以灵活应对子步骤的变化外"不要调用我,让我来调用你"的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的方法可以实现,也可以没有任何实现(抽象方法),但它们设置为protected方法。
Strategy
动机 (Motivation)
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改,如果将这些算法都编码到对象中,将会使对象变得异常复杂变而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题 ?
- 定义策略接口
1. 定义策略接口
首先,我们定义一个策略接口 PaymentStrategy
,这是所有支付策略的共同接口。
java
// PaymentStrategy.java
public interface PaymentStrategy {
void pay(int amount);
}
2. 实现具体策略
接着,我们创建几个实现了 PaymentStrategy
接口的具体策略类:
java
// CreditCardPayment.java
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
private String cardHolderName;
public CreditCardPayment(String cardNumber, String cardHolderName) {
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using Credit Card.");
}
}
// PayPalPayment.java
public class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal.");
}
}
3. 创建上下文类
上下文类 ShoppingCart
使用一个策略来进行支付。策略对象在运行时可以替换,因此 ShoppingCart
可以动态地改变它的支付行为。
java
// ShoppingCart.java
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public ShoppingCart(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
4. 使用策略模式
最后,我们来看一下如何使用策略模式:
java
public class StrategyPatternDemo {
public static void main(String[] args) {
// 使用信用卡支付
PaymentStrategy creditCardPayment = new CreditCardPayment("1234 5678 9012 3456", "John Doe");
ShoppingCart cart = new ShoppingCart(creditCardPayment);
cart.checkout(100); // 输出: "100 paid using Credit Card."
// 切换到使用PayPal支付
PaymentStrategy payPalPayment = new PayPalPayment("john@example.com");
cart.setPaymentStrategy(payPalPayment);
cart.checkout(200); // 输出: "200 paid using PayPal."
}
}
5. 运行输出
100 paid using Credit Card. 200 paid using PayPal.
解释
- 策略接口(
PaymentStrategy
):定义了一个支付策略的接口。 - 具体策略(
CreditCardPayment
和PayPalPayment
):实现了策略接口,提供了不同的支付算法。 - 上下文类(
ShoppingCart
) :使用策略接口,客户端可以动态地更改上下文类的策略。
这种设计模式的优点是可以轻松地扩展新的策略而不改变现有代码,这非常符合"开闭原则"(Open/Closed Principle)。
Observer
动机(Motivation )
- 在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系"---------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密将使软件不能很好地抵御变化。
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
Observer 模式由两种对象构成:
- Subject(主题或被观察者) :主题维护了一个观察者列表,当主题的状态发生变化时,负责通知所有注册的观察者。
- Observer(观察者) :观察者订阅主题,并在主题状态变化时接收通知。
1. 定义 Observer
接口
包含一个更新方法,当主题状态变化时,主题调用这个方法来通知观察者。
java
// 观察者接口,定义了接收到更新通知的方法
interface Observer {
void update(float temperature);
}
2. 定义 Subject
接口
用于注册、移除观察者,以及在状态变化时通知观察者。
java
// 主题接口,定义了注册、移除观察者,以及通知观察者的方法
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
3. 实现具体的 Subject
类
管理观察者列表,并在状态变化时调用通知方法。
java
import java.util.ArrayList;
import java.util.List;
// 具体的主题类实现 Subject 接口
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
public WeatherStation() {
observers = new ArrayList<>(); // 初始化观察者列表
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer); // 添加新的观察者
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer); // 移除观察者
}
@Override
public void notifyObservers() {
for (Observer observer : observers) { // 通知所有观察者
observer.update(temperature);
}
}
// 更新温度,并通知所有观察者
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers(); // 通知所有观察者温度已更新
}
}
4. 实现具体的 Observer
类
实现 Observer 接口,并定义在接收到主题通知时的行为。
java
// 具体的观察者类实现 Observer 接口
class TemperatureDisplay implements Observer {
private float temperature;
@Override
public void update(float temperature) {
this.temperature = temperature;
display(); // 更新后显示温度
}
public void display() {
System.out.println("当前温度: " + temperature + "°C");
}
}
5. 使用策略模式
java
public class Main {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation(); // 创建一个天气站
// 创建两个温度显示器(观察者)
TemperatureDisplay display1 = new TemperatureDisplay();
TemperatureDisplay display2 = new TemperatureDisplay();
// 将两个观察者注册到天气站
weatherStation.registerObserver(display1);
weatherStation.registerObserver(display2);
// 更新温度,通知所有观察者
weatherStation.setTemperature(25.3f); // 输出两次温度变化通知
weatherStation.setTemperature(30.2f);
// 移除一个观察者
weatherStation.removeObserver(display1);
// 再次更新温度,通知剩余的观察者
weatherStation.setTemperature(28.4f); // 只输出一次温度变化通知
}
}
通过这种实现,WeatherStation
的状态变化会自动通知所有注册的TemperatureDisplay
,展示了Observer模式的典型用法。这个模式解耦了主题和观察者,使它们可以独立地改变而不会相互影响。
"单一职责 " 模式 :
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式
- Decorator
- Bridge
装饰模式
动机(Motivation)
- 在某些情况下我们可能会"过度地使用继承来扩展对象的功能",由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 如何使"对象功能的扩展"能够根据需要来动态地实现?同时避免"扩展功能的增多"带来的子类膨胀问题?从而使得任何"功能扩展变化"所导致的影响将为最低?
对于文件的读取,可能有不同类型的流,如果按照继承的方式,子类会爆炸
按照这个典型的子类,其中read()方法在各个子类都有。出现大量的重复代码
cpp
class CryptoBufferedFileStream : public FileStream {
public:
virtual char Read(int number) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Read(number); // 读文件流
}
virtual void Seek(int position) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Seek(position); // 定位文件流
}
virtual void Write(byte data) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Write(data); // 写文件流
}
};
按照设计原则,"优先使用组合,不是继承",进行优化
发现57行和79行,当变量都是某个类型的子类的时候,就可以将其声明成某个类型,此处就是Stream。依次类推,这里改成了,编译时一样,运行时不一样(用多态来应对变化)
cpp
class CryptoNetworkStream {
Stream* stream; // = new NetworkStream();
public:
virtual char Read(int number) {
// 额外的加密操作...
stream->Read(number); // 读网络流
}
};
进一步优化,将统一的类型,提取到一个公共类,变成装饰类
cpp
// 扩展操作
class DecoratorStream : public Stream {
protected:
Stream* stream; // 指向被装饰的流
};
class CryptoStream : public DecoratorStream {
public:
CryptoStream(Stream* stm) : DecoratorStream(stm) {
// 构造函数实现
}
};
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 &减少子类个数)。
《设计模式》
Bridge
动机 (Motivation)
- 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度乃至多个纬度的变化。
- 如何应对这种"多维度的变化"?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?
对于平台实现和业务抽象,两个不同方向,分别都有不同的实现,就会出现多个子类,分别去override playground...以及Login,结构相似,出现大量的重复代码,按照设计原则,"优先使用组合,不是继承",进行优化
然后,两个类里面,当变量都是某个类型的子类的时候,就可以将其声明成某个类型,此处就是messager。依次类推,这里改成了,编译时一样,运行时不一样(用多态来应对变化)
改到这里,你会发现这两个类已经是一样的了(编译时一样),消除同样的子类
接着,我们发现对于messager里面的方法,并不是每一个子类都会去override所有的方法。所以,我们根据业务,将messager的方法进行拆分。
然后子类根据需要进行,将继承转组合
更进一步,我们发现,messagerImpl是在每一个被使用的地方都有,这个也是重复代码,如此,将其提取到上一级。
模式定义
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
《设计模式》
假设我们有一个图形库,可以绘制不同颜色的形状。我们希望分离"形状"和"颜色"两个维度的变化,让它们可以独立扩展。这是桥接模式的理想场景。
1. 创建 Implementor
接口
java
// Implementor接口:颜色
interface Color {
void applyColor();
}
2. 创建 ConcreteImplementor
具体实现类
java
// 具体实现类:红色
class RedColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color");
}
}
// 具体实现类:绿色
class GreenColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying green color");
}
}
3. 创建 Abstraction
抽象类
java
// 抽象类:形状
abstract class Shape {
protected Color color;
// 构造函数中注入Color接口
protected Shape(Color color) {
this.color = color;
}
abstract void draw(); // 抽象方法,由具体子类实现
}
4. 创建 RefinedAbstraction
具体实现类
java
// 具体形状类:圆形
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Circle drawn. ");
color.applyColor();
}
}
// 具体形状类:方形
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Square drawn. ");
color.applyColor();
}
}
5. 测试桥接模式
java
public class BridgePatternDemo {
public static void main(String[] args) {
// 创建红色的圆形
Shape redCircle = new Circle(new RedColor());
redCircle.draw();
// 创建绿色的方形
Shape greenSquare = new Square(new GreenColor());
greenSquare.draw();
}
}
输出结果
Circle drawn. Applying red color Square drawn. Applying green color
解释
-
Shape 抽象类 :
Shape
类定义了形状的抽象结构,并持有Color
接口的引用。通过这种设计,形状和颜色是解耦的,颜色的变化不会影响形状的代码,反之亦然。 -
Color 实现接口 :
Color
接口定义了颜色的行为,具体的颜色实现(如红色、绿色)通过各自的ConcreteImplementor
类来定义。 -
扩展性 :通过这种模式,我们可以轻松添加新的形状或新的颜色,而不会影响现有的代码结构。例如,如果我们需要添加一个新的
Triangle
形状或BlueColor
,只需增加相应的实现类即可。