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

news2024/11/15 11:07:18

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 method
    void 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 cout
		splitter.addIProgress(this); // 观察者1
		splitter.addIProgress(&cn); // 观察者2

		splitter.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(){
        //==========
    }
};



//业务抽象 m

class 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.coloradmin.cn/o/1391186.html

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

相关文章

编译原理1.1习题 语言处理器

图源&#xff1a;文心一言 编译原理习题整理~&#x1f95d;&#x1f95d; 作为初学者的我&#xff0c;这些习题主要用于自我巩固。由于是自学&#xff0c;答案难免有误&#xff0c;非常欢迎各位小伙伴指正与讨论&#xff01;&#x1f44f;&#x1f4a1; 第1版&#xff1a;自…

目标检测-One Stage-YOLOv7

文章目录 前言一、YOLOv7的不同版本二、YOLOv7的网络结构二、YOLOv7的创新点三、创新点的详细解读ELAN和E-ELANBoF训练技巧计划型重参化卷积辅助训练模块标签分配Lead head guided label assignerCoarse-to-fine lead head guided label assigner 基于级联模型的复合缩放方法 总…

开发知识点-JAVA-springboot

springboot springbootConfiguration注解的底层核心原理Bean注解的底层核心原理 springboot Configuration注解的底层核心原理 https://www.bilibili.com/video/BV1rq4y1E7gK/?spm_id_from333.999.0.0&vd_sourcef21773b7086456ae21a58a6cc59023be spring.io 全家桶 24…

【Emgu CV教程】5.4、几何变换之图像翻转

今天讲解的两个函数&#xff0c;可以实现以下样式的翻转。 水平翻转&#xff1a;将图像沿Y轴(图像最左侧垂直边缘)翻转的操作。原始图像中位于左侧的内容将移动到目标图像的右侧&#xff0c;原始图像中位于右侧的内容将移动到目标图像的左侧。垂直翻转&#xff1a;将图像沿X轴…

智能小程序小部件(Widget)导航、地图、画布等组件,以及开放能力、原生组件说明

智能小程序小部件(Widget)导航、地图、画布等组件&#xff0c;以及开放能力、原生组件说明。 导航组件 navigator 页面链接&#xff0c;控制小程序的跳转。navigator 子节点的背景色应为透明色。 属性说明 属性名类型默认值必填说明urlstring是跳转地址deltanumber1否当 …

用Spark在大数据平台DataBricks轻松处理数据

Apache Spark是一个强大的开源分布式计算系统&#xff0c;专为大规模数据处理而设计。而DataBricks则提供了一个基于云的环境&#xff0c;使得在Spark上处理数据变得更加高效和便捷。本文将介绍如何在DataBricks平台上使用Spark轻松处理大数据。DataBricks是一个基于云的大数据…

8.临床预测模型验证——交叉验证/Bootstrap法

基本概念 交叉验证&#xff1a; 将一定比例的数据挑选出来作为训练集&#xff0c;将其余未选中的样本作为测试集&#xff0c;先在训练集中构建模型&#xff0c;再在测试集中做预测。 内部验证&#xff1a;手动将样本随机分为训练集和测试集&#xff0c;先在训练集中构建模型…

MySQL面试题 | 11.精选MySQL面试题

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

vue2 pdfjs-2.8.335-dist pdf文件在线预览功能

1、首先先将 pdfjs-2.8.335-dist 文件夹从网上搜索下载&#xff0c;复制到public文件夹下. 2、在components下新建组件PdfViewer.vue文件 3、在el-upload 中调用 pdf-viewer 组件 4、在el-upload 中的 on-preview方法中加上对应的src路径 internalPreview(file) { //判断需要…

【Python】箱型图和热图绘制详解和示例

