考虑要设计一个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就好了。画一画类图,你还会发现总的类数量变少了。