C++设计模式(李建忠)笔记1

C++设计模式(李建忠)

本文是学习笔记,如有侵权,请联系删除。

参考链接

Youtube: C++设计模式

Gtihub源码与PPT:https://github.com/ZachL1/Bilibili-plus

豆瓣: 设计模式–可复用面向对象软件的基础

文章目录

  • C++设计模式(李建忠)
    • 2 面向对象设计原则
      • 里氏替换原则例子
      • 接口隔离原则例子
    • 3 模板方法(Template Method)
      • Motivation
      • 模板方法定义
      • 模板方法结构
    • 4 策略模式(Strategy)
      • Motivation
      • 策略模式的定义
      • 策略模式结构
    • 5 观察者模式(Observer)
      • Motivation
      • 观察者模式定义
      • 观察者模式的结构
    • 6 装饰模式(Decorator)
      • Motivation
      • 装饰模式定义
      • 装饰模式的结构
    • 7 桥接模式(Bridge)
      • 桥接模式的定义
      • 桥接模式结构
    • 后记

主要介绍SOLID原则,Template Method, Strategy, Observer, Decorator, Bridge设计模式

2 面向对象设计原则

SOLID 是一组面向对象设计原则,这些原则旨在帮助设计者创建更加灵活、可维护、可扩展且易于理解的软件系统。这五个原则分别是:

  1. 单一职责原则(Single Responsibility Principle - SRP):

    • 一个类应该只有一个引起变化的原因,即一个类应该只有一个责任。这意味着一个类应该专注于一种类型的功能,而不是承担多个不同的责任。
  2. 开放封闭原则(Open/Closed Principle - OCP):

    • 软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展而不是修改现有代码来实现。
  3. 里氏替换原则(Liskov Substitution Principle - LSP):

    • 子类应该能够替代父类并保持程序的正确性。也就是说,对于基类的任何使用,都应该能够在不知道是基类还是子类的情况下替代为子类。
  4. 接口隔离原则(Interface Segregation Principle - ISP):

    • 不应该强迫客户端依赖于它们不使用的接口。一个类不应该强制实现它用不到的接口。应该根据实际需要定义更小、更具体的接口。
  5. 依赖倒置原则(Dependency Inversion Principle - DIP):

    • 高层模块不应该依赖于低层模块,而是应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这一原则促使使用接口或抽象类来实现松耦合。

这些原则共同构成了 SOLID 原则,它们的目标是提供一种指导性的设计思想,帮助开发者创建易于维护、可扩展且具有良好设计的软件系统。在实际应用中,遵循这些原则有助于提高代码质量、降低维护成本,并使系统更容易适应变化。

里氏替换原则例子

里氏替换原则(Liskov Substitution Principle - LSP)是 SOLID 原则中的一条,它强调派生类(子类)应该能够替代其基类(父类)而不导致程序出错。以下是一个简单的 C++ 示例:

#include <iostream>// 基类:图形
class Shape {
public:virtual void draw() const {std::cout << "Drawing a shape." << std::endl;}
};// 派生类1:矩形
class Rectangle : public Shape {
public:void draw() const override {std::cout << "Drawing a rectangle." << std::endl;}void calculateArea() const {std::cout << "Calculating the area of a rectangle." << std::endl;}
};// 派生类2:圆形
class Circle : public Shape {
public:void draw() const override {std::cout << "Drawing a circle." << std::endl;}void calculateRadius() const {std::cout << "Calculating the radius of a circle." << std::endl;}
};// 使用图形的函数
void drawShape(const Shape& shape) {shape.draw();
}int main() {Rectangle rectangle;Circle circle;// 使用派生类替代基类drawShape(rectangle);drawShape(circle);return 0;
}

在这个例子中,Shape 是一个基类,它有一个虚拟函数 draw,表示绘制图形。然后有两个派生类,RectangleCircle,它们分别重写了 draw 方法。在 main 函数中,我们使用 drawShape 函数来画不同的图形,这里传入的是 RectangleCircle 对象,它们是基类 Shape 的派生类。

这符合里氏替换原则,因为我们可以使用 RectangleCircle 替代其基类 Shape,而不会导致程序出错。虽然 RectangleCircle 有额外的方法,但只要我们使用基类的方法,就能够在不知道具体子类的情况下使用它们。

