china仁

导航

策略模式

  设计模式早已不是什么新鲜的话题,网上相关的文章更是铺天盖地,但是对于一个从事面向程序开发的人员来说,它又确实是我们最常遇到的老熟人,从最基础的标准库到各种框架的架构设计,可以说设计模式无处不在。以我们java程序员为例,最基础的IO库就是一个装饰者模式的典型实现,事件响应的处理,回调方法的运用基本上都是基于观察者模式。servlet的实现其实是采用了模板方法模式,spring的DI容器更是一个大工厂(工厂模式)...之前由于错误的观念,觉得在实际的代码中需要实现设计模式的机会不多,设计模式没那么重要,所以虽然写了好几年代码,但是对于设计模式却只是零零散散的学习过,既不系统也不深入。

  其实在通常的项目中,实现设计模式的机会的确未必有很多,这主要是因为大量的基础架构代码已经以设计模式的方式架构,很多只要码农直接调用相关的API或进行简单的参数配置即可,造成了设计模式并非必须的印象。如果你是一个架构师,或者是基础框架和类库的开发人员,则设计模式的使用将非常普遍,否则又何以设计出可复用、可扩展和可维护的软件呢?设计模式是过去开发者经过实践检验过的一套解决方案,撇开前人的智慧,从零开始可并不明智。

  其次设计模式归根结底是应解决需求变更的要求而应运而生的。凡是有过开发经验的人都应该被客户需求的朝令夕改困扰过。如果软件开发总是一锤子买卖,我无论用什么方式组织代码,只要能运行,通过测试,上线就万事大吉,那设计模式实在是没什么必要,甚至是累赘,因为很多设计模式的实现会导致类型膨胀,软件复杂度变高。但是由于需求变更的存在,软件的复用性,可维护性和可扩展性的重要性就凸显出来。为了尽可能的做到这几点,人们开始提炼出一些设计原则:比如常常听到的面向接口编程,封装变化,对扩展开放,对修改关闭,松耦合,高内聚等等等等。但这些只是一些抽象的指导原则,如何落实呢,设计模式就是在实践中总结出来的一套行之有效的能将这些设计原则落到实处的代码组织方式。当然了,这些原则都不是教条,在实际中甚至可能产生几条原则之间鱼与熊掌不可兼得的情况,所以要灵活,所谓运用之妙,存乎一心。

  说到灵活,无论我们把设计模式背得有多熟练,都不太可能在实际开发中,刚一开篇,就指点江山的说:嗯,这里应该使用抽象工厂,那里可以使用适配器。要知道刚开始开发的时候,如果一个模块的业务逻辑较为复杂,这个时候,甚至单纯的使它实现客户需求,正确运行往往都需要一番试错调整的过程,哪能机械的套用某某设计模式呢?(这里吐一下槽,以前在公司写文档的时候,经常是先写文档,然后实现代码,可是在文档的格式中居然是要求程序员事先把方法流程全部都规划好,不用试错,不用调整代码,未出隆中,天下三分。每到这时,我都痛苦万分,当然最后实现的代码肯定和文档严重错位。)而任何书上的设计模式实现都只是一个参考而已,它总是和具体的语言特性相关,比如经典的工厂模式依然是使用new的方式来实例化代码,而spring则是使用反射来实例化代码。在java这样的单继承语言中适配器模式属于对象适配器,而在c++这样的多继承语言中还可以实现类适配器。更为重要的是教学代码可以单独提取出来,不用照顾上下文环境,而在实际开发中,与上下文环境无缝对接常常要求对设计模式进行适当变形,但同时保留其主旨精神,对设计原则的实现,对软件质量的提高。所以李建中老师说设计模式总是和重构相配合的,这的确对我起到了纠正迷误的作用。记得有一本书叫做重构与模式,老外写的。看来高手们的看法总是趋于一致的。

  对设计模式整体的看法就聊到这里,水平实在有限,肯定错误很多,所幸我写博客纯粹就是自娱自乐,倒也没什么心理负担。最近把深入浅出设计模式细读了一遍,想把相关的内容以博客的形式总结一下。开篇第一章就是策略模式。

      首先来看代码,假设某游戏公司要设计一个鸭子玩具系列,有红头鸭,绿头鸭。这个鸭子要会飞,这真的很容易:

  public abstract class Duck {

    public void fly() {

      System.out.println("fly");

    }

  }

  class RedDuck extends Duck {

  }

  class GreenDuck extends Duck {

  }

       而且我们用了继承来复用代码,这里假设红头鸭和绿头鸭都遵循同样的飞行行为。许多糟糕的java教科书对继承的代码复用功能可是无限推崇的。但是如果我们扩展产品线,又增加了唐老鸭,橡皮鸭,野鸭...并且它们都有不同于父类默认的飞行方式。这好办,重写fly()方法。但是如果唐老鸭和橡皮鸭都执行同样的飞行行为,难道同样的代码要写两遍吗?如果需求继续发生变动,唐老鸭和橡皮鸭的飞行行为要改变,那就要修改两处吗?如果需求继续变化,客户发现还是原来的方式好,于是唐老鸭和橡皮鸭又要都修改吗?在更复杂的系统里,如果有上百种鸭子需要修改呢?天哪,代码无法复用,难以维护,而且违反了开-闭原则,对已经经过测试的代码修改万一引入新的错误,你以为测试组的同事都那么好说话,心甘情愿陪你加班?

        从这里我们可以看到通过继承实现代码复用是有很大局限性的,别忘了那条设计原则:优先使用组合。没错,通过组合来复用代码会更加的灵活。我们可以把飞行行为从Duck代码中分离出来,为什么,因为鸭子的飞行行为随着需求变更而不断发生变化,一条重要的设计原则就是封装变化,把变化的部分和保持稳定的代码隔离开来,但是这两部分代码还是要交互的啊,如何保证容易变化的部分变化后,对于不变的部分保持透明,使其代码不用发生改变呢?通过面向接口编程等方式实现这两部分的解耦。我们可以设计一个飞行接口Flyable

interface Flyable {

  public void fly();

}

将其组合进Duck类中:

 

  

现在我们可以自由的定制各种飞行策略类来实现Flyable接口,然后把具体的策略类赋值给flyable成员变量就ok了。什么,需求变更了,写一个新的策略类,原有的代码不变,反正Duck总是和接口类型打交道,具体是什么类型对于Duck是完全透明的,这就是对扩展开放对修改关闭。执行同样策略的多个鸭子类型的飞行策略都要改变,只需要改一处即可,而且修改被局限于一个小范围内,测试组的同事们也不会对你口诛笔伐了。

       

 

     

  

posted on 2014-07-24 01:26  china仁  阅读(179)  评论(0)    收藏  举报