动机

由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化。

如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?

代码:通信模块

第一个版本

  1. Messager通信类,是抽象类,表示通信
  2. 继承与Messager的平台具体实现类:PCMessagerBase、MobileMessagerBase
  3. 继承PCMessagerBase的经典版PCMessagerLite、完美版PCMessagerPerfect;继承MobileMessagerBase的经典版MobileMessagerLite、完美版MobileMessagerPerfect ```cpp //////通信 class Messager { public: virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0; //播放声音 virtual void PlaySound()=0; //画图形 virtual void DrawShape()=0; //写数据 virtual void WriteText()=0; //连接网络 virtual void Connect()=0;

    virtual ~Messager(){} };

//////平台实现:不同平台中,播放声音、在界面上画图形的实现可能不同 //PC平台 class PCMessagerBase : public Messager{ public: virtual void PlaySound(){ //** } virtual void DrawShape(){ //** } virtual void WriteText(){ //** } virtual void Connect(){ //** } };

//移动版 class MobileMessagerBase : public Messager{ public: virtual void PlaySound(){ //========== } virtual void DrawShape(){ //========== } virtual void WriteText(){ //========== } virtual void Connect(){ //========== } };

//////业务抽象:推出两个版本,经典版和完美版。在里面实现一些函数 //PC的经典版 class PCMessagerLite : public PCMessagerBase { public: virtual void Login(string username, string password){ PCMessagerBase::Connect(); //…….. } virtual void SendMessage(string message){ PCMessagerBase::WriteText(); //…….. } virtual void SendPicture(Image image){ PCMessagerBase::DrawShape(); //…….. } };

//PC的完美版 class PCMessagerPerfect : public PCMessagerBase { public: virtual void Login(string username, string password){ //登录的时候播放一些声音 PCMessagerBase::PlaySound(); //** PCMessagerBase::Connect(); //…….. } virtual void SendMessage(string message){ //发送消息的时候播放一些声音 PCMessagerBase::PlaySound(); //** PCMessagerBase::WriteText(); //…….. } virtual void SendPicture(Image image){ //发送图片的时候,播放一些声音 PCMessagerBase::PlaySound(); //** PCMessagerBase::DrawShape(); //…….. } };

//移动端经典版 class MobileMessagerLite : public MobileMessagerBase { public: virtual void Login(string username, string password){ MobileMessagerBase::Connect(); //…….. } virtual void SendMessage(string message){ MobileMessagerBase::WriteText(); //…….. } virtual void SendPicture(Image image){ MobileMessagerBase::DrawShape(); //…….. } };

//移动端完美版 class MobileMessagerPerfect : public MobileMessagerBase { public: virtual void Login(string username, string password){ MobileMessagerBase::PlaySound(); //** MobileMessagerBase::Connect(); //…….. } virtual void SendMessage(string message){

  1. MobileMessagerBase::PlaySound();
  2. //********
  3. MobileMessagerBase::WriteText();
  4. //........
  5. }
  6. virtual void SendPicture(Image image){
  7. MobileMessagerBase::PlaySound();
  8. //********
  9. MobileMessagerBase::DrawShape();
  10. //........
  11. }

};

//使用的时候 void Process(){ //编译时装配 Messager *m = new MobileMessagerPerfect(); }

  1. 如果把平面实现的变化(分支)列为n,把业务抽象的变化(分支)列为m。那么类的数目就有1+n+(m+n)
  2. 不难发现,上面其实还有很多重复的代码。
  3. ```cpp
  4. class PCMessagerPerfect : public PCMessagerBase {
  5. public:
  6. virtual void Login(string username, string password){
  7. PCMessagerBase::PlaySound();
  8. //********
  9. PCMessagerBase::Connect();
  10. //........
  11. }
  12. //...
  13. };
  14. //移动端完美版
  15. class MobileMessagerPerfect : public MobileMessagerBase {
  16. public:
  17. virtual void Login(string username, string password){
  18. MobileMessagerBase::PlaySound();
  19. //********
  20. MobileMessagerBase::Connect();
  21. //........
  22. }
  23. //...
  24. };
  1. 第5行-第17行;第7行-第19行是一样的;
  2. 第4行-第16行;第6行-第18行表面上是不一样的,但其实又是一样的,它们是同一样的虚函数

第一次修改:把继承改成组成(继承转组合),然后使用基类指针多态调用

  1. class PCMessagerPerfect {
  2. PCMessagerBase* messager; //申明成指针,指针才有多态性
  3. public:
  4. virtual void Login(string username, string password) {
  5. messager->PlaySound();
  6. //*****
  7. messager->Connect();
  8. //....
  9. }
  10. ....
  11. }
  12. class MobileMessagerPerfect {
  13. MobileMessagerBase* messager;
  14. public:
  15. virtual void Login(string username, string password) {
  16. messager->PlaySound();
  17. //******
  18. messager->Connect();
  19. //......
  20. }
  21. }