接口隔离原则例子

具体关于接口隔离原则ISP的例子

在 C++ 中,通过接口类和多重继承,可以很好地体现接口隔离原则(ISP)。下面是一个简单的 C++ 设计例子:

#include <iostream>// 接口1:动物接口
class IAnimal {
public:virtual void eat() = 0;virtual void sleep() = 0;
};// 接口2:飞行动物接口
class IFlyable {
public:virtual void fly() = 0;
};// 具体实现类:狗
class Dog : public IAnimal {
public:void eat() override {std::cout << "Dog is eating." << std::endl;}void sleep() override {std::cout << "Dog is sleeping." << std::endl;}
};// 具体实现类:鹰
class Eagle : public IAnimal, public IFlyable {
public:void eat() override {std::cout << "Eagle is eating." << std::endl;}void sleep() override {std::cout << "Eagle is sleeping." << std::endl;}void fly() override {std::cout << "Eagle is flying." << std::endl;}
};int main() {Dog dog;Eagle eagle;// 使用动物接口IAnimal* animal1 = &dog;IAnimal* animal2 = &eagle;animal1->eat();animal1->sleep();animal2->eat();animal2->sleep();// 使用飞行动物接口IFlyable* flyer = &eagle;flyer->fly();return 0;
}

在这个例子中,IAnimal 是一个表示动物的接口,包含了 eatsleep 两个方法。IFlyable 是一个表示飞行动物的接口,包含了 fly 方法。然后,Dog 类实现了 IAnimal 接口,而 Eagle 类实现了 IAnimalIFlyable 两个接口。

这样设计的好处在于,任何依赖于动物接口的代码都只需要关心 eatsleep 方法,而不必关心飞行的细节。同样,依赖于飞行动物接口的代码也只需要关心 fly 方法。这符合接口隔离原则,使得每个接口都小而专用,不强迫客户端依赖于不需要的接口。

3 模板方法(Template Method)

Motivation

在软件构件过程中,对于某项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架和应用之间的关系)而无法和任务的整体结构同时实现。

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

适用性

一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。

举例

库函数Library,定义好流程,但是其中某个具体操作留到调用者来实现

class Library {
public:// template methodvoid Run(){Step1();if (Step2()) { // 支持变化 ==》虚函数的多态调用      Step3(); }for (int i = 0; i < 4; i++){Step4();  // 支持变化 ==》虚函数的多态调用 }Step5();}virtual ~Library(){ }protected:void Step1() { //稳定的部分//.....}void Step3() {//稳定//.....}void Step5() { //稳定//.....}virtual bool Step2() = 0;// 变化的部分virtual void Step4() =0; // 变化的部分
};

应用:使用库函数Library

