策略模式(Strategy)

本文深入探讨了如何利用策略模式解决在设计鸭子游戏程序时,不同鸭子具备不同飞行与叫声特性的复杂性。通过分离变化与不变部分,引入接口和抽象类的概念,实现动态改变实例行为的目标。策略模式强调封装变化,通过多用组合而非继承,以及针对接口编程而非实现编程,有效避免了继承带来的耦合问题,实现了灵活的代码复用和扩展。最后,通过策略模式的应用实例,展示了如何在鸭子类中实现飞行和叫声行为的策略化管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[b]0、总述[/b]
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式分为三种类型,共23种。
[b]创建型模式[/b]:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
[b]结构型模式[/b]:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
[b]行为型模式[/b]:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
因为并不是所有模式都十分常用,有些模式我们只要大概了解,需要时再深入了解即可。而有的模式则渗透几乎所有我们常用框架里,比如代理模式,装饰者模式,观察者模式等需要重点理解掌握。
现在我们从策略模式入手。

[b]1. 例子引入--添加新特性慎用继承[/b]
我们要做一套模拟鸭子的游戏程序。我们设计了一个鸭子超类Duck,有swim()和quack()特性,具体子类去实现这些方法。现在我们要增加一个fly()的特性,怎么实现?
如果加在父类上,则所有子类自动继承该特性,如果是橡皮鸭,则不应该具备该特性,那就要自己手动重写该方法屏蔽fly的功能。如果再新加子类同样面临这个问题。

当涉及[color=red][b]维护[/b][/color]时,为了复用(reuse)目的而使用继承,结局并不完美。一般一套系统,维护的成本一定会超过开发的成本,所以维护的情况一定要考虑。

经过上面的分析,我们需要的是一个更清晰的方法,让“某些”(而不是全部)鸭子类型可飞。这时可能会有人想,我们可以把fly()取出来,放进一个Flyable接口,这么一来,只有会飞的鸭子才实现接口。
[img]http://dl2.iteye.com/upload/attachment/0099/7866/d025f4fb-f9a1-3754-93a1-dbbd86036019.bmp[/img]

这个设计真是糟糕,虽然解决了“部分”问题,但这样重复的代码会变很多,因为接口没有实现,不能让实现接口的类[color=red][b]复用代码[/b][/color]。这能算是从一个恶梦跳到另一个恶梦。
如果能有一种建立软件的方法,让我们需要改变软件时,可以在对既有的代码影响最小的情况下轻易达到花较少时间重做代码该有多好。幸运的是有一个[b]设计原则[/b],正适应这种状况:
[color=red][b]封装变化[/b][/color]:[color=red]找出应用中可能需要变化之处,把他们独立封装出来,不要和那些不需要变化的代码放在一起。以便以后可以轻易的扩展该部分,而不会影响其他部分。[/color]
系统设计的时候我们需要仔细分析需求,哪部分是容易变化的,比如新增或删除特性。这部分就需要设计上有限照顾。记住,并没有一个法则是系统所有地方都需要满足的,只要重要地方满足就可以了。

[b]2. 分开变化和不变化的部分[/b]
对于上面的例子我们该如何开始?我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变,为了要把两个行为从Duck类中分开,我们将把他们自Duck类中取出,建立一组新类代表每个行为。
我们还希望一切能有弹性,比如可以动态的改变实例的行为。有了这个需求,我们需要借助第二个[b]设计原则[/b]:[color=red]针对接口编程,而不是针对实现编程[/color]。

我们利用接口代表每个行为,让接口的实现类来实现不同的具体行为。比如上面的例子,FlyBehavior接口代表飞行的特性,然后用FlyWithWings来具体实现飞行方式。Duck类不需要实现FlyBehavior接口, 而是使用接口的行为,不需要绑定具体实现。
以前的做法是通过给父类增加fly()特性,或直接实现接口来指定具体的fly行为。这两种做法都依赖于“实现”。
[img]http://dl2.iteye.com/upload/attachment/0099/7862/06a87b85-274e-3e4b-a6e3-129d82eb5254.bmp[/img]

[color=red]这里解释下,为什么不把FlyBehavior设计成抽象类,而是接口?[/color]
其实“针对接口编程”真正的意思是“针对超类型(supertype)编程”。这里所谓的接口有多个含义,是一个概念也是一种Java的interface构造,关键就是多态。利用多态,程序可以针对超类型编程,执行时会根据实例状态执行到真正的行为,不会绑死在超类型的行为上。“针对超类型编程”可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或一个接口”,如此只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量,这也意味着,生命类时,不用理会以后执行时的真正对象类型。

[color=red][b]扩展理解:接口和抽象类的对比:[/b][/color]
[url]http://blog.youkuaiyun.com/fenglibing/article/details/2745123[/url]
[url]http://dev.yesky.com/436/7581936.shtml[/url]
抽象类是模板,接口是规范。

基于上面的分析,为了给Duck新增飞行和鸣叫的行为,我们新增两个接口FlyBehavior和QuackBehavior,还有它们对应的具体实现类:
[img]http://dl2.iteye.com/upload/attachment/0099/7864/3ac4ee78-3ba5-3a1b-b326-a4095133ebcf.bmp[/img]

然后,在Duck类中加入两个实例变量,分别为FlyBehavior与QuackBehavior接口类型,每个变量会利用多态的方式在运行时引用正确的行为类型。(比如通过构造对象时指定具体行为,也可以通过set方法中途改变行为。)搞定!

回头看一下,每一个Duck都有一个FlyBehavior且有一个QuackBehavior,让Duck将fly和quack[b]委托[/b]他们代为处理,将两个类结合起来使用就是组合。这里可以提下第三个[b]设计原则[/b]:[color=red]多用组合,少用继承。[/color]

这样的设计,可以让fly和quack的动作被其他对象复用,因为这些行为已经与Duck类无关了。而我们可以新增一些行为,不会影响到既有的行为类。这么一来,由了继承的“复用”好处,却没有继承所带来的包袱。

[b]3. 策略模式[/b]
上面这个例子完成后,其实我们已经掌握了一个设计模式:策略模式(Strategy Pattern)。把行为抽象为算法。一系列行为其实就是一系列算法,不同的对象使用了不同的算法,不同的策略。
简单定义下:
[b]策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。[/b]
对应类图:

[img]http://dl2.iteye.com/upload/attachment/0099/7860/fe7346ea-b414-326f-b796-df041038e2e2.bmp[/img]

讲了优点不讲缺点似乎有点不完整:
[b]策略模式的缺点[/b]
1.客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2.造成很多的策略类。
解决方案:采用工厂模式。

[b]4.总结[/b]
1.面向对象基础:抽象,分装,继承,多态
2.目前遇到的3个设计原则:
(1)封装变化,把变化的和不变的分开。
(2)多用组合少用继承。
(3)针对接口编程,不针对实现编程。
3.一个设计模式:策略模式.

[b]主要参考资料:[/b]
《HeadFirst》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值