设计模式--桥模式

考虑要设计一个Messager模块,这个模块要实现如下功能

  • 登录
  • 发送消息
  • 播放声音(登录或者播放的时候播放声音)
  • 画图(登录的时候显示的图片)

那么最直观的设计如下

class Messager {
public:
  virtual void Login(string userName, string password) = 0;
  virtual void SendMessage(string message) = 0;
  
  virtual void PlaySound() = 0;
  virtual void DrawShape() = 0;

  virtual ~Message() {}
};

然后呢,我们这个Messager要实现跨平台的功能,要在PC平台和手机平台都能适用。然而对于PC和Mobile来说,唯一有区别的地方只有播放声音和画图这些与平台相关的函数不同,而登录、发送消息这些业务功能是相同的,所以你可能会设计出如下的两个类:

class PCMessagerBase : public Messager {
  virtual void PlaySound() { /*******/ }
  virtual void DrawShape() { /*******/ }
};
class MobileMessagerBase : public Messager {
  virtual void PlaySound() { /*******/ }
  virtual void DrawShape() { /*******/ }
};

接下来继续考虑在同一个平台要推出两个不同版本的Messager,一个是精简版,一个是完美版。比如说精简版的在登录的时候不会播放声音和显示图片,而完美版的则支持这两个功能。其设计可能如下:

class PCMessagerLite : public PCMessagerBase {
  virtual void Login(string userName, string password) { /******/ }
  virtual void SendMessage() { /*******/ }
};
class PCMessagerPerfect : public PCMessagerBase {
  virtual void Login(string userName, string password) {
    PCMessageBase::PlaySound();
    PCMessageBase::DrawShape();
  }
  virtual void SendMessage() {
    PCMessageBase::PlaySound();
    PCMessageBase::DrawShape();
  }
}

class MobileMessagerLite : public MobileMessagerBase {
  virtual void Login(string userName, string password) { /******/ }
  virtual void SendMessage() { /*******/ }
};
class MobileMessagerPerfect : public MobileMessagerBase {
  virtual void Login(string userName, string password) {
    MobileMessageBase::PlaySound();
    MobileMessageBase::DrawShape();
  }
  virtual void SendMessage() {
    MobileMessageBase::PlaySound();
    MobileMessageBase::DrawShape();
  }
}

那么到目前为止,类的结构图如下

这么设计有什么缺点呢?缺点是代码重复性太大了。这种重复是一种结构性的重复。你仔细观察观察MobileMessagerPerfect和PCMessagerPeferct这两个类中Login和SendMessage的实现,会发现他们的流程是一模一样的,都是播放声音,显示登录画面,登录。不同的只是因为平台导致的播放声音和登录画面的实现不同。

那么有什么改进的方法呢? 你可以发现在Perfect类的Login中的PlaySound和DrawShape可以追溯到同一个虚函数调用。如果你知道装饰者模式的话,那么你很快的可以想到改进的方法是将PCMobildBase作为一个类的字段去组合它,而不是去继承它

class PCMessagePerfect {
private:
  PCMessageBase* message;
}

通过message->PlaySound()来调用。同理MobildMessagePerfect中设计一个MobileMessageBase*字段。但是如果都申明为一个具体平台的Base字段的话,程序又写的太死了,所以你又发现可以将这个类的字段申明为Message*,然后在将来要构造Perfect对象的时候传进去一个具体的Base对象。但是现在问题又来了,现在*Base对象是个抽象类,是无法new出对象的。(Login, SendMessage等方法没有实现)

那么问题的根源是什么?原因在是Message类中,你把业务逻辑(Login, SendMessage)和平台实现(PlaySound, DrawShap)混合到一起了,这样做是很不合适的,应该将他们拆分开。

最终的类设计应该如下

class Message { Login, SendMessage }  // 业务功能部分
class MessageImp { PlaySound, DrawShap } // 平台实现部分
class MessageBase : MessageImp {} // 具体的平台实现
class MessagePerfect : Message {  // 继承Message的业务功能部分
  MessageImp* messageImp;      // 组合得到Message的平台功能部分
}

 要支持PC平台的,只要传入一个PCMessageImp就可以了,而要实现手机平台的,只要传入一个MobileMessagerImp就好了。画一画类图,你还会发现总的类数量变少了。

原文地址:https://www.cnblogs.com/David-Lin/p/10807168.html