桥接模式是一种结构型设计模式,它是将抽象部分和实现部分隔离,通过组合关系将抽象部分和实现部分解耦,使它们可以独立变化。
因此,桥接模式可以很好的处理两个或两个以上维度的变化。
举一个例子说明:
假设我们现在要设计一款游戏,初步需求是有两个角色(Casa 和 Titan)和两把武器:Pistol(手枪)和 Anchor(船锚)。其中Casa是一个射手,她配备的武器是Pistol,Titan是一个战士,配备的武器是Anchor。
我们如果不用桥接模式的话,可能会这样实现:
// 卡莎角色
class CasaCharacter
{
public:
CasaCharacter()
{
this->weapon_ = "Pistol";
this->damage_ = 10;
}
// 攻击
void Attack()
{
std::cout << weapon_ << "开始攻击,伤害为" << damage_ << std::endl;
}
private:
// 武器
std::string weapon_;
// 伤害
int damage_;
};
// 泰坦角色
class TitanCharacter
{
public:
TitanCharacter()
{
this->weapon_ = "Anchor";
this->damage_ = 20;
}
// 攻击
void Attack()
{
std::cout << weapon_ << "开始攻击,伤害为" << damage_ << std::endl;
}
private:
// 武器
std::string weapon_;
// 伤害
int damage_;
};
测试:
void TestBridge()
{
std::shared_ptr<CasaCharacter> casa = std::make_shared<CasaCharacter>();
casa->Attack();
std::shared_ptr<TitanCharacter> titan = std::make_shared<TitanCharacter>();
titan->Attack();
}
int main()
{
// 策略模式用法
// TestStrategy();
// TestObserver();
// TestDecorator();
TestBridge();
system("pause");
return 0;
}
输出:
Pistol开始攻击,伤害为10
Anchor开始攻击,伤害为20
我们实现了两个类,分别是Casa角色类和Titan角色类,在构造函数里设置角色的武器和伤害,实现了攻击方法。
假如现在我们的需求变了,Casa不仅可以配备手枪,还可以配备船锚、Titan也不仅可以配备船锚,还可以配备手枪。我们现在要实现这个需求,只能再加两个类,在构造函数里设置武器和伤害。这样的设计可能会导致类的爆炸式增长,同时也不利于扩展和维护。
并且这样的实现导致我们的测试代码是一种编译时装配,我们希望我们的程序尽可能是运行时装配,这样可以写成“活”的。
接下来使用桥接模式实现:
class IWeapon
{
protected:
// 武器名字
std::string weapon_name_;
// 武器伤害
int damage_;
public:
virtual ~IWeapon() {}
virtual void Attack() = 0;
std::string GetWeaponName()
{
return weapon_name_;
}
};
// 抽象角色类
class ICharacter
{
protected:
std::shared_ptr<IWeapon> weapon_;
public:
virtual ~ICharacter(){}
virtual void Fight() = 0;
void SelectWeapon(std::shared_ptr<IWeapon> _weapon)
{
this->weapon_ = _weapon;
}
};
// Casa角色
class Casa
: public ICharacter
{
public:
virtual void Fight() override
{
std::cout << weapon_->GetWeaponName() << "开始攻击" << std::endl;
weapon_->Attack();
}
};
// Titan角色
class Titan
: public ICharacter
{
public:
virtual void Fight() override
{
std::cout << weapon_->GetWeaponName() << "开始攻击" << std::endl;
weapon_->Attack();
}
};
// Pistol武器
class Pistol
: public IWeapon
{
public:
Pistol()
{
weapon_name_ = "手枪";
damage_ = 10;
hit_rate_ = 0.1;
}
virtual void Attack() override
{
std::cout << weapon_name_ << "造成伤害" << damage_ * (1 + hit_rate_) << std::endl;
}
private:
// 暴击率
double hit_rate_;
};
// Anchor武器
class Anchor
: public IWeapon
{
public:
Anchor()
{
weapon_name_ = "船锚";
damage_ = 20;
}
virtual void Attack() override
{
std::cout << weapon_name_ << "造成伤害" << damage_ << std::endl;
}
};
void TestBridge()
{
// Casa角色
std::shared_ptr<ICharacter> casa = std::make_shared<Casa>();
// Titan角色
std::shared_ptr<ICharacter> titan = std::make_shared<Titan>();
// Pistol武器
std::shared_ptr<IWeapon> pistol = std::make_shared<Pistol>();
// Anchor武器
std::shared_ptr<IWeapon> anchor = std::make_shared<Anchor>();
// 给角色装备武器
casa->SelectWeapon(pistol);
titan->SelectWeapon(anchor);
// 角色开始打架
casa->Fight();
titan->Fight();
}
int main()
{
// 策略模式用法
// TestStrategy();
// TestObserver();
// TestDecorator();
TestBridge();
system("pause");
return 0;
}
输出:
手枪开始攻击
手枪造成伤害11
船锚开始攻击
船锚造成伤害20
使用桥接模式实现了抽象角色类和抽象武器类,具体角色Casa类、具体角色Titan类、具体武器Pistol类、具体武器Anchor类。
测试代码我们通过SelectWeapon给角色装备武器,实现了运行时装配。这样我们就可以随意的给角色装备武器。
我们可以发现通过桥接模式,实现了松耦合,并且角色和武器之间的关系是组合关系,我们可以随意的组合角色和武器,任意一方发生变化都不会对另一方造成影响,也就是两个或两个以上维度的独立变化。
桥模式的主要思想是通过将一个可变的抽象部分和一个可变的实现部分分离开来,从而使得它们可以独立地变化,相互之间不会造成影响。因此,桥模式可以很好的处理两个或两个以上变化的维度,它可以写出更好的代码,也便于代码的维护和扩展。
可以对比以上代码来理解这段话。
要点总结
装饰模式和桥接模式的区别:
装饰模式:动态地给一个对象增加额外的功能,让这个对象变得更加复杂。
桥接模式:将抽象部分和实现部分隔离开,使它们都可以独立地变化。
桥接模式是用合成的方式实现多个对象之间的“组合”,而装饰模式是用继承的方式实现多个对象之间的“聚合”。
桥接模式的耦合度更低,多维度的东西可以拥有自己的属性和方法,即角色和武器两个维度有自己的属性和方法。装饰模式使用继承,必然拥有相同的属性和方法。
装饰模式示例
二者对比大家就会发现:
桥接模式是为了实现多个没有关联的维度的东西自由组合,这里的没有关联是指它们拥有各自的属性和方法,没有相同点。装饰模式使用了继承必然是两个种类具有相同的属性和方法,它不是为了实现两个维度之间的自由组合,而是为了实现对对象之间的一层又一层包装,调用方法时,每一层包装递归的调用上一层的包装。