【重构获得模式 Refactoring to Patterns】

重构获得模式 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.rhkb.cn/news/418281.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka 实战演练:创建、配置与测试 Kafka全面教程

文章目录 1.配置文件2.消费者1.注解方式2.KafkaConsumer 3.依赖1.注解依赖2.KafkaConsumer依赖 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都…

5G前传-介绍

1. 引用 知识分享系列一&#xff1a;5G基础知识-CSDN博客 5G前传的最新进展-CSDN博客 灰光和彩光_通信行业5G招标系列点评之二&#xff1a;一文读懂5G前传-光纤、灰光、彩光、CWDM、LWDM、MWDM...-CSDN博客 术语&#xff1a; 英文缩写描述‌BBU&#xff1a;Building Baseba…

review——C++中的右值引用

目录 前言 一、什么是左值、什么是右值 二、右值引用 1.右值引用与右值引用的一些性质 2.解释一下左值引用与右值应用于程序员之间的关系 3.右值引用与移动语义 4.右值引用右值后变成左值的必要性与完美转发 1.右值引用引用右值后变为左值属性的必要性 2.完美转发 Ⅰ…

【docker】docker 镜像仓库的管理

Docker 仓库&#xff08; Docker Registry &#xff09; 是用于存储和分发 Docker 镜像的集中式存储库。 它就像是一个大型的镜像仓库&#xff0c;开发者可以将自己创建的 Docker 镜像推送到仓库中&#xff0c;也可以从仓库中拉取所需的镜像。 Docker 仓库可以分为公共仓…

Java获取小程序码示例(三种小程序码)

首先我们可以看到官方文档上是有三种码的 获取小程序码 这里特别要注意的是第一种和第三种是有数量限制的&#xff0c;所以大家生成的时候记得保存&#xff0c;也不要一直瞎生成 还有一点要注意的是第一种和第二种是太阳码 第三种是方形码 好了直接上代码 这里要注意&#xff…

Golang | Leetcode Golang题解之第391题完美矩形

题目&#xff1a; 题解&#xff1a; func isRectangleCover(rectangles [][]int) bool {type point struct{ x, y int }area, minX, minY, maxX, maxY : 0, rectangles[0][0], rectangles[0][1], rectangles[0][2], rectangles[0][3]cnt : map[point]int{}for _, rect : range…

HTML生日蛋糕

目录 写在前面 完整代码 代码分析 系列文章 写在最后 写在前面 HTML实现的生日蛋糕来喽&#xff0c;小编亲测&#xff0c;发给好友可以直接打开哦。在代码的第183行可以写下对朋友的祝福&#xff0c;快拿去送给你的好朋友吧&#xff01; 完整代码 <!DOCTYPE html>…

C++ 上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法

C 上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法

【LeetCode】15.三数之和

题目要求 解题思路 这道题我们可以使用暴力解法来解决&#xff0c;时间复杂度为O&#xff08;N^3&#xff09;。但是会超时&#xff0c;因此我们需要对暴力解法进行优化&#xff0c;而这道题我们使用双指针来进行优化&#xff0c;即依次固定一个数&#xff0c;在接下来的区间中…

vue3整合antv x6实现图编辑器快速入门

安装&#xff1a; npm install antv/x6 --save如果使用 umd 包&#xff0c;可以使用下面三个 CDN 中的任何一个&#xff0c;默认使用 X6 的最新版&#xff1a; https://unpkg.com/antv/x6/dist/index.jshttps://cdn.jsdelivr.net/npm/antv/x6/dist/index.jshttps://cdnjs.clo…

mysql树形结构返回是否叶子节点

我们界面上展示树形结构的时候往往会用到懒加载&#xff0c;做懒加载需要知道哪个节点是叶子节点&#xff0c;这样叶子节点就不需要继续往下加载了&#xff0c;这种需求可以通过sql实现 先来看下表结构 方式一,通过sql语句直接获取leaf 什么是叶子节点&#xff1f;就是没有哪…

Alternative account/备选科目代码配置说明 【1:1和国家科目配置运营科目】

业务场景&#xff1a; 有个关于mapping的问题&#xff1a; 1. 如果在GL account的master data里面&#xff0c;之前已经做好了alternative account的mapping, 那我要改成另外的local account,这样 A 会不会影响本地子公司的报表&#xff1f; 2. 如果GL account和alternativ…

Redis 缓存深度解析:穿透、击穿、雪崩与预热的全面解读

Redis 缓存深度解析&#xff1a;穿透、击穿、雪崩与预热的全面解读 一 . 什么是缓存 ?二 . 使用 Redis 作为缓存三 . 缓存的更新策略3.1 定期生成3.2 实时生成 四 . 缓存预热、缓存穿透、缓存雪崩、缓存击穿4.1 缓存预热4.2 缓存穿透4.3 缓存雪崩4.4 缓存击穿 Hello , 大家好 …

WebAPI (一)DOM树、DOM对象,操作元素样式(style className,classList)。表单元素属性。自定义属性。间歇函数定时器

文章目录 Web API基本认知一、 变量声明二、 DOM1. DOM 树2. DOM对象3. 获取DOM对象(1)、选择匹配的第一个元素(2)、选择匹配多个元素 三、 操作元素1. 操作元素内容2. 操作元素属性(1)、常用属性&#xff08;href之类的&#xff09;(2)、通过style属性操作CSS(3)、通过类名(cl…

wireshark安装及抓包新手使用教程

Wireshark是非常流行的网络封包分析软件&#xff0c;可以截取各种网络数据包&#xff0c;并显示数据包详细信息。常用于开发测试过程各种问题定位。本文主要内容包括&#xff1a; 1、Wireshark软件下载和安装以及Wireshark主界面介绍。 2、WireShark简单抓包示例。通过该例子学…

Ollama—87.4k star 的开源大模型服务框架!!

这一年来&#xff0c;AI 发展的越来越快&#xff0c;大模型使用的门槛也越来越低&#xff0c;每个人都可以在自己的本地运行大模型。今天再给大家介绍一个最厉害的开源大模型服务框架——ollama。 项目介绍 Ollama 是一个开源的大语言模型&#xff08;LLM&#xff09;服务工具…

IDM 工具下载 地图高程数据

巧用IDM工具 快捷下载ASTER GDEM v3高程数据 ASTER GDEM v3是NASA推出的30米高清DEM,覆盖了几乎全部的地球陆地。那么,在NASA官网怎么下载ASTER GDEM v3的地形高程数据呢? 首先,你需要注册一个nasa的账号 注册网址: https://urs.earthdata.nasa.gov/users/new 注册方式和国…

基于人工智能的聊天情感分析系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 情感分析是一种自然语言处理任务&#xff0c;旨在识别文本中的情感&#xff0c;如“积极”、“消极”或“中立”。在聊天应用中&#…

iPhone手机清理软件:照片清理功能全解析

在数字化生活中&#xff0c;智能手机成为我们记录生活点滴的主要工具&#xff0c;尤其是iPhone&#xff0c;以其卓越的相机功能备受用户青睐。然而&#xff0c;成千上万的照片迅速堆积&#xff0c;不仅占用了大量存储空间&#xff0c;还使得设备运行缓慢。在众多解决方案中&…

多环境jdk安装,CentOS,统信UOS,Ubuntu,KylinOS,windows

文章目录 1.CentOS1.1yum安装1.2压缩包安装 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都是在得 1.CentOS 1.1yum安装 yum install -y jav…