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

2024-01-17 15:04

本文主要是介绍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个设计模式,后面继续学习。

这篇关于C++设计模式(李建忠)笔记1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/616417

相关文章

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名