第二次修改:PCMessagerBase和MobileMessagerBase又可以申明成同一个基类

  1. class PCMessagerPerfect {
  2. Messager* messager; //申明成父类,在使用的时候 =new PCMessagerBase()
  3. public:
  4. virtual void Login(string username, string password) {
  5. messager->PlaySound();
  6. //*****
  7. messager->Connect();
  8. //....
  9. }
  10. ....
  11. }
  12. class MobileMessagerPerfect {
  13. Messager* messager; //在使用时(即运行的时候) = new MobileMessagerBase
  14. public:
  15. virtual void Login(string username, string password) {
  16. messager->PlaySound();
  17. //******
  18. messager->Connect();
  19. //......
  20. }
  21. }

第三次修改

  1. 发现这两个类其实一模一样,就可以合并成一个了。把PCMessagerPerfectMobileMessagerPerfect改成MessagerPerfect
  2. 那MessagerLite也是可以如法炮制 ```cpp class MessagerPerfect { Messager* messager; //申明成父类,在使用的时候 =new PCMessagerBase()

public: virtual void Login(string username, string password) { messager->PlaySound(); //* messager->Connect(); //…. } …. }

class MessagerLite{ Messager* messager; public: virtual void Login(string username, string password) { messger->Connect(); //… } //… }

  1. 问题:
  2. 1. `MessagerPerfect::messager = new PCMessagerBase()`是不成立的。因为`PCMessagerBase`还是一个抽象类,PCMessagerBaseoverride了部分虚函数,还有一些虚函数(LoginSendMessageSendPicture)没有实现
  3. 1. `MessagerLite``MessagerPerfect`也只实现了部分的虚函数,还有虚函数没有实现。所以它们也是抽象类
  4. 原因:Messager中的LoginSendMessgerSendPicture Messager中的PlaySoundDrawShapeWriteTextConnect是两类东西,放在一起是不合适的。应该把它们拆分开。
  5. <a name="a2Mw2"></a>
  6. ### 第二个版本
  7. Messager中的LoginSendMessgerSendPictureMessager中的PlaySoundDrawShapeWriteTextConnect拆分成两个类。<br />其中一个类称之为MessagerImp,是具体的平台实现。
  8. ```cpp
  9. //消息
  10. class Messager{
  11. public:
  12. virtual void Login(string username, string password)=0;
  13. virtual void SendMessage(string message)=0;
  14. virtual void SendPicture(Image image)=0;
  15. virtual ~Messager(){}
  16. };
  17. //具体的平台实现
  18. class MessagerImp{
  19. public:
  20. virtual void PlaySound()=0;
  21. virtual void DrawShape()=0;
  22. virtual void WriteText()=0;
  23. virtual void Connect()=0;
  24. virtual MessagerImp(){}
  25. };
  26. //平台实现 n
  27. class PCMessagerImp : public MessagerImp{
  28. public:
  29. virtual void PlaySound(){
  30. //**********
  31. }
  32. virtual void DrawShape(){
  33. //**********
  34. }
  35. virtual void WriteText(){
  36. //**********
  37. }
  38. virtual void Connect(){
  39. //**********
  40. }
  41. };
  42. class MobileMessagerImp : public MessagerImp{
  43. public:
  44. virtual void PlaySound(){
  45. //==========
  46. }
  47. virtual void DrawShape(){
  48. //==========
  49. }
  50. virtual void WriteText(){
  51. //==========
  52. }
  53. virtual void Connect(){
  54. //==========
  55. }
  56. };
  57. //业务抽象
  58. class MessagerLite :public Messager { //经典版
  59. MessagerImp* messageImp; // 运行时在放入, = new PCMessagerBase()
  60. public:
  61. virtual void Login(string username, string password){
  62. messagerImp->Connect();
  63. //........
  64. }
  65. virtual void SendMessage(string message){
  66. messagerImp->WriteText();
  67. //........
  68. }
  69. virtual void SendPicture(Image image){
  70. messagerImp->DrawShape();
  71. //........
  72. }
  73. };
  74. class MessagerPerfect :public Messager { //完美版
  75. MessagerImp* messageImp; //运行时,在赋值。= new MobileMessagerImp
  76. public:
  77. virtual void Login(string username, string password){
  78. messagerImp->PlaySound();
  79. //********
  80. messagerImp->Connect();
  81. //........
  82. }
  83. virtual void SendMessage(string message){
  84. messagerImp->PlaySound();
  85. //********
  86. messagerImp->WriteText();
  87. //........
  88. }
  89. virtual void SendPicture(Image image){
  90. messagerImp->PlaySound();
  91. //********
  92. messagerImp->DrawShape();
  93. //........
  94. }
  95. };
  96. void Process(){
  97. //运行时装配
  98. MessagerImp* mImp = new PCMessagerImp();
  99. Messager *m =new Messager(mImp);
  100. }

