【重构获得模式 Refactoring to Patterns】

embedded/2024/11/8 22:01:34/

重构获得模式 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. 定义策略接口

1. 定义策略接口

首先,我们定义一个策略接口 PaymentStrategy,这是所有支付策略的共同接口。

// PaymentStrategy.java
public interface PaymentStrategy {void pay(int amount);
}
2. 实现具体策略

接着,我们创建几个实现了 PaymentStrategy 接口的具体策略类:

// 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;}@Overridepublic 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;}@Overridepublic void pay(int amount) {System.out.println(amount + " paid using PayPal.");}
}
3. 创建上下文类

上下文类 ShoppingCart 使用一个策略来进行支付。策略对象在运行时可以替换,因此 ShoppingCart 可以动态地改变它的支付行为。

// 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. 使用策略模式

最后,我们来看一下如何使用策略模式:

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.

解释
  1. 策略接口(PaymentStrategy:定义了一个支付策略的接口。
  2. 具体策略(CreditCardPaymentPayPalPayment:实现了策略接口,提供了不同的支付算法。
  3. 上下文类(ShoppingCart使用策略接口,客户端可以动态地更改上下文类的策略

这种设计模式的优点是可以轻松地扩展新的策略而不改变现有代码,这非常符合“开闭原则”(Open/Closed Principle)。

Observer

动机(Motivation )

  • 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系"---------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密将使软件不能很好地抵御变化
  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合

Observer 模式由两种对象构成:

  1. Subject(主题或被观察者):主题维护了一个观察者列表,当主题的状态发生变化时,负责通知所有注册的观察者。
  2. Observer(观察者):观察者订阅主题,并在主题状态变化时接收通知。
1. 定义 Observer 接口

包含一个更新方法,当主题状态变化时,主题调用这个方法来通知观察者。

// 观察者接口,定义了接收到更新通知的方法
interface Observer {void update(float temperature);
}

2. 定义 Subject 接口

用于注册、移除观察者,以及在状态变化时通知观察者。

// 主题接口,定义了注册、移除观察者,以及通知观察者的方法
interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}
3. 实现具体的 Subject

管理观察者列表,并在状态变化时调用通知方法。

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<>();  // 初始化观察者列表}@Overridepublic void registerObserver(Observer observer) {observers.add(observer);  // 添加新的观察者}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);  // 移除观察者}@Overridepublic void notifyObservers() {for (Observer observer : observers) {  // 通知所有观察者observer.update(temperature);}}// 更新温度,并通知所有观察者public void setTemperature(float temperature) {this.temperature = temperature;notifyObservers();  // 通知所有观察者温度已更新}
}
4. 实现具体的 Observer

实现 Observer 接口,并定义在接收到主题通知时的行为

// 具体的观察者类实现 Observer 接口
class TemperatureDisplay implements Observer {private float temperature;@Overridepublic void update(float temperature) {this.temperature = temperature;display();  // 更新后显示温度}public void display() {System.out.println("当前温度: " + temperature + "°C");}
}

5. 使用策略模式
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()方法在各个子类都有。出现大量的重复代码

​​​​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。依次类推,这里改成了,编译时一样,运行时不一样(用多态来应对变化)

class CryptoNetworkStream {Stream* stream; // = new NetworkStream();public:virtual char Read(int number) {// 额外的加密操作...stream->Read(number); // 读网络流}
};

进一步优化,将统一的类型,提取到一个公共类,变成装饰类

// 扩展操作
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 接口
// Implementor接口:颜色
interface Color {void applyColor();
}
2. 创建 ConcreteImplementor 具体实现类
// 具体实现类:红色
class RedColor implements Color {@Overridepublic void applyColor() {System.out.println("Applying red color");}
}// 具体实现类:绿色
class GreenColor implements Color {@Overridepublic void applyColor() {System.out.println("Applying green color");}
}
3. 创建 Abstraction 抽象类
// 抽象类:形状
abstract class Shape {protected Color color;// 构造函数中注入Color接口protected Shape(Color color) {this.color = color;}abstract void draw(); // 抽象方法,由具体子类实现
}
4. 创建 RefinedAbstraction 具体实现类
// 具体形状类:圆形
class Circle extends Shape {public Circle(Color color) {super(color);}@Overridevoid draw() {System.out.print("Circle drawn. ");color.applyColor();}
}// 具体形状类:方形
class Square extends Shape {public Square(Color color) {super(color);}@Overridevoid draw() {System.out.print("Square drawn. ");color.applyColor();}
}
5. 测试桥接模式
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

解释
  1. Shape 抽象类Shape 类定义了形状的抽象结构,并持有 Color 接口的引用。通过这种设计,形状和颜色是解耦的,颜色的变化不会影响形状的代码,反之亦然。

  2. Color 实现接口Color 接口定义了颜色的行为,具体的颜色实现(如红色、绿色)通过各自的 ConcreteImplementor 类来定义。

  3. 扩展性:通过这种模式,我们可以轻松添加新的形状或新的颜色,而不会影响现有的代码结构。例如,如果我们需要添加一个新的 Triangle 形状或 BlueColor,只需增加相应的实现类即可。


http://www.ppmy.cn/embedded/108457.html

相关文章

Redis典型应用 - 分布式锁

文章目录 目录 文章目录 1. 什么是分布式锁 2. 分布式锁的基本实现 3. 引入过期时间 4. 引入校验Id 5. 引入 watch dog(看门狗) 6. 引入redlock算法 工作原理 Redlock的优点&#xff1a; 总结 1. 什么是分布式锁 在一个分布式系统中,也可能会出现多个节点访问一个共…

linux下进行lvm分区及扩容

目录 LVM存储管理介绍 lvm磁盘扩容有两种方式 创建lvm磁盘 1. 首先先加入第一块儿新的磁盘 2. 对新磁盘 /dev/sdb 进行分区 通过LVM命令创建新卷 1. 创建物理卷 2.创建卷组 并将物理卷加入其中 3. 创建逻辑卷并分配大小 4.格式化刚刚创建的硬盘 5. 挂载磁盘 扩容lvm…

如何打造高校实验室教学管理系统?Java SpringBoot助力,MySQL存储优化,2025届必备设计指南

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

Computer Exercise

每日一练 单选题 在计算机机箱前面板接口插针上&#xff08;     C   &#xff09;表示复位开关。 A.SPK    B.PWRLED    C.RESET    D.HDDLED每台PC机最多可接&#xff08;     B   &#xff09;块IDE硬盘。 A.2    B.4    C.6    D.8&#xff08;    …

欧拉系统安装 NVIDIA 显卡驱动

1、安装显卡驱动编译工具 yum install gcc make kernel-devel 2、安装显卡驱动依赖包 yum install vulkan-loader 可选安装项&#xff0c;不安装该系统包时会出现以下警告提示&#xff0c;但不影响安装和使用。 3、安装 NVIDIA GPU 驱动 生产环境建议选择 .run 格式的驱动…

【Linux系统学习】2.Linux基础命令

Linux基础命令 Linux的目录结构 Linux命令入门 目录切换相关命令(cd/pwd) 相对路径、绝对路径和特殊路径符 创建目录命令(mkdir) 文件操作命令part1(touch、cat、more&#xff09; 文件操作命令part2(cp、mv、rm&#xff09; 查找命令(which、find&#xff09; grep、wc和管道符…

基于深度学习的动态对抗策略

基于深度学习的动态对抗策略是为了应对不断变化的对抗环境而提出的一类方法&#xff0c;这些策略能够动态地调整和优化模型的防御机制&#xff0c;以提高深度学习模型在各种对抗攻击下的鲁棒性和安全性。这类策略结合了对抗样本生成、模型防御和自适应学习的技术&#xff0c;形…

OneHotEncoder一个不太合理的地方

OneHotEncoder&#xff0c;在Xtrain上fit&#xff0c;在Xtest上transform 如果遇到某个值出现在Xtest&#xff0c;而没有在Xtrain出现过时&#xff0c;会抛出如下错误&#xff1a; OneHotEncoder Found unknown categories [xxx] in column xx during transform OneHotEncoder …