人生如梦,一樽还酹江月
概念
建立两套相互独立的类继承体系,然后,通过让一个类引用另一个类来连结起来, 避免类组合的爆炸。
例如,当前有各种图形:圆形,三角形,长方形,它们有公共的基类class ShapeBase, 公共的虚函数接口为drawShape,用于绘制出不同的形状。
现在加一个需求,需要能绘制出不同颜色的形状,颜色要求有红色,绿色,蓝色,而且将来可能增加颜色。
如果用继承的方式来实现的话,一种方式是,有一个公共的ShapeBase基类,然后派生出各种图形,然后各个图形又派生出自己的各种颜色的图形,如下图所示,缺点是
特别容易爆炸。
于是桥模式登场了:
将图形和颜色分成两个独立的继承体系,基类分别为Class ShapeBase,以及 Class ColorBase, 在ShapeBase中增加一个ColorBase*指针,使用该指针
去调用color体系的方法,从而解耦。
void Circle::DrawShape(){
paintCircle(); //绘制圆形
mColorImplement->PaintShape();//上色
}
工程应用例子
场景:
赛车有多种动力源,Motor, Engine, HydraulicCynlinder, 构成一个继承体系;动力源的作用是接收车手的油门信号,并结合自己的内部算法逻辑,输出车的动力扭矩。
对于内部算法,有多种:SampleTorqueAlgorithm仅仅根据油门产生扭矩, GasRpmTorqueAlgorithm根据油门和转速产生扭矩, 构成一个继承体系。
这些算法可以被各种动力源使用,即动力源和算法构成组合。
可以使用桥模式,在每个动力源class的内部增加一个指针,指向其使用的算法示例,从而解耦。
代码结构:
有一些技巧,例如readConfig函数用于从外部配置文件中读入具体参数。请细看代码。
算法类的体系:
#pragma once
#include <iostream>
#include <memory>
using namespace std;
class TorqueAlgorithmBase
{
public:
TorqueAlgorithmBase() {}
virtual ~TorqueAlgorithmBase() {}
virtual float solve(float gas, float rpm) = 0;
virtual void readUserConfig(int fileId) = 0;//可以用于读入用户的配置文件内容,例如读入算法使用到的可配置数据。
};
class SampleTorqueAlgorithm :public TorqueAlgorithmBase
{
public:
SampleTorqueAlgorithm() {}
virtual ~SampleTorqueAlgorithm() {}
virtual float solve(float gas, float rpm) override{
return gas * mMaxOutput;
}
virtual void readUserConfig(int fileId) override{
//读取针对该算法的用户提供的数据,例如mMaxOutput
}
private:
float mMaxOutput = 300.0f;
};
class GasRpmTorqueAlgorithm :public TorqueAlgorithmBase
{
public:
GasRpmTorqueAlgorithm() {}
virtual ~GasRpmTorqueAlgorithm() {}
virtual float solve(float gas, float rpm) override {
//根据gas和rpm计算输出的torque,例如,如下一个算法
float res = gas * mMaxOutput + rpm * gas*0.001;
return res;
}
virtual void readUserConfig(int fileId) override {
//读取针对该算法的用户提供的数据,例如mMaxOutput
}
private:
float mMaxOutput = 300.0f;
};
动力源的类体系:
class EnergySourceBase {
public:
EnergySourceBase() {
//默认使用SampleTorqueAlgorithm
mTorqueAlgo = std::make_shared<SampleTorqueAlgorithm>();//基类指针指向子类obj
}
virtual ~EnergySourceBase() {}
virtual float generateTorque(float gas, float rpm) = 0;
virtual void readUserConfig(int fileId)=0;//读入用户的配置,并根据该配置决定使用哪种TorqueAlgo.实际中入参可以是一个配置文件
protected:
shared_ptr<TorqueAlgorithmBase> mTorqueAlgo;//基类指针
};
class Motor :public EnergySourceBase {
public:
Motor(){}
virtual ~Motor(){}
virtual float generateTorque(float gas, float rpm) override {
return mTorqueAlgo->solve(gas, rpm);
}
virtual void readUserConfig(int fileId) override {
//从fileId文件中读取出指示使用哪种torqueAlgo的信息,例如存到int order中。
int order = 0;
if (order == 0) {
mTorqueAlgo.reset(new SampleTorqueAlgorithm());
}
else {
mTorqueAlgo.reset(new GasRpmTorqueAlgorithm());
} // -- 以上代码在各个子类中有重复,可以整合到父类中。例如,可以在父类中加一个geneRateAlgo函数。
//--从file中提取Motor的相关参数,例如重量。
//weight=...
//让mTorqueAlgo读取其需要的参数。
mTorqueAlgo->readUserConfig(fileId);
}
private:
float weight;
};
class Engine :public EnergySourceBase {
public:
Engine() {}
virtual ~Engine() {}
virtual float generateTorque(float gas, float rpm) override {
return mTorqueAlgo->solve(gas, rpm);
}
virtual void readUserConfig(int fileId) override {
//从fileId文件中读取出指示使用哪种torqueAlgo的信息,例如存到int order中。
int order = 0;
if (order == 0) {
mTorqueAlgo.reset(new SampleTorqueAlgorithm());
}
else {
mTorqueAlgo.reset(new GasRpmTorqueAlgorithm());
}
//--从file中提取Engine的相关参数,例如重量。
//weight=...
//让mTorqueAlgo读取其需要的参数。
mTorqueAlgo->readUserConfig(fileId);
}
private:
float weight;
};
class HydraulicCynlinder :public EnergySourceBase {
public:
HydraulicCynlinder() {}
virtual ~HydraulicCynlinder() {}
virtual float generateTorque(float gas, float rpm) override {
return mTorqueAlgo->solve(gas, rpm);
}
virtual void readUserConfig(int fileId) override {
//从fileId文件中读取出指示使用哪种torqueAlgo的信息,例如存到int order中。
int order = 0;
if (order == 0) {
mTorqueAlgo.reset(new SampleTorqueAlgorithm());
}
else {
mTorqueAlgo.reset(new GasRpmTorqueAlgorithm());
}
//--从file中提取HydraulicCynlinder的相关参数,例如重量,mLiquidDensity。
//weight=...; mLiquidDensity=;
//让mTorqueAlgo读取其需要的参数。
mTorqueAlgo->readUserConfig(fileId);
}
private:
float weight;
float mLiquidDensity;
};
Test it:
void main() {
Engine obj;//客户需要一个Engine
int fileId = 3;
obj.readUserConfig(fileId);//fileId代表一个文件,里面存储着engine的具体参数, 使用哪种torque ALgorithm信息,以及该种algorithm的具体参数。
float gas = 0.3;
float rpm = 3000;
float torque = obj.generateTorque(gas, rpm);
}
Ref:
https://refactoring.guru/design-patterns/bridge
https://refactoring.guru/design-patterns/bridge/cpp/example
https://www.cnblogs.com/WindSun/p/10260547.html
最是人间留不住,朱颜辞镜花辞月。