一.简介
今天来学习一下23种设计模式中的桥接模式。我们再设计系统时,如果某个类存在两个独立变化的维度,一种方案是使用多层继承,如果第一个维度有A个分支,第二个维度有B个分支的话,那么总共我们需要A*B个子类才能实现,这将是一个非常庞大的继承树。如果第一个维度增加了一个分支,那么我们需要再增加B个子类,扩展也变得很麻烦。而第二种方式就是我们今天要学习的桥接模式,通过桥接模式,我们可以将两个维度分离出来,使两者独立扩展,成为两个独立的继承树,并在最上层的抽象层中建立一个关联,这个关联就类似于连接两个独立结构的桥,所以我们称之为桥接模式。
下面我们看一下桥接模式的定义以及桥接模式的UML类图:
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
简单解释一下桥接模式的UML类图,整体的继承树被拆分成两组继承树,以Abstraction为根的继承树和以Implementor为根的继承树。Abstraction通过一个抽象的接口调用Implementor,我们可以将Implementor的子类对象注入到Abstraction子类对象中,通过聚合的方式达到想要的效果。
二.桥接模式的例子
我们通过一个例子来看一下桥接模式的应用。我们现在要设计一个小游戏,包含几个兵种,男刀客,女刀客,男枪手,女枪手。怎么样设计这个人物的结构体系呢?我们分别看一下多层继承的情况和桥接模式的情况。
1.多层继承的情况
// Design Pattern.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//人物基类
class Character
{
public:
virtual void Attack() = 0;
};
//男人物
class MaleCharacter : public Character
{
public:
virtual void Attack() = 0;
};
//女人物
class FemaleCharacter : public Character
{
public:
virtual void Attack() = 0;
};
//男刀客
class PhysicalMaleChar : public MaleCharacter
{
public:
virtual void Attack()
{
cout << "男角色: 物理攻击" << endl;
}
};
//女刀客
class PhysicalFemaleChar : public FemaleCharacter
{
public:
virtual void Attack()
{
cout << "女角色: 物理攻击" << endl;
}
};
//男法师
class MagicMaleChar : public MaleCharacter
{
public:
virtual void Attack()
{
cout << "男角色: 法术攻击" << endl;
}
};
//女法师
class MagicFemalChar : public FemaleCharacter
{
public:
virtual void Attack()
{
cout << "女角色: 法术攻击" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Character* character = new MagicFemalChar();
character->Attack();
system("pause");
return 0;
}
结果:
女角色: 法术攻击
请按任意键继续. . .
恩,虽然实现了想要的功能,不过如果我们的职业增加了几个,那么我们就要给每个新增的职业创建两个类,很麻烦。还是看看使用桥接模式的情况吧。
2.桥接模式的情况
// Design Pattern.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//属性基类,相当于Implement类
class Attribute
{
public:
virtual void Attack() = 0;
};
//人物基类,相当于UML图中的Abstract类
class Character
{
protected:
//保存一个属性的指针
Attribute* m_Attribute;
public:
//设置属性(职业)
void SetAttribute(Attribute* attribute)
{
m_Attribute = attribute;
}
//攻击接口
virtual void Attack() = 0;
};
//男人物
class MaleCharacter : public Character
{
public:
virtual void Attack() override
{
cout << "男角色: ";
m_Attribute->Attack();
}
};
//女人物
class FemaleCharacter : public Character
{
public:
virtual void Attack() override
{
cout << "女角色: ";
m_Attribute->Attack();
}
};
//物理属性
class PhysicalAttribute : public Attribute
{
public:
void Attack() override
{
cout << "物理攻击" << endl;
}
};
//法系属性
class MagicAttribute : public Attribute
{
public:
void Attack() override
{
cout << "法系攻击" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Character* character = new FemaleCharacter();
Attribute* magicAttribute = new MagicAttribute();
character->SetAttribute(magicAttribute);
character->Attack();
system("pause");
return 0;
}
结果:
女角色: 法系攻击
请按任意键继续. . .
我们使用了桥接模式,将人物性别和人物攻击的属性作为两个不同的属性,拆分成两个不同的继承树,然后在人物基类中保存一个人物属性的指针,使二者行程了聚合关系。这样,我们减少了冗余的类,大大减少了继承的复杂度,增强了复用性。在增加新类或者属性时,只需要增加新的属性类即可,符合开放-封闭原则。
三.桥接模式的总结
最后,我们来总结一下桥接模式的优点缺点以及使用时机。
优点:
1)桥接模式是多层继承体系的一个很好的代替解决方案,大大减少了子类的个数,增加了复用性。
2)桥接模式分离了抽象接口以及实现部分,采用聚合方式,解耦了抽象和实现之间固有的绑定关系,让抽象和实现在不同的继承体系中实现。
3)桥接模式很好地提高了系统的扩展性,在扩展两个维度中任意一个维度时,都不需要修改原有内容,符合开放-封闭原则。
缺点:
1)桥接模式增加了系统的理解的难度,不如多层继承来得直接。
2)如何识别并分离两个维度比较困难,所以使用有一定的局限性。
使用时机:
如果我们的系统之前是使用多层继承方式实现的,在两个层次之间静态的继承关系不够灵活时,我们就可以考虑桥接模式。当我们的系统中存在两个或多个独立变化的维度,这两个维度都需要独立扩展,为了不让扩展的复杂度变成M*N,而是M+N的话,我们就可以使用桥接模式。