桥(Bridge)模式
“单一职责模式”的适用场景
在软件组件设计中,如果责任划分的不清晰,使用继承得到的结果往往是随需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
场景
某一天,你的老板让你开发一个多媒体软件,其中包括音乐播放器,一个视频播放器和一个图片播放器。
class Player{
public:
virtual void play() = 0;
virtual void setFile(const char *fileName) = 0;
}
class MusicPlayer :public Player{
public:
virtual void play(){
//播放音乐
}
virtual void setFile(const char *musicName){
//设置音乐
}
}
class PicturePlayer :public Player{
public:
virtual void play(){
//播放图片
}
virtual void setFile(const char *pictureName){
//设置图片
}
}
class VideoPlayer :public Player{
public:
virtual void play(){
//播放视频
}
virtual void setFile(const char *videoName){
//设置视频
}
}
过了两天,你的老板发现这个播放器很受欢迎,于是想要通过这个来盈利,所以让你开发一个标准版(付费班)。
class StandardMusicPlayer:public MusicPlayer {
public:
virtual void play(){
//检查是否付费
//加五毛特效
MusicPlayer::play();
}
virtual void setFile(const char *musicName){
//检查是否付费
//加五毛特效
MusicPlayer::setFile(musicName);
}
virtual char* getVersions(){
//返回现在的版本
}
}
class StandardPicturePlayer :public PicturePlayer {
public:
virtual void play(){
//检查是否付费
//加五毛特效
PicturePlayer::play();
}
virtual void setFile(const char *pictureName){
//检查是否付费
//加五毛特效
PicturePlayer::setFile(pictureName);
}
virtual char* getVersions(){
//返回"标准版"
}
}
class StandardVideoPlayer :public VideoPlayer {
public:
virtual void play(){
//检查是否付费
//加五毛特效
VideoPlayer::play();
}
virtual void setFile(const char *videoName){
//检查是否付费
//加五毛特效
VideoPlayer::setFile(videoName);
}
virtual char* getVersions(){
//返回"标准版"
}
}
又过了几天,老板为了顺应部分社会高层人士对身份地位的需求,让你开发一个豪华版(IQ tax)。于是你只能再写三个类,给各个功能加上duang很乌黑很浓密的特效。这下子这个项目就有9个类,但实际上这9个类之间的相似度很高。
按照这样的写法假设你有n个功能类,m个版本,你就需要写m*n个类。这个类的数量是很不合理的。
使用桥模式
假设已经已经三个播放器,这里只实现多个版本
class Versions{
public:
Versions(Player *player) :mPlayer(player){}
virtual void play() = 0;
virtual void setFile(const char *videoName) = 0;
virtual char* getVersions() = 0;
virtual void Sponsor() = 0;
private:
Player *mPlayer;
}
class StandardPlayer :public Versions{
public:
StandardPlayer (Player *player) :Versions(player){}
virtual void play(){
//检查是否付费
//加五毛特效
mPlayer->play();
}
virtual void setFile(const char *videoName){
//检查是否付费
//加五毛特效
mPlayer->setFile(videoName);
}
virtual char* getVersions(){
//返回"标准版"
}
virtual void Sponsor(){
//赞助开发者(每次1块钱)
}
}
class LuxuryPlayer :public Versions{
public:
LuxuryPlayer (Player *player) :Versions(player){}
virtual void play(){
//检查是否付费
//duang很乌黑很浓密的特效
mPlayer->play();
}
virtual void setFile(const char *videoName){
//检查是否付费
//duang很乌黑很浓密的特效
mPlayer->setFile(videoName);
}
virtual char* getVersions(){
//返回"豪华版"
}
virtual void Sponsor(){
//赞助开发者(每次5块钱)
}
}
桥模式的特点
桥模式适用于两类东西之间的关系,比如LuxuryPlayer它本质上不是一个Player,他是一个Versions,因为他有getVersions函数和Sponsor函数,Player类没有这两个函数。装饰器模式适用于两个大类之间(Version和Player)的关系,桥接的是两个大类。
这里要特别说明一些,桥模式和装饰器模式最大的区别在于装饰器模式适用于一类东西之间的关系,装饰器装饰后的对象依然是那个对象,比如上一篇博客里面的Log,不管怎么装饰,它依然是一个Log类,装饰器模式下,只是对一类东西的装饰;