类爆炸之 Bridge 模式

allen_java 4年前
   <h2>迷之微笑</h2>    <p>经过C哥的精心指导,消息中心终于上线!代码运行了半个月,稳定无bug。</p>    <p>王小二托着下腮,看着代码,一抹迷之微笑随之闪现^_^。作为一名有追求的码农,此时的快乐或许只有自己能懂。</p>    <h2>消息中心的重构</h2>    <p>一天清晨,小二凝神聚力,手指在键盘间有节奏的敲击着,一行行代码跃然屏上。不知不觉,老大在小二背后站了半天了...</p>    <p>"小二,之前消息中心是你做的吧?"</p>    <p>"嗯嗯,是的。"</p>    <p>"好的,咱们现在正在搞服务拆分。而消息中心又是一个通用的服务,所以我想把消息中心拆出来,作为底层服务。"</p>    <p>"好啊,早应该这样了!"</p>    <p>"嗯,具体发送消息的逻辑,这块交给java组同学去写。你只需要按照约定的数据格式,将数据push到队列里去,java那边去消费就可以了。"</p>    <p>"嗯...可以,队列用什么实现呢?"</p>    <p>"关于队列,这次需要你支持两种方式:一种是redis、一种是mq"</p>    <p>"也就是说我既支持往redis队列里面push数据,也支持往mq里push数据?"</p>    <p>"是的,就是这样,这块你好好设计下吧!"</p>    <p>"好的,放心吧老大!"</p>    <h2>设计类图</h2>    <p>小二这两天正在研究设计模式,既然接到了重构的新需求,那就好好大展一番身手吧!</p>    <p>不一会,小二就理出了大体的思路:</p>    <p>发送消息,分为3步:</p>    <p>1、不同的消息(短信、微信)组装各自的数据格式和内容;</p>    <p>2、消息可以使用不同的方式(redis、mq)推送到队列里;</p>    <p>3、使用一个send()方法,先从步骤1获取数据,再利用步骤2的方法push到相应的队列里。</p>    <p>思路清楚了,小二马上画出了类图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/020ce7a2178d99298cfd3a96d92776c4.png"></p>    <p>classboom00.png-35.7kB</p>    <p>小二反复看了几遍自己设计的类图:</p>    <p>嗯,基本实现了需求。</p>    <p>1、消息分为短信消息和微信消息(SmsMessage和WechatMessage)</p>    <p>2、相同的消息既可以通过redis发送,又可以通过Mq发送。</p>    <p>没毛病,great!</p>    <h2>类爆炸</h2>    <p>和往常一样,比较大的设计,还是得请C哥把把关。</p>    <p>小二找到C哥,详细介绍了自己的需求和设计。</p>    <p>"嗯...小二啊,问题是解决了,但设计看起来有点问题啊!"</p>    <p>"啊?有问题?请C哥指教"</p>    <p>"这个会引起类爆炸!"</p>    <p>"啥?类还会爆炸?你别逗我了"</p>    <p>"哈哈,不信?来,我让你看看类怎么爆炸的。假设需求要你新增Email消息类型,你再设计下类图"</p>    <p>"好的,C哥你等下,马上设计出来"</p>    <p>不一会,王小二就设计出了新的类图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4f004a0b7f4e87beacf24bfe30cf9e87.png"></p>    <p>classboom1.png-46.9kB</p>    <p>"小二,红色部分是你新增的3个类。"</p>    <p>"嗯嗯,是的!"</p>    <p>"好,在此基础上,你再增加Mysql队列的发送方式"</p>    <p>"好的!"</p>    <p>小二拿着新的类图找到了C哥:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/28deb63a8c5a680d4df11b1480e7222b.png"></p>    <p>classboom2.png-54.7kB</p>    <p>“小二,刚才只是让你新增一种消息类型和发送方式,你看看一共增加了几个类?”</p>    <p>“1.2.3..6,一共新增了6个类!”</p>    <p>"好,你现在一共有13个类,假设再让你新增一种消息类型和发送方式,你又会新增多少个类?"</p>    <p>"嗯...会新增8个类,到时候就13+8=21个类了..."</p>    <p>“类太多了,爆炸了吧?哈哈,这就是类爆炸”</p>    <p>“确实是,类确实太多了!但是,怎么解决呢?...”</p>    <h2>Bridge模式登场</h2>    <p>"小二啊,你还记不记得前面我给你讲的四人团的三条建议?"</p>    <p>“嗯,记得:</p>    <pre>  1、针对接口编程;   2、优先使用对象组合,而不是类继承;   3、找到并封装变化的点。</pre>    <p>”</p>    <p>“对,就是这3点。你看看,你的设计就违背了上面的原则。”C哥说道。</p>    <p>"嗯?还真违反了???"王小二看了一会...</p>    <p>"哦...是的,C哥,确实是。违反了第2点,你看我类图中使用的都是继承,这个继承间耦合性太高了,太庞大了!"</p>    <p>“是的,现在我们就用Bridge模式把他拆出来。”</p>    <p>"我先给你讲讲Bridge模式的基本定义吧!"</p>    <p>“好的,C哥!”</p>    <p>Bridge模式,也即桥梁模式,四人团的说法是:“将抽象部分与它的实现部分分离,使它们都可以独立地变化。”</p>    <p>“啊?C哥,表示完全听不懂...”</p>    <p>"哈哈,正常,你一下能听懂才怪呢,这句话很容易使初学者产生误解,我们边实践,边解释这个定义。"</p>    <p>“小二,你刚才不是说四人团建议:‘找到并封装变化的点’吗?你现在在你的设计中找到这些变化的点,并封装起来。”</p>    <p>“好的,C哥,我想想...”</p>    <p>小二想了一会:“变化的点有2个。一个是消息类型会变化,一个是发送方式会变化。”</p>    <p>想好后,小二马上画了出来。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/748d62022c043dedf871ae0f9a7d9735.png"></p>    <p>22.png-9.7kB</p>    <p>"嗯,不错,小二你解释下吧"</p>    <p>小二解释道:"</p>    <p>变化的点有2个:一个是消息类型[Sms、Wechat...],一个是发送方式[redis、mq...]。</p>    <p>所以我把他们各自都封装了起来,成为2个独立的抽象类:Message和SendType。</p>    <p>Message类负责组装好自己消息类型的数据( combine_data() ),并发送( send() )出去。</p>    <p>SendType类负责将数据push( push_to_queue() )进相应的队列。"</p>    <p>"不错嘛,小二,我在你类图的基础上扩展下,你就知道怎么解决类爆炸的问题了。"</p>    <p>"哇塞,好的,C哥!"</p>    <p>不一会,C哥就在小二的基础上,画出了完整的类图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c925c1b5c11870d0748073f0cd1c8c62.png"></p>    <p>ok1.png-27kB</p>    <p>"看不太懂,C哥你解释下吧!"</p>    <p>C哥解释道:"</p>    <p>小二你看,消息有2种类型:短信和微信。</p>    <p>但不论是短信和微信,他们都应该知道自己的消息格式和内容。</p>    <p>并且,他们得把自己发送出去,也就是push到相应的队列里面去。</p>    <p>而如何push到队列里面去呢?这又有2种实现方式,一种是redis队列,一种是mq队列。</p>    <p>也就是,实现发送这个动作,得知道如何发送。</p>    <p>你看这里,我没有用你最初设计的类继承的方法:</p>    <p>这里的抽象部分:即是Message的抽象;</p>    <p>这里的实现部分:即是SendType的实现。</p>    <p>在抽象部分与实现部分之间搭个桥,使抽象部分可以引用实现部分的对象,就是桥接模式。</p>    <p>这样使用对象组合的方式,特别的灵活。"</p>    <p>"哇塞,C哥,这个桥接威力好大啊!"</p>    <p>"是啊,桥接模式比较难,但也更有用。你看,这样不管你是增加一种新的消息类型还是一种新的发送方式,他们之间没有耦合,可以独立的变化。"</p>    <p>"是啊,这样类爆炸的问题也就没有了,冗余减少了,代码更好维护!"</p>    <p>"是这样的!"</p>    <h2>代码实现</h2>    <p>见证了bridge模式的威力之后,小二迫不及待的写出了相应的伪代码:</p>    <p>"C哥,你帮我看下我写的代码思路对吗?"</p>    <p>"好的,我看看..."</p>    <pre>  <?php  //消息抽象类  abstract class Message{      //定义发送方式对象与消息数据      public $send_type_obj;      public $data;        //构造函数      public function __construct($send_type_obj,$data)      {          $this->send_type_obj=$send_type_obj;          $this->data=$data;      }        //抽象类:不同的消息来重写此方法,以得到不同的消息数据      abstract public function combine_data();        //桥接到外部对象(引用外部对象,push到相应的队列)      public function push_to_queue($data){          if($this->send_type_obj instanceof SendType){              $this->send_type_obj->push_to_queue($data);          }      }        //完成发送      public function send(){          $combined_data=$this->combine_data();          $this->push_to_queue($combined_data);      }  }    //短信消息类  class SmsMessage extends Message {      //发送短信消息数据      public function combine_data(){          return 'sms combined data:'.$this->data;      }  }    //微信消息类  class WechatMessage extends Message {      //发送微信消息数据      public function combine_data(){          return 'wechat combined data:'.$this->data;      }  }    //发送方式抽象类  abstract class SendType{      abstract public function push_to_queue($data);  }    //Redis发送方式类  class RedisSendType extends SendType {      //将消息push到redis队列里,完成发送      public function push_to_queue($data)      {          echo  $data." has sent by redis queue\n";      }  }    //Mq发送方式类  class MqSendType extends SendType {      //将消息push到mq队列里,完成发送      public function push_to_queue($data)      {          echo  $data." has sent by mq queue\n";      }  }    /************Test Case*************/    //实例化不同的发送方式类  $redis_send_obj=new RedisSendType();  $mq_send_obj= new MqSendType();    //通过redis发送短信  $sms_redis_obj=new SmsMessage($redis_send_obj,'123');  $sms_redis_obj->send();    //通过redis发送微信  $wechat_redis_obj=new WechatMessage($redis_send_obj,'456');  $wechat_redis_obj->send();    //通过mq发送短信  $sms_mq_obj=new SmsMessage($mq_send_obj,'789');  $sms_mq_obj->send();    //通过mq发送微信  $wechat_mq_obj=new WechatMessage($mq_send_obj,'100');  $wechat_mq_obj->send();</pre>    <p>"嗯,看起来没毛病,我看看你的运行结果。"</p>    <p>"好的,C哥,这是运行结果"</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/17181f253141839785c886e1273407f8.png"></p>    <p>image_1bdjqul59f110vkqqrrel138f2h.png-11.4kB</p>    <p>"哈哈,确实没问题,不错嘛小二!"</p>    <p>"C哥指点的好,谢谢C哥,又学习了一种强大的设计模式!"</p>    <h2>结语</h2>    <p>设计模式如此强大,从bridge就可见其不一般。</p>    <p>那到底什么是设计模式呢?有没有一个通俗的定义呢?</p>    <p>其实,通俗点说:</p>    <p>设计模式,是针对特定问题的,反复出现的解决方案,这种方案被抽象化、模板化。并且随着时间的流逝,被历史证明这是优秀的解决方案。</p>    <p>所以,跟着王小二一起好好的学习设计模式吧,相信你终将迈入"左手代码右手诗"的天地!^_^</p>    <p> </p>    <p> </p>    <p>来自:https://juejin.im/post/58f00cc28d6d8100646d0ffc</p>    <p> </p>