class Application : public Library {  // 子类实现虚函数
protected:virtual bool Step2(){//... }virtual void Step4() {//... }
};int main()
{Library* lib = new Application();lib->Run();delete lib;
}

模板方法定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法结构

在这里插入图片描述

上图中斜着的类是抽象类,斜着的方法是抽象方法。

AbstractClass(抽象类,如Library)

——定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法的各步骤。

——实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作。

ConcreateClass(具体类,如Application)

—— 实现原语操作以完成算法中与特定子类相关的步骤。

4 策略模式(Strategy)

Motivation

在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都写在对象中,将会使对象变得异常复杂;而且有时候支持不频繁使用的算法也是一个性能负担。

如何在运行时根据需要透明地更改对象的算法,将对象和算法解耦?

举例:不同国家税率的计算,新的需求是新增一个国家税率的计算。

不好的做法:增加一个if else

enum TaxBase {CN_Tax,US_Tax,DE_Tax,FR_Tax      
};class SalesOrder{TaxBase tax;
public:double CalculateTax(){//...if (tax == CN_Tax){//CN***********}else if (tax == US_Tax){//US***********}else if (tax == DE_Tax){//DE***********}else if (tax == FR_Tax){ //...}//....}};

好的做法:定义一个新类(扩展),实现虚函数,以此来实现不同的税率计算。

遵循开闭原则:对扩展开放,对修改关闭。


class TaxStrategy {
public:virtual double Calculate(const Context& context)=0;virtual ~TaxStrategy(){}
};class CNTax : public TaxStrategy {
public:virtual double Calculate(const Context& context){//***********}
};class USTax : public TaxStrategy {
public:virtual double Calculate(const Context& context){//***********}
};class DETax : public TaxStrategy {
public:virtual double Calculate(const Context& context){//***********}
};// 扩展,遵循开闭原则:对扩展开放,对修改关闭。
class FRTax : public TaxStrategy {
public:virtual double Calculate(const Context& context){//.........}
};class SalesOrder {
private:TaxStrategy* strategy;public:SalesOrder(StrategyFactory* strategyFactory) {this->strategy = strategyFactory->NewStrategy();}~SalesOrder(){delete this->strategy;}public double CalculateTax() {//...Context context();double val = strategy->Calculate(context); //多态//...}};

策略模式的定义

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。

策略模式结构

在这里插入图片描述

Strategy(策略,如TaxStrategy)

— 定义所有支持的算法的公共接口。Context使用这个接口来调用某 ConcreteStrategy定义的算法。

ConcreteStrategy(具体策略,如CNTax,USTax等)

— 以Strategy接口实现某具体算法。

Context(上下文,如SalesOrder)

— 用一个ConcreteStrategy对象来配置。

— 维护一个对Strategy对象的引用。

— 可定义一个接口来让Strategy访问它的数据。

协作

•Strategy和Context相互作用以实现选定的算法。当算法被调用时 , Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。

• Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context;这样, 客户仅与Context交互。通常有一系列的ConcreteStrategy类可供客户从中选择。

含有许多条件语句的代码通常意味着需要使用Strategy模式

5 观察者模式(Observer)

Motivation

在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象的状态发生改变,所有的依赖对象都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系,从而实现软件体系结构的松耦合。

观察者模式定义

定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

这一模式中的关键对象是目标(subject)和观察者(observer)。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变 , 所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。

这种交互也称为发布-订阅(puplish-subscribe)。目标是通知的发布者。它发出通知时并不需知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知。

代码场景:分件切分器,把大文件切分为小文件,现在想要一个进度条显示分割进度

MainForm调用FileSplitter进行文件切分,里面显示进度条

class MainForm : public Form
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){string filePath = txtFilePath->getText();int number = atoi(txtFileNumber->getText().c_str());FileSplitter splitter(filePath, number, progressBar);splitter.split();}
};

FileSplitter的伪代码如下:违反依赖倒置原则中抽象不应该依赖细节。

class FileSplitter
{string m_filePath;int m_fileNumber;ProgressBar* m_progressBar; // 依赖于进度条细节,带来实现细节变更的困扰public:FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :m_filePath(filePath), m_fileNumber(fileNumber),m_progressBar(progressBar) {}void split(){//1.读取大文件//2.分批次向小文件写入for (int i = 0; i < m_fileNumber; i++){//...float progressValue = m_fileNumber;progressValue = (i + 1) / progressValue;  // 更新进度条m_progressBar->setValue(progressValue);}}
};

违反依赖倒置原则

依赖倒置原则(Dependency Inversion Principle,DIP)是面向对象设计原则中的一条,它是SOLID原则中的其中一项。依赖倒置原则强调的是高层模块不应该依赖于低层模块,两者都应该依赖于抽象;而且,抽象不应该依赖于具体细节,具体细节应该依赖于抽象。

利用观察者模式修正,提供抽象基类,为希望表示进度的类定义了一个接口。

FileSplitter和MainForm

// IProgress 是一个抽象基类,为希望表示进度的类定义了一个接口。
class IProgress { // Observer
public:virtual void DoProgress(float value)=0;virtual ~IProgress(){}
};class FileSplitter
{string m_filePath;int m_fileNumber;List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者public:FileSplitter(const string& filePath, int fileNumber) :m_filePath(filePath), m_fileNumber(fileNumber) {}void split(){//1.读取大文件//2.分批次向小文件写入for (int i = 0; i < m_fileNumber; i++){//...// 进度展示float progressValue = m_fileNumber;progressValue = (i + 1) / progressValue;onProgress(progressValue);  // 发送通知}}// **********不管添加多少个观察者,以下的结构稳定不变void addIProgress(IProgress* iprogress){m_iprogressList.add(iprogress);}void removeIProgress(IProgress* iprogress){m_iprogressList.remove(iprogress);}protected:virtual void onProgress(float value) {List<IProgress*>::iterator itor = m_iprogressList.begin();while (itor != m_iprogressList.end())(*itor)->DoProgress(value); // 更新进度,多态,可以是bar,可以是console cout等等itor++;}}
};
// **********不管添加多少个观察者,以上的结构稳定不变// 继承Iprogress,需要实现DoProgress,从而自定义进度显示
class MainForm : public Form, public IProgress
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){string filePath = txtFilePath->getText();int number = atoi(txtFileNumber->getText().c_str());ConsoleNotifier cn;FileSplitter splitter(filePath, number);// 两个进度展示:一个是setValue为prograssBar,另一个是console coutsplitter.addIProgress(this); // 观察者1splitter.addIProgress(&cn); // 观察者2splitter.split();splitter.removeIProgress(this);}virtual void DoProgress(float value) {progressBar->setValue(value);}
};// 定义一个新类,这是一个新的观察者,新增进度显示的种类
class ConsoleNotifier : public IProgress {
public:virtual void DoProgress(float value){cout << ".";}
};

观察者模式的结构

在这里插入图片描述

参与者

• Subject(目标)

— 目标知道它的观察者。可以有任意多个观察者观察同一个目标。

— 提供注册和删除观察者对象的接口。

• Observer(观察者,上面代码中的IProgress)

— 为那些在目标发生改变时需获得通知的对象定义一个更新接口。

• ConcreteSubject(具体目标)

— 将有关状态存入各ConcreteObserver对象。

— 当它的状态发生改变时, 向它的各个观察者发出通知。

• ConcreteObserver(具体观察者)

— 维护一个指向ConcreteSubject对象的引用。

— 存储有关状态,这些状态应与目标的状态保持一致。

— 实现Observer的更新接口以使自身状态与目标的状态保持一致。

使用面向对象的抽象,Observer模式使得我们可以独立地改变目标和观察者,从而使两者之间的依赖关系达到松耦合。

目标发送通知时,无需指定观察者,通知会自动传播。

观察者自己决定是否需要订阅通知,目标对象对此一无所知。

Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式中的一个重要组成部分。

6 装饰模式(Decorator)

Motivation

在某些情况下,我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

如何使“对象功能的扩展”能够根据需要来动态地实现,同时避免“扩展功能的增多”带来的子类膨胀问题?想要使得任何“功能扩展变化”所导致的影响降到最低。

代码示例

考虑不同的流stream,有文件流,内存流,网络流等,需要对不同的流进行加密,缓冲。

在这里插入图片描述

第一次设计:每一个子类中都额外添加加密的操作,有大量的代码重复,bad smell

//业务操作
class Stream{
publicvirtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}
};//主体类
class FileStream: public Stream{
public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{
public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{
public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//扩展操作
class CryptoFileStream :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);//写文件流//额外的加密操作...}
};class CryptoNetworkStream : :public NetworkStream{
public:virtual char Read(int number){//额外的加密操作...NetworkStream::Read(number);//读网络流}virtual void Seek(int position){//额外的加密操作...NetworkStream::Seek(position);//定位网络流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...NetworkStream::Write(data);//写网络流//额外的加密操作...}
};class CryptoMemoryStream : public MemoryStream{
public:virtual char Read(int number){//额外的加密操作...MemoryStream::Read(number);//读内存流}virtual void Seek(int position){//额外的加密操作...MemoryStream::Seek(position);//定位内存流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...MemoryStream::Write(data);//写内存流//额外的加密操作...}
};class BufferedFileStream : public FileStream{//...
};class BufferedNetworkStream : public NetworkStream{//...
};class BufferedMemoryStream : public MemoryStream{//...
}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);//写文件流//额外的加密操作...//额外的缓冲操作...}
};void Process(){//编译时装配CryptoFileStream *fs1 = new CryptoFileStream();BufferedFileStream *fs2 = new BufferedFileStream();CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();}

第二个版本:把CryptoStream继承自Stream,直接对Stream进行加密,运行时绑定它的子类(FileStream,NetworkStream,MemoryStream)。这样可以省去冗余。

//业务操作
class Stream{publicvirtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}
};//主体类
class FileStream: public Stream{
public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{
public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{
public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//扩展操作class CryptoStream: public Stream {Stream* stream;// 多种变化// new FileStream()// new NetworkStream()// new MemoryStream()public:CryptoStream(Stream* stm):stream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream->Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream->Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public Stream{Stream* stream;//...public:BufferedStream(Stream* stm):stream(stm){}//...
};void Process(){//运行时装配FileStream* s1=new FileStream();CryptoStream* s2=new CryptoStream(s1);BufferedStream* s3=new BufferedStream(s1);BufferedStream* s4=new BufferedStream(s2);
}

第三个版本:把CryptoStream和BufferedStream中的Stream* stream;提取出来,放到DecoratorStream中。

在这里插入图片描述

//业务操作
class Stream{publicvirtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}
};//主体类
class FileStream: public Stream{
public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{
public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{
public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//扩展操作class DecoratorStream: public Stream{
protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};class CryptoStream: public DecoratorStream {public:CryptoStream(Stream* stm):DecoratorStream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream->Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream->Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public DecoratorStream{Stream* stream;//...public:BufferedStream(Stream* stm):DecoratorStream(stm){}//...
};void Process(){//运行时装配FileStream* s1=new FileStream();CryptoStream* s2=new CryptoStream(s1);BufferedStream* s3=new BufferedStream(s1);BufferedStream* s4=new BufferedStream(s2);}

装饰模式定义

动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。

装饰模式的结构

在这里插入图片描述

参与者

Component

——定义一个对象接口,可以给这些对象动态地添加职责。

ConcreteComponent

——定义一个对象,可以给这个对象添加一些职责。

Decorator

——维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

ConcreteDecorator

——向组件添加职责。

通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的灵活性差和多子类衍生问题。

Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系。

Decorator模式的要点在于解决主体类在多个方向上扩展功能的问题。

7 桥接模式(Bridge)

Motivation

由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个变化维度。

如何应对这种多维度的变化?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?

代码示例:有通信模块Messager,现在要支持PC平台和mobile平台的设计,分别需要有lite版本和perfect版本。

不好的代码

class Messager{
public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual ~Messager(){}
};//平台实现class PCMessagerBase : public Messager{
public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}
};class MobileMessagerBase : public Messager{
public:virtual void PlaySound(){//==========}virtual void DrawShape(){//==========}virtual void WriteText(){//==========}virtual void Connect(){//==========}
};//业务抽象class PCMessagerLite : public PCMessagerBase {
public:virtual void Login(string username, string password){PCMessagerBase::Connect();//........}virtual void SendMessage(string message){PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image){PCMessagerBase::DrawShape();//........}
};class PCMessagerPerfect : public PCMessagerBase {
public:virtual void Login(string username, string password){PCMessagerBase::PlaySound();//********PCMessagerBase::Connect();//........}virtual void SendMessage(string message){PCMessagerBase::PlaySound();//********PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image){PCMessagerBase::PlaySound();//********PCMessagerBase::DrawShape();//........}
};class MobileMessagerLite : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::DrawShape();//........}
};class MobileMessagerPerfect : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::PlaySound();//********MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::PlaySound();//********MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::PlaySound();//********MobileMessagerBase::DrawShape();//........}
};void Process(){//编译时装配Messager *m =new MobileMessagerPerfect();
}

好的代码:将业务功能Messager和平台实现MessagerImp分离

class Messager{
protected:MessagerImp* messagerImp;//...
public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual ~Messager(){}
};class MessagerImp{
public:virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual ~MessagerImp(){}
};//平台实现 n
class PCMessagerImp : public MessagerImp{
public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}
};class MobileMessagerImp : public MessagerImp{
public:virtual void PlaySound(){//==========}virtual void DrawShape(){//==========}virtual void WriteText(){//==========}virtual void Connect(){//==========}
};//业务抽象 mclass MessagerLite :public Messager {public:virtual void Login(string username, string password){messagerImp->Connect();//........}virtual void SendMessage(string message){messagerImp->WriteText();//........}virtual void SendPicture(Image image){messagerImp->DrawShape();//........}
};class MessagerPerfect  :public Messager {public:virtual void Login(string username, string password){messagerImp->PlaySound();//********messagerImp->Connect();//........}virtual void SendMessage(string message){messagerImp->PlaySound();//********messagerImp->WriteText();//........}virtual void SendPicture(Image image){messagerImp->PlaySound();//********messagerImp->DrawShape();//........}
};void Process(){//运行时装配MessagerImp* mImp=new PCMessagerImp();Messager *m =new Messager(mImp);
}

桥接模式的定义

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式结构

在这里插入图片描述

Abstraction (Messager)
— 定义抽象类的接口。
— 维护一个指向Implementor类型对象的指针。

RefinedAbstraction (MessagerLite,MessagerPerfect)
— 扩充由Abstraction定义的接口。

Implementor (MessagerImp)
— 定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。

ConcreteImplementor (PCMessagerImp, MobileMessagerImp)
— 实现Implementor接口并定义它的具体实现。

Bridge模式有以下一些优点:

  1. 分离接口及其实现部分:一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。
  2. 提高可扩充性:你可以独立地对Abstraction和Implementor层次结构进行扩充。
  3. 实现细节对客户透明:你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的引用计数机制(如果有的话)。

后记

截至2024年1月16日,花费1天时间学习前5个设计模式,后面继续学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629240.shtml

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

相关文章

探索单元测试和 E2E 测试:提升软件质量的关键步骤(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

修复系统和修复常见安卓问题的 10 个应用

我们都喜欢我们的 Android 智能手机&#xff0c;对吧&#xff1f;有很多值得喜欢的地方。 Android 手机易于使用且通常无故障&#xff0c;但毕竟它只是一台机器&#xff0c;偶尔也会出现问题。面对现实吧&#xff0c;我们的智能手机并不完美。用户经常遇到的一些常见 Android …

select子句简单查询

Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 目录 数据查询 起别名 连接 ​编辑 去重 ​编辑 另外补充几个不常用的命令 如果要进行查询,那么需要使用数据操纵语言&#xff08;Data Manipulation Language&#xff0c;DML&am…

React Store及store持久化的使用

1.安装 npm insatll react-redux npm install reduxjs/toolkit npm install redux-persist2. 使用React Toolkit创建counterStore并配置持久化 store/modules/counterStore.ts&#xff1a; import { createSlice } from reduxjs/toolkit// 定义状态类型 interface Action {…

linux-部署Samba文件共享服务

linux-部署Samba文件共享服务 1、使用命令安装samba服务和samba客户端 dnf install samba samba-client # 或者 yum install samba samba-client2、配置文件的设置(可提前备份smb.conf) vim /etc/samba/smb.conf [global]workgroup SAMBAsecurity userpassdb backend tdbsam…

在EasyBoss ERP上查Shopee产品表现,数据更全、处理更高效!

在运营Shopee本土店的过程中&#xff0c;卖家需要及时了解产品数据来进行产品的调整和优化。不过&#xff0c;调整和优化需要基于数据&#xff0c;而非仅凭直觉。 但是&#xff0c;如何全面地查看产品表现数据、快速处理表现不佳的产品&#xff0c;成为诸多卖家头痛的问题&…

java的运行机制以及整体流程

背景&#xff1a;学习了这么多年的Java&#xff0c;把自己的理解写成JVM系列&#xff0c;以便于后面的温习&#xff0c;以及帮助更多的java开发人员。 开篇先梳理下&#xff0c;我们之前写的第一个Hello World&#xff01;&#xff0c;当我们运行出来后&#xff0c;在控制台打印…

史上最全的数据科学与艺术

1.背景介绍 数据分析是一种将数据转化为价值的艺术和科学。它涉及到大量的数学、统计、编程、数据库、机器学习等多个领域的知识。数据分析的目的是从数据中提取有用的信息&#xff0c;以便做出明智的决策。 数据分析的艺术体现在数据分析师需要具备丰富的经验和洞察力&#…

『C++成长记』内存管理

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、C/C内存分布 二、内存管理方式 &#x1f4d2;2.1C语言内存管理方式 &#x…

【OpenAI】自定义GPTs应用(GPT助手应用)及外部API接口请求

11月10日&#xff0c;OpenAI正式宣布向所有ChatGPT Plus用户开放GPTs功能 简而言之&#xff1a;GPT应用市场(简称GPTs, 全称GPT Store) Ps&#xff1a; 上图为首次进入时的页面&#xff0c;第一部分是自己创建的GPTs应用&#xff0c;下面是公开可以使用的GPTs应用 一、创建GPTs…

振弦采集仪在桥梁结构健康监测中的应用探索

振弦采集仪在桥梁结构健康监测中的应用探索 振弦采集仪是一种用于测量结构振动特性的仪器&#xff0c;通过采集结构上的振弦信号&#xff0c;可以评估结构的健康状况。在桥梁结构健康监测中&#xff0c;振弦采集仪可以发挥重要的作用。 首先&#xff0c;振弦采集仪能够实时监测…

上传文件:413 Request Entity Too Large Maximum upload size exceeded 解决方案

文章目录 前言一、原因分析二、解决方案1.nginx配置文件2.application.yml配置文件 总结 前言 在上传文件时&#xff0c;如果没有做一些配置的话&#xff0c;会导致上传失败&#xff1a;413 Request Entity Too Large 或者 Maximum upload size exceeded。 提示&#xff1a;以…

geemap学习笔记049:下载Landsat数据时遇到的一个问题

前言 最近在下载Landsat 8 地面反射率数据&#xff08;Surface Reflectance&#xff09;时&#xff0c;遇到了一个问题&#xff0c;无论是使用geemap.ee_export_image_to_drive() 函数还是geemap.download_ee_image() 函数下载的数据&#xff0c;易康都打不开&#xff0c;显示…

【Java】面向对象 OOP

文章目录 面向对象OOP概述一、对象内存相关二、类的成员之一&#xff1a;成员变量&#xff08;Field&#xff09;2.1 如何声明成员变量2.2 成员变量 与 局部变量 三、类的成员之一&#xff1a;成员方法&#xff08;Method&#xff09;3.1 方法调用内存分析3.2 方法的重载3.3 可…

NAS入门(学习笔记)

文章目录 AutoMLNAS初期NAS当前NAS框架One-Shot NAS权重共享策略 Zero-Shot NASZen-NASNASWOTEPENAS 参考资料 AutoML 深度学习使特征学习自动化 AutoML 使深度学习自动化 自动化机器学习 (automated machine learning) 是一种自动化的数据驱动方法, 并做出一系列决策。 按…

第2、3次作业

题目1&#xff1a; 基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! 题目2&#xff1a; 给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c;基于[www.openlab.com/student](http://www.openlab.c…

RIP【新华三与华为区别】

【介绍】 rip分为rip 1 与 rip 2 &#xff0c;rip 2 是对 rip 1 的一种升级&#xff0c;rip 2 可以进行认证等功能 【命令】 新华三&#xff1a; [HC3-R1] rip #启用rip [HC3-R1-rip] version 2 #告知rip 版本号 [HC3-R1-rip] network 192.168.1.0 #宣告其网段 [HC3-R1-rip] …

【python】py-spy 实时显示python进程内的线程堆栈CPU消耗 python CPU消耗分析

安装 pip install py-spy AI调用源码&#xff0c;红色调用时&#xff0c;python进程CPU 100% 启动程序&#xff0c;输入问题&#xff0c;观察CPU top sudo .local/bin/py-spy top --pid 7150 可以看到&#xff0c;此时与显卡交互占用了绝大部分CPU&#xff0c;有点死循环检测…

AI大模型预先学习笔记一:transformer和fine tune技术介绍

一、商业观点&#xff1a;企业借助大模型获得业务增长可能 二、底层原理&#xff1a;transformer 1&#xff09;备注 ①下面每个步骤都是自回归的过程&#xff08;aotu-regressive&#xff09;&#xff1a;已输出内容的每个字作为输入&#xff0c;一起生成下一个字 ②合起来就…

全自动网页制作系统流星全自动网页生成系统重构版输入网页信息即可制作

源码优点: 所有模板经过精心审核与修改&#xff0c;完美兼容小屏手机大屏手机&#xff0c;以及各种平板端、电脑端和360浏览器、谷歌浏览器、火狐浏览器等等各大浏览器显示。 免费制作 为用户使用方便考虑&#xff0c;全自动网页制作系统无需繁琐的注册与登入&#xff0c;直接…