【重构获得模式 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;
    }
    
    @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 可以动态地改变它的支付行为。

// 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<>();  // 初始化观察者列表
    }

    @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 接口,并定义在接收到主题通知时的行为

// 具体的观察者类实现 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. 使用策略模式
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 {
    @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 抽象类
// 抽象类:形状
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);
    }

    @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. 测试桥接模式
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.mfbz.cn/a/872583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Opencv中的直方图(1)计算反向投影直方图函数calcBackProject()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算直方图的反向投影。 cv::calcBackProject 函数计算直方图的反向投影。也就是说&#xff0c;类似于 calcHist&#xff0c;在每个位置 (x, y)…

12道经典性能测试人员面试题

1.性能测试包含了哪些软件测试&#xff08;至少举出3种&#xff09;&#xff1f; 参考答案&#xff1a;负载测试、压力测试、容量测试。 负载测试&#xff08;Load Testing&#xff09;&#xff1a;负载测试是一种主要为了测试软件系统是否达到需求文档设计的目标&#xff0c…

Spring MVC 八股文

目录 重点 SpringMVC的工作原理 Spring MVC 拦截器 Spring MVC 的拦截器和 Filter 过滤器有什么差别&#xff1f; 基础 什么是SpringMVC SpringMVC的优点 Spring MVC的核心组件 Spring MVC的常用注解由有哪些 Controller 注解有什么用 重点 SpringMVC的工作原理 1、客…

【舍入,取整,取小数,取余数丨Excel 函数】

数学函数 1、Round函数 Roundup函数 Rounddown函数 取整&#xff1a;(Int /Trunc)其他舍入函数&#xff1a; 2、Mod函数用Mod函数提取小数用Mod函数 分奇偶通过身份证号码判断性别 1、Round函数 Roundup函数 Rounddown函数 Round(数字&#xff0c;保留几位小数)&#xff08;四…

Word快速重复上一步操作的三种高效方法

在日常工作、学习和生活中&#xff0c;我们经常需要执行一系列重复性的操作。这些操作可能简单如复制粘贴、调整图片大小&#xff0c;也可能复杂如编辑文档、处理数据等。为了提高效率&#xff0c;掌握快速重复上一步操作的方法显得尤为重要。本文将介绍三种高效的方法&#xf…

Carla自动驾驶仿真十:Carlaviz三维可视化平台搭建

文章目录 前言一、环境准备1、docker安装2、websocket-client安装3、carlaviz代码下载 二、carlaviz使用1、打开carla客户端2、输入启动命令3、进入carlaviz4、修改manual_control.py脚本5、运行manual_control.py脚本6、运行carlaviz官方脚本&#xff08;推荐&#xff09; 前言…

【2024最新】Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够了!

前言 本文罗列了了python零基础入门到精通的详细教程&#xff0c;内容均以知识目录的形式展开。 第一章&#xff1a;python基础之markdown Typora软件下载Typora基本使用Typora补充说明编程与编程语言计算机的本质计算机五大组成部分计算机三大核心硬件操作系统 第二章&…

【计算机网络】浏览器输入访问某网址时,后台流程是什么

在访问网址时&#xff0c;后台的具体流程可以因不同的网站、服务器和应用架构而异。 实际过程中可能还涉及更多的细节和步骤&#xff0c;如缓存处理、重定向、负载均衡等。 此外&#xff0c;不同的网站和应用架构可能会有不同的实现方式和优化策略。 部分特定网站或应用&#x…

RK3588开发板利用udp发送和接收数据

目录 1 send.cpp 2 receive.cpp 3 编译运行 4 测试 1 send.cpp #include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //…

利用数据质量工具提高业务效率 | 数据治理应用篇

您的数据库是否井然有序&#xff1f; 在当今社会&#xff0c;企业管理者们愈发开始重视数据的重要性。数据不仅能推动战略决策&#xff0c;还能影响业务成果、推动创新&#xff0c;并为企业提供竞争优势。然而&#xff0c;随着数据量的增加&#xff0c;确保数据的准确性、一致…

【C++ 面试 - 新特性】每日 3 题(三)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

使用pytorch深度学习框架搭建神经网络

简介 现在主流有两个框架pytorch和TensorFlow,本文主要介绍pytorch PyTorch&#xff1a;由 Facebook 的人工智能研究小组开发和维护。PyTorch 以其动态计算图&#xff08;Dynamic Computational Graph&#xff09;和易用性著称&#xff0c;非常适合研究人员和开发者进行实验和…

小皮面板webman ai项目本地启动教程

1.前置条件 下载小皮面板 下载后&#xff0c;双击安装&#xff0c;一路next&#xff08;下一步&#xff09;&#xff0c;无需更改配置。 2.安装必须软件 在小皮面板的软件管理页&#xff0c;安装编号①②③④下面四个软件。 3.启动本地服务 进入到小皮面板的首页&#x…

深入学习电路基础:从理论到实践

引言 电路是电子学的核心&#xff0c;也是现代科技的基石。从简单的灯泡开关到复杂的计算机处理器&#xff0c;电路在各类电子设备中都起到了至关重要的作用。深入学习电路知识不仅有助于理解电子设备的工作原理&#xff0c;还能够为实际设计和开发电子产品打下坚实的基础。 …

ARP协议(原理,特点,报文格式,具体过程),ARP缓存(有效时间,为什么),ARP欺骗(定向断网,成为中间人),RARP简单介绍

目录 ARP协议 引入 介绍 原理 arp请求/响应 特点 报文格式 硬件类型 协议类型 硬件/协议地址长度 op(操作码) 过程 发送请求并处理 返回响应并处理 总结 arp缓存 介绍 arp表项的有效时间 解释 arp欺骗 介绍 定向断网 基于arp的成为中间人的方式 多向…

有什么简单方便的cad编辑器?2024快速进行cad编辑的软件合集

有什么简单方便的cad编辑器&#xff1f;2024快速进行cad编辑的软件合集 在建筑、工程、设计等领域&#xff0c;CAD&#xff08;计算机辅助设计&#xff09;软件是必不可少的工具。然而&#xff0c;面对复杂的CAD文件&#xff0c;有时我们只需要简单的编辑功能&#xff0c;而不…

工厂模式-小记

工厂模式-小记 工厂模式简单工厂模式场景复现抽象产品接口具体产品工厂类测试方法 工厂方法模式工厂方法模式场景描述抽象工厂接口具体工厂抽象产品具体产品客户端测试 抽象工厂模式场景描述抽象工厂具体工厂抽象产品具体产品客户端测试 三者的对比 工厂模式 工厂模式提供了一…

2024 年高教社杯全国大学生数学建模竞赛题目【A/B/C/D/E题】完整思路

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ A题是数模类赛事很常见的物理类赛题&#xff0c;需要学习不少相关知识。此题涉及对一个动态系统的建模&#xff0c;模拟…

策略模式的小记

策略模式 策略模式支付系统【场景再现】硬编码完成不同的支付策略使用策略模式&#xff0c;对比不同&#xff08;1&#xff09;支付策略接口&#xff08;2&#xff09;具体的支付策略类&#xff08;3&#xff09;上下文&#xff08;4&#xff09;客户端&#xff08;5&#xff0…