箱型图&#xff08;Box Plot&#xff09;和热图&#xff08;Heatmap&#xff09;是两种常用的数据可视化工具&#xff0c;它们各自有着不同的特点和用途。在写总结和文献时对数据的表达更加直观&#xff0c;本文对这两种图像的绘制进行详解和示例。 箱型图由一组数据的最小值、…

中国1981-2023年逐年每15天8km植被指数数据集

摘要 中国1981-2023年逐年每15天8km植被指数数据集来源于GIMMS NDVI数据&#xff0c;包括了1981年7月&#xff0d;2023年12月的长时间序列逐年每15天植被指数变化&#xff0c;格式为arcgis grid格式&#xff0c;投影为WGS84&#xff0c;其时间分辨率是15天&#xff0c;空间分辨…

【机组】算术逻辑运算单元实验的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 ​ 目录 &#x1f33a; 一、 实验目的…

Java NIO (二)NIO Buffer类的重要方法

1 allocate()方法 在使用Buffer实例前&#xff0c;我们需要先获取Buffer子类的实例对象&#xff0c;并且分配内存空间。需要获取一个Buffer实例对象时&#xff0c;并不是使用子类的构造器来创建&#xff0c;而是调用子类的allocate()方法。 public class AllocateTest {static…

3.goLand基础语法

目录 概述语法for常量与变量数组切片 slice切片问题问题1问题2 Make 和 New结构体和指针结构体标签 结束 概述 从 java 转来学 go &#xff0c;在此记录&#xff0c;方便以后翻阅。 语法 for package mainimport "fmt"func main() {for i : 0; i < 3; i {fmt.…

关于java的封装

关于java的封装 我们在前面的文章中&#xff0c;了解到了类和对象的知识&#xff0c;以及做了创建对象的时候对内存的分析&#xff0c;我们本篇文章来了解一下面向对象的三大基本特征之一&#xff0c;封装&#x1f600;。 一、初识封装 封装就好比&#xff0c;我们把一些物品…

如何在 Python3 中使用变量

介绍 变量是一个重要的编程概念&#xff0c;值得掌握。它们本质上是在程序中用于表示值的符号。 本教程将涵盖一些变量基础知识&#xff0c;以及如何在您创建的 Python 3 程序中最好地使用它们。 理解变量 从技术角度来说&#xff0c;变量是将存储位置分配给与符号名称或标…

YOLOv8改进 | 主干篇 | 低照度增强网络PE-YOLO改进主干(改进暗光条件下的物体检测模型)

一、本文介绍 本文给大家带来的改进机制是低照度图像增强网络PE-YOLO中的PENet,PENet通过拉普拉斯金字塔将图像分解成多个分辨率的组件,增强图像细节和低频信息。它包括一个细节处理模块(DPM),用于通过上下文分支和边缘分支增强图像细节,以及一个低频增强滤波器(LEF),…

<软考高项备考>《论文专题 - 71 风险管理(3)》

3 过程2-识别风险 3.1 问题 4W1H过程做什么是识别单个项目风险以及整体项目风险的来源&#xff0c;并记录风险特征的过程。作用:1、记录现有的单个项目风险&#xff0c;以及整体项目风险的来源:2、汇总相关信息&#xff0c;以便项目团队能够恰当地应对已识别的风险。为什么做…

重温经典struts1之DispatchAction完成一个Action中编写多个方法接收页面的增删改查请求

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 之前我们学习的Action类中&#xff0c;编写一个方法&#xff0c;重写父类的execute方法&#xff0c;接收页面发来的请求&#xff0c;而且&#xff0c;一个action中只能…

Jetson AGX Orin安装archiconda、Pytorch

想在Jetson AGX Orin创建一个虚拟环境&#xff0c;然后安装pytorch&#xff0c;过程中遇到了很多的坑&#xff0c;这篇文章主要用于记录过程~因为Orin本身是Arm架构&#xff0c;X86架构可以装Anaconda&#xff0c;对于ARM要装archiconda。 1.安装archiconda 1.1确定操作系统架…