设计模式之策略模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

       在三国故事有一个非常经典的“锦囊妙计”,说的是赵云要陪刘备去赴会,但是诸葛亮担心这二人不能应对即将出现的问题,就把他提前预料到的问题写了三个解决办法,装进了三个锦囊,交给赵云并告诉他在什么情况下打开什么锦囊。果然,在诸葛亮的神机妙算之下,二人靠着锦囊里面的妙计成功地化解危机,转危为安。赵云提前不需要管如何解决问题,专业的问题交给专业的人,解决办法交给诸葛亮。只需要遇到问题A,掏出A锦囊,遇到问题B,掏出B锦囊...。这个故事,就类似于我们的策略模式。

       假设我们现在需要开发了一款游戏,里面是各种各样的鸭子,例如绿头鸭和红头鸭,再或者是黄色的橡皮鸭,一边游泳一边叫。如果采用标准的OO设计,那么我们刚开始可以想到这样的结构:


        一顿霹雳啪啦之后,游戏上线了,并且运行了一段时间。突然有一天,产品经理叫所有开发人员开会,添加了一个新需求:鸭子需要飞起来。如果是新手,可能就要这么改了:直接在父类中添加一个fly() 飞行方法,并且实现。代码修改完之后,上线测试就傻眼了,屏幕里面有只橡皮鸭在飞来飞去。新手一想,这好办,直接在橡皮鸭子类中重写飞行方法。让它飞不起来就行了。劈里啪啦一顿改,马上修改好了,游戏又可以正常运行了,目前的结构变成了这样:


       虽然目前来看没什么问题,但是如果仔细分析一下,就会发现这里面存在很大的问题。

第一:虽然只对局部的代码做了修改,但是影响范围确扩散到了所有的子类中,导致出现了完全意料不到的变化。

第二:在这个例子中只是有三个类型的鸭子,如果鸭子类型达到了上千种甚至更过,就需要逐个去检查并修改其代码。导致原本为了减轻工作量来做的设计(继承),现在变成了噩梦。

第三:类型扩展非常不灵活,假如需要添加一只木头鸭,那么木头鸭只能游泳,不会飞也不会叫,也需要检查并覆盖这两个方法。

        通过观察我们的类设计,我们会发现,除了游泳这个方法,其他的方法都有变化。那么把鸣叫和飞行方法提出来两个接口:Flyable 和 Quackable 。只有能叫又能飞的鸭子才需要实现这个接口,其他的鸭子不需要实现,这样是否可行:


        答案是这样做是不行的,因为:第一,假如许多人在开发不同的飞行方法,那么你会看到各种以千奇百怪姿势飞行的鸭子。第二,这样就导致了代码重复比较大,相同的代码不能复用,在每个类种都需要实现一遍,相对于前一种解决办法,只不过是从龙潭跳进了虎穴。

        那么有没有一种方式,能够灵活扩展,并且能够达到代码复用的目的呢,当然了,那就是采用策略模式。

        首先,我们要遵守一个原则:找出对象中的变化之处,把它们独立出来,不要和不变的混在一起。这个原则可以使代码变化所造成的无法预料的问题变少,系统变得更有弹性。在我们的例子中,不变的是游泳这个方法,变化的是鸣叫和飞行等方法。即使后期再有其他的需求,那么游泳的方法肯定不会改变。如果一旦出现一种不会游泳的鸭子,那么一定是分类有问题,此时就需要新的父类。

        此时,就需要引入一种新的编程思想——面向接口编程。在变量声明时使用接口对象,运行时动态的指定实现类。如果想要解决目前的困境,那么就需要这样设计:

1.提取鸭子的飞行行为( FlyBehavior )和鸣叫行为(QuackBehavior),抽取为两个接口,对这两个接口进行不同的实现,这样一来,飞行和鸣叫的方法可以被其他类型复用,例如鹅,因为此时方法已经脱离了鸭子类型,而且我们新增行为,也不会影响到到已经实现的方法:


2.对于父类鸭子类型,只保留游泳方法实现,然后添加两个接口类型的成员变量,使用performQuake 和 performFly两个方法来代替之前的飞行和鸣叫方法。


3.子类实现时,动态指定接口实现类。例如,添加一个绿头鸭时,只需要以下代码。

public class GreenDuck extends Duck{   
    public GreenDuck(){
		// 实现飞行的方法被委托给 FlyWithWings
		flyBehavior = new FlyWithWings(); 
		//实现鸣叫的方法委托给 QuackBehavior
		quackBehavior = new QuackBehavior();
    }
	public void display(){
		System.out.println("展现出绿色的头部");
	}
}

        此时虽然有一些小瑕疵,例如飞行行为的实现类硬编码在了构造函数中,如果添加一只机器鸭,在低空采用翅膀飞行,高空采用火箭飞行,那么就不能正真动态地来切换实现类,此时就要采用其他设计模式来解决这个问题,例如添加一个setFlyBehavior方法,可以灵活动态指定。但是相比于之前的编码方式,这样做以后对类之间的依赖解耦,提高了代码复用性,后期扩展也十分方便。可谓一举多得,以上就是策略模式的思想。


参考:

1.《Head First 设计模式》--- 策略模式

2. 菜鸟教程--- 策略模式


        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值