第三个版本:桥模式

其实不难看出,MessagerLiteMessagerPerfect还存在冗余代码。它们有着一个相同的成员变量MessagerImp* messageImp

如果某一个类的多个子类有着共同的字段,我们可以把这个字段往上提一层——马丁·福勒经典重构理论

MessagerImp* messageImp字段提到Messager类中

  1. class Messager{
  2. protected:
  3. MessagerImp* messagerImp;
  4. //将MessagerLite、MessagerPerfect的公共字段提到父类Messager
  5. public:
  6. virtual void Login(string username, string password)=0;
  7. virtual void SendMessage(string message)=0;
  8. virtual void SendPicture(Image image)=0;
  9. virtual ~Messager(){}
  10. };
  11. //消息函数的接口(到具体平台中再实现)
  12. class MessagerImp{
  13. public:
  14. virtual void PlaySound()=0;
  15. virtual void DrawShape()=0;
  16. virtual void WriteText()=0;
  17. virtual void Connect()=0;
  18. virtual MessagerImp(){}
  19. };
  20. //平台实现 n
  21. class PCMessagerImp : public MessagerImp{
  22. public:
  23. virtual void PlaySound(){
  24. //**********
  25. }
  26. virtual void DrawShape(){
  27. //**********
  28. }
  29. virtual void WriteText(){
  30. //**********
  31. }
  32. virtual void Connect(){
  33. //**********
  34. }
  35. };
  36. class MobileMessagerImp : public MessagerImp{
  37. public:
  38. virtual void PlaySound(){
  39. //==========
  40. }
  41. virtual void DrawShape(){
  42. //==========
  43. }
  44. virtual void WriteText(){
  45. //==========
  46. }
  47. virtual void Connect(){
  48. //==========
  49. }
  50. };
  51. //业务抽象 m
  52. //类的数目:1+n+m
  53. class MessagerLite :public Messager {
  54. public:
  55. virtual void Login(string username, string password){
  56. messagerImp->Connect();
  57. //........
  58. }
  59. virtual void SendMessage(string message){
  60. messagerImp->WriteText();
  61. //........
  62. }
  63. virtual void SendPicture(Image image){
  64. messagerImp->DrawShape();
  65. //........
  66. }
  67. };
  68. class MessagerPerfect :public Messager {
  69. public:
  70. virtual void Login(string username, string password){
  71. messagerImp->PlaySound();
  72. //********
  73. messagerImp->Connect();
  74. //........
  75. }
  76. virtual void SendMessage(string message){
  77. messagerImp->PlaySound();
  78. //********
  79. messagerImp->WriteText();
  80. //........
  81. }
  82. virtual void SendPicture(Image image){
  83. messagerImp->PlaySound();
  84. //********
  85. messagerImp->DrawShape();
  86. //........
  87. }
  88. };
  89. //使用
  90. void Process(){
  91. //运行时装配(运行时把它们组合在一起)
  92. MessagerImp* mImp=new PCMessagerImp();
  93. Messager *m =new Messager(mImp);
  94. }

类的个数变成了1+n+m。但是运行时,还是有n*m的功能,它们可以相互交叉组合。

总结

反过来思考。其实,你在第一个版本中,在一个类(即Messager)中,放了两组责任不同的函数(如下),它们是两种不同的变化方向,一个是平台实现,一个是业务方向。

  1. Login、SendMessage、SendPicture => 业务抽象,即有经典版、有完美版
  2. PlaySound、DrawShape、WriteText、Connect => 平台实现,即有移动端、有PC端

这两个不同变化的方向,使子类往不同的方向变化,会导致类的暴增。所以,就不应该把它们放在一个类里,这也是单一职责所倡导的。

模式定义

将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。——《设计模式》GOF

结构

image.png

  1. Abstraction => Messager
  2. Implementor => MessagerImp
  3. RefinedAbstraction => MessagerLite、MessagerPerfect,即抽象类的变化
  4. ConcreteImplementorA、ConcreteImplementorB => PCMessagerImp、MobileMessagerImp,即具体的实现类,比如PC平台、移动端

两个方向分开变化,而不是像在第一个版本中,搅在一起变化。

要点总结

要点一:Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,就是“子类化”它们。

要点二:Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方案

要点三:Bridge模式的应用一般在“有两个非常强的变化维度”上。有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式

  • Bridge其实提供了一种思路,把属于其他变化维度的东西通过组合的方式合在一起。打包一个基类,然后用一个抽象指针指向它。
  • 如果有多个变化维度,可以一个维度一个抽象指针 ```cpp class Messager{ protected: MessagerImp messagerImp; //第二个变化维度 //如果还有别的变化维度,可以再填加抽象指针 MessagerImp2 messagerImp2; //第三个变化维度 //….

public: //Messager自己本身是一个变化维度 => 第一个变化维度 virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0;

  1. virtual ~Messager(){}

}; ```