《Head first设计模式》之策略模式
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
假设有一个模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个游戏的内部设计了一个鸭子超类Duck,并让各种鸭子继承此超类。
public abstract class Duck {
public void quack(){
// 所有的鸭子都会呱呱叫,由Duck类负责实现
}
public void swim(){
// 所有的鸭子都会游泳,由Duck类负责实现
}
public abstract void display(); // 每个鸭子的子类负责实现自己的display
}
现在要增加一个功能,让鸭子能飞。
实现方法1:在Duck类里加上fly()方法
只需要在Duck类中加上fly()方法,所有的鸭子都会继承fly()。
但是问题来了,并非所有的子类都会飞,比如橡皮鸭子就不能飞。在超类中加上fly(),就会导致所有的子类都具备fly(),连那些不该具备fly()的子类也无法免除。
对代码所做的局部修改,影响层面可不只是局部!
实现方法2:把fly()从超类中取出来
我们可以把fly()从超类中取出来,放入一个“Flyable接口”中。这么一来,只有会飞的鸭子才能实现此接口。同样的方式,也可以用来设计一个“Quackable接口”,因为不是所有的鸭子都会叫。
这也是个超笨的主意,这样一来重复的代码会变多。如果有48个Duck的子类都要稍微修改一下飞行的行为,需要修改48个Duck的子类的代码。
虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用。
设计原则:找出应用中可能出现变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
分开变化和不会变化的部分
就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常。现在,为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck类),一个是“fly”相关的,一个是“quack”相关的,每一组类将实现各自的动作。
设计鸭子的行为
如何设计那组实现飞行和呱呱叫的行为的类呢?
我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们应该在鸭子类中包含设定行为的方法,这样可以实现在“运行时”动态地“改变”鸭子的飞行行为。
设计原则:针对接口编程,而不是针对实现编程。
// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark();
// 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound();
// 更棒的是,子类实例化的动作不再需要在代码中硬编码,
// 例如new Dog(),而是“在运行时才指定具体实现的对象”
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();
我们利用接口代表行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中一个接口。
所以这次鸭子类不会负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就成为“行为”类。
这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现。这两种做法都是依赖于“实现”,我们被“实现”绑得死死的,没办法更改行为。
在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。
实现鸭子的行为
在此,我们有两个接口,FlyBehavior与QuackBehavior,还有对应的子类,负责实现具体的行为:
FlyWithWings继承自FlyBehavior,实现鸭子飞行;
FlyNoWay继承自FlyBehavior,实现不会飞的鸭子的行为;
Quack继承自QuackBehavior,实现鸭子呱呱叫;
Squeak继承自QuackBehavior,实现橡皮鸭子吱吱叫;
MuteQuack继承自QuackBehavior,实现不会叫的鸭子的行为。
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。这么一来,有了继承的“复用”好处,却没有继承所带来的包袱。
而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为
在Duck类中加入两个实例变量,分别为“flyBehavior”与“quackBehavior”,声明为接口类型,每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型。
我们用两个相似的方法performFly()与performQuack()取代Duck类中的fly()与quack()。
实现performQuack():
// 在这部分代码中,我们不在乎quackBehavior接口的对象到底是什么,
// 我们只关心该对象知道如何进行呱呱叫就够了。
public class Duck {
// 每只鸭子都会引用实现QuackBehavior接口的对象
QuackBehavior quackBehavior;
public void performQuack(){
// 鸭子对象不亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象
quackBehavior.quack();
}
}
设定quackBehavior的实例变量:
public class MallardDuck extends Duck {
public MallardDuck(){
// 绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用时,
// 叫的职责被委托给Quack对象,而我们得到了真正的呱呱叫
quackBehavior = new Quack();
}
}
同样的处理方式也可以用在飞行行为上。
组合
每一个鸭子都有一个FlyBehavior和QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。
当你将两个类结合起来使用,如同本例一般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当地行为对象“组合”来的。
设计原则:多用组合,少用继承。
使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
完整的代码
// Duck超类
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
public void swim(){
System.out.println("All ducks float, even decoys!");
}
}
// MallardDuck类
public class MallardDuck extends Duck {
public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
// 飞行行为接口
public interface FlyBehavior {
public void fly();
}
// 飞行行为类
public class FlyWithWings implements FlyBehavior {
public void fly(){
System.out.println("I'm flying!!");
}
}
// 飞行行为类
public class FlyNoWay implements FlyBehavior {
public void fly(){
System.out.println("I can't fly");
}
}
// 叫声行为接口
public interface QuackBehavior {
public void quack();
}
// 叫声行为类
public class Quack implements QuackBehavior{
public void quack(){
System.out.println("quack");
}
}
// 叫声行为类
public class MuteQuack implements QuackBehavior{
public void quack(){
System.out.println("<< silence >>");
}
}
// 叫声行为类
public class Squeak implements QuackBehavior{
public void quack(){
System.out.println("squeak");
}
}
// 测试类
public class MiniDuckSilmulator{
public static void main(String[] args){
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
}
《Head first设计模式》之策略模式的更多相关文章
- 设计模式:策略模式(Strategy)
定 义:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化, 不会影响到使用算法的客户. 示例:商场收银系统,实现正常收费.满300返100.打8折.......等不同收费 ...
- PHP设计模式之策略模式
前提: 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查 找.排序等,一种常用的方法是硬编码(Hard Cod ...
- JavaScript设计模式之策略模式(学习笔记)
在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选 ...
- 乐在其中设计模式(C#) - 策略模式(Strategy Pattern)
原文:乐在其中设计模式(C#) - 策略模式(Strategy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 策略模式(Strategy Pattern) 作者:webabc ...
- JavaScript设计模式之策略模式
所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...
- 【设计模式】【应用】使用模板方法设计模式、策略模式 处理DAO中的增删改查
原文:使用模板方法设计模式.策略模式 处理DAO中的增删改查 关于模板模式和策略模式参考前面的文章. 分析 在dao中,我们经常要做增删改查操作,如果每个对每个业务对象的操作都写一遍,代码量非常庞大. ...
- [design-patterns]设计模式之一策略模式
设计模式 从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景.那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不 ...
- 设计模式入门,策略模式,c++代码实现
// test01.cpp : Defines the entry point for the console application.////第一章,设计模式入门,策略模式#include &quo ...
- 设计模式之策略模式和状态模式(strategy pattern & state pattern)
本文来讲解一下两个结构比较相似的行为设计模式:策略模式和状态模式.两者单独的理解和学习都是比较直观简单的,但是实际使用的时候却并不好实践,算是易学难用的设计模式吧.这也是把两者放在一起介绍的原因,经过 ...
- python设计模式之策略模式
每次看到项目中存在大量的if else代码时,都会心生一丝不安全感. 特别是产品给的需求需要添加或者更改一种if条件时,生怕会因为自己的疏忽而使代码天崩地裂,哈哈,本文的目的就是来解决这种不安全感的, ...
随机推荐
- NTT - 牛客
链接:https://www.nowcoder.com/acm/contest/133/D来源:牛客网 题目描述 Applese打开了m个QQ群,向群友们发出了组队的邀请.作为网红选手,Applese ...
- Hive 这些基础知识,你忘记了吗?
Hive 其实是一个客户端,类似于navcat.plsql 这种,不同的是Hive 是读取 HDFS 上的数据,作为离线查询使用,离线就意味着速度很慢,有可能跑一个任务需要几个小时甚至更长时间都有可能 ...
- 想玩转JAVA高并发,这些概念你必须懂!
我们在找工作时,经常在招聘信息上看到有这么一条:有构建大型互联网服务及高并发等经验,你第一时间想到的是媒体常说的双十一吗?带着问题,我们一起思考技术…. 高并发高并发 它是互联网分布式系统架构设计中必 ...
- Java框架之SpringMVC 04-视图解析-Spring表单-JSON-上传下载
SpringMVC 视图解析 请求处理方法(controller方法)执行完成后,最终返回一个 ModelAndView 对象,即使出现异常也会返回一个 ModelAndView 对象.对于那些返回 ...
- spring boot 整合 swagger2
swagger2为了更好的管理api文档接口 swagger构建的api文档如下,清晰,避免了手写api诸多痛点 一,添加依赖 <!--swagger2的官方依赖--> <depen ...
- 关于SpringDataJpa中测试出现StackOverflowError错误问题
在使用SpringDataJpa进行多表查询时,使用导航查询,每次都出现 StackOverflowError错误, 经过查找资料,网上百度,终于找到原因, StackOverflowError 是栈 ...
- Installing PyCharm
Installing PyCharm| # ...
- kuangbin专题专题十一 网络流 POJ 1087 A Plug for UNIX
题目链接:https://vjudge.net/problem/POJ-1087 题目:有n个插座,插座上只有一个插孔,有m个用电器,每个用电器都有插头,它们的插头可以一样, 有k个插孔转化器, a ...
- c#数字图像处理(四)线性点运算
灰度图像的点运算可分为线性点运算和非线性点运算两种.4.1线性点运算定义线性点运算就是输出灰度级与输入灰度级呈线性关系的点运算.在这种情况下,灰度变换函数的形式为: g(x, y)=pf(x,y)+L ...
- linux--->配置lamp环境(centos7 最小版)
这篇博客写的很全,按照顺序敲代码即可 参考:https://www.cnblogs.com/me80/p/7218883.html