【重构获得模式 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.mzph.cn/pingmian/53300.shtml

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

相关文章

大语言模型LLM权重4bit向量量化(Vector Quantization)/查找表量化基本原理

参考 https://apple.github.io/coremltools/docs-guides/source/opt-palettization-overview.html https://apple.github.io/coremltools/docs-guides/source/opt-palettization-algos.html Apple Intelligence Foundation Language Models 苹果向量量化&#xff1a; DKM:…

在VMware虚拟机中编译文件的时候报错:找不到头文件ft2build.h

以下是报错内容&#xff0c;提示说找不到头文件ft2build.h freetype_show_font.c:12:10: fatal error: ft2build.h: No such file or directory #include <ft2build.h> ^~~~~~~~~~~~ compilation terminated. 在编译之前已经交叉编译了freetype&#xff0c;…

MQ-2烟雾传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 三、程序设计 main.c文件 mq2.h文件 mq2.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 MQ-2气体传感器是一种常用的气体传感器&#xff0c;用于检测空气中的烟雾浓度。工作原理是基于半导…

Java项目: 基于SpringBoot+mybatis+maven+mysql图书馆管理系统(含源码+数据库+任务书+答辩PPT+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismavenmysql图书馆管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操…

网络安全应急响应技术原理与应用

网络安全应急响应概述 概念 为应对网络安全事件&#xff0c;相关人员或组织机构对网络安全事件进行监测、预警、分析、响应和恢复等工作 网络安全应急响应组织建立与工作机制 网络安全应急响应预案内容与类型 常见网络安全应急事件场景与处理流程 应急演练&#xff1a;对假定…

一台手机一个ip地址吗?手机ip地址泄露了怎么办

在数字化时代&#xff0c;‌手机作为我们日常生活中不可或缺的一部分&#xff0c;‌其网络安全性也日益受到关注。‌其中一个常见的疑问便是&#xff1a;‌“一台手机是否对应一个固定的IP地址&#xff1f;‌”实际上&#xff0c;‌情况并非如此简单。‌本文首先解答这一问题&a…

Web3社交新经济,与 SOEX 实现无缝交易的高级安全性

出于充分的理由&#xff0c;安全性是交易中至关重要的考虑因素。每个人都应该确保自己的资金在交易时是安全的。由于 &#xff33;&#xff2f;&#xff25;&#xff38; 充当您与交易所的最佳连接&#xff0c;因此必须强调的是&#xff0c;该系统不会引发任何安全问题。 &a…

模型 涌现思想

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。整体产生新特性&#xff0c;超越部分之和。 1 涌现思想的应用 1.1 蚁群算法中的涌现思想 蚁群算法&#xff08;Ant Colony Optimization, ACO&#xff09;是一种模拟蚂蚁觅食行为的计算模型&#xf…

QT项目实战之音乐播放器2.0版本

该版本相较于1.0版本最主要的不同在于连接数据库实现类似于歌曲收藏和取消收藏的功能。 详细情况看我的这篇文章http://t.csdnimg.cn/WS5s8。 效果展示 VSMyMusicShow2.0 define.h UseMySQL.h musicInfo.h VSMyMusicPlayer.h

PMP–一、二、三模–分类–14.敏捷–技巧–帮助团队交付价值的执行实践迭代和增量如何帮助交付工作产品

文章目录 技巧一模14.敏捷--实践--帮助团队交付价值的执行实践--持续集成--在不同层面测试、验收测试驱动开发 (ATDD) 、测试驱动开发和行为驱动开发、刺探 。90、 [单选] 敏捷项目的第一次迭代即将开始。发起人召集团队、Scrum主管、产品负责人和其他项目干系人参加启动会议。…

木舟0基础学习Java的第二十六天(JavaWeb)

设置响应头 resp.setHeader("key","nihao");//推荐使用英文 中文会乱码 案例&#xff1a;模拟登录 jdbc.properties driverClasscom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/test?verifyServerCertificatefalse&useSSLfalse nameroot p…

第十三届山东省ICPC

vp链接&#xff1a;https://codeforces.com/gym/104417 A. Orders 根据题意模拟&#xff0c;分别按照 a&#xff0c;b 排序&#xff0c;排序后再判断该订单是否能完成。 #include <bits/stdc.h> using namespace std;#define int long longconst int N 105; int n, k…

pikachu文件包含漏洞靶场

本地文件包含 1、先随意进行提交 可以得出是GET传参 可以在filename参数进行文件包含 2、准备一个2.jpg文件 内容为<?php phpinfo();?> 3、上传2.jpg文件 4、访问文件保存的路径uploads/2.jpg 5、将我们上传的文件包含进来 使用../返回上级目录 来进行包含木马文件 …

备战秋招60天算法挑战,Day33

题目链接&#xff1a; https://leetcode.cn/problems/longest-increasing-subsequence/ 视频题解&#xff1a; https://www.bilibili.com/video/BV1RRvheFEog/ LeetCode 300. 最长递增子序列 题目描述 给你一个整数数组nums &#xff0c;找到其中最长严格递增子序列的长度。 …

自幂数判断c++

题目描述 样例输入 3 152 111 153样例输出 F F T 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; long long m,a; int main(){cin>>m;for(int i1;i<m;i){cin>>a;long long ta,n[10],cc0,s0;while(t!0){//求位数与拆位n[cc]t%10;tt/…

多线程篇(并发相关类- 原子操作类)(持续更新迭代)

目录 前言 一、原子变量操作类&#xff08;AtomicLong为例&#xff09; 1. 前言 2. 实例 二、JDK 8新增的原子操作类LongAdder 三、LongAccumulator类原理探究 前言 JUC包提供了一系列的原子性操作类&#xff0c;这些类都是使用非阻塞算法CAS实现的&#xff0c;相比使用…

dubbo 服务消费原理分析之应用级服务发现

文章目录 前言一、MigrationRuleListener1、迁移状态模型2、Provider 端升级3、Consumer 端升级4、服务消费选址5、MigrationRuleListener.onRefer6、MigrationRuleHandler.doMigrate6、MigrationRuleHandler.refreshInvoker7、MigrationClusterInvoker.migrateToApplicationFi…

初识命名空间

1.创建两个命名空间 ip netns add host1 ip netns add host2 2. 查看命名空间 ip netns ls 3 、 创建veth ip -netns host1 link add veth0 type veth peer name host1-peer 4、 查看命名空间接口 ip -netns host1 address 5、 把host1-peer移动到host2命名空间 ip -ne…

ctfshow-nodejs

什么是nodejs Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境。可以说nodejs是一个运行环境&#xff0c;或者说是一个 JS 语言解释器 Nodejs 是基于 Chrome 的 V8 引擎开发的一个 C 程序&#xff0c;目的是提供一个 JS 的运行环境。最早 Nodejs 主要是安装在服务器…

SAP B1 基础实操 - 用户定义字段 (UDF)

目录 一、功能介绍 1. 使用场景 2. 操作逻辑 3. 常用定义部分 3.1 主数据 3.2 营销单据 4. 字段设置表单 4.1 字段基础信息 4.2 不同类详细设置 4.3 默认值/必填 二、案例 1 要求 2 操作步骤 一、功能介绍 1. 使用场景 在实施过程中&#xff0c;经常会碰见用户需…