Head First设计模式——策略设计模式
策略设计模式
说在前面的话
入软件一年啦,平心而论,总算不限于只会钻研些基础的语言语法了,数据结构和算法也恶补的差不多了。所以~趁着现在一边实习一边啃《Head First设计模式》的功夫,怀着一颗敬畏的心决定经营自己的博客,以后呢,也会把这件事坚持下去并作为鞭策自己的一个方法吧O(∩_∩)O~~。24岁的编程巨婴的第一篇博客,在此就立个flag:为了怕以后自己的博客流于平庸,所有的内容只限“有感而发”,仅仅将自己有所思考的东西才会总结成博客,而不是教科书般的全部列出。另外以后的话,如果有引用别人的段落或思想,我也会特别标注出来,愿自己不忘初心喽~
什么是设计模式
记得半年前,我还仅仅限于学了个java的程度的时候,去选了学校朱老师的“设计模式”课程,对软件开发一无所知的我,去试听了第一节课就被处事严谨的朱老师给吓退了,后来当我了解到设计模式有多么重要之后,也是很后悔自己当时的退选。
言归正传:
设计模式是一种编程思想的总结,是一种抽象的、为了提高代码可靠性阅读性等等而被提取并反复使用的规范。
说白了,设计模式就是前人将编程及软件工程中的问题总结成的一套套的解决办法
策略模式解决什么问题
那么,策略设计模式要解决的问题是什么呢?
在代码的维护中,超类中封装了公有方法,在维护的过程中,可能发生如下的问题:
- 需要增加新的方法、实例域。
- 需要对已有的方法或者实例域进行修改。
- 我们可不可以在不改变对象自身的情况下改变对象的行为?
而如果直接在超类中修改,很有可能发生如下问题:所有不具有该方法的子类,都需要重写这个方法。而这个重写带来的可能的恶果是:
- 可能重写的代价很大,方法很复杂,换句话说,我们要对每个方法的实现细节了如指掌。
- 大大降低了代码复用性。
- 方法被写死在超类中,“硬编码”。耦合太严重,牵一发而动全身。
而从另外一个角度,如果定义成接口,把具体需要改变的方法“抽离”出来,需要该方法的子类特定地去实现接口,然后重写接口中的方法。这样做看似解决了问题,但是却带来了更为严重的后果:
- 由于接口中都是抽象方法,代码无法复用,极大地增大了代码量,每一个实现类都需要重写接口中的方法。
如何解决?
虽然简单地使每个子类特异性地去实现其所需要的功能的接口被证明是不可行的,但是在这里,超类已经不再与特定地功能“硬编码在一起”。这为我们提供了一种思路:解除类与特定“行为”的耦合性。
那么,策略设计模式是如何实现解耦的呢?
简单来讲,策略设计模式将类中可变的“行为”高度提取到一个个接口中,对于每一个接口,创建实现该行为的不同实现类。到这里,完成了可变部分的封装。接下来,在超类中,添加“行为”的成员变量,其声明是接口(利用多态性,后面会详述),并添加相应的setter用于动态地改变“行为”,写相应的方法调用接口成员的方法完成具体行为。
举例说明
就以书上的例子,简单说明下。
超类:Duck(鸭子)行为:fly()飞行,display()自我介绍,swim()游泳,quack()嘎嘎叫
public class Duck{
public void swim(){...}
public void fly(){...}
public void quack(){...}
public void display(){...}
}
子类:DecoyDuck诱饵鸭,MallardDuck野鸭,ModelDuck模型鸭,RedHeadDuck红头鸭,RubberDuck橡胶鸭。
1. 提取可变部分
在鸭子的三种行为中,所有鸭子都会游泳swim()属于公共部分(display()方法后面会讲到)。而fly()与quack()对于不同的鸭子有不同的结果,所以我们将二者分别提取到两个接口FlyBehavior和QuackBehavior中,并分别在其中声明了抽象方法fly()和quack(),以FlyBehavior为例:
FlyBehavior .java
public interface FlyBehavior {
public void fly();
}
飞行行为FlyBehavior是对鸭子的飞行行为的高度概括,具体到每一个鸭子,行为会有不同的表现,新建两个实现类:鸭子会飞(FlyWithWings),鸭子不会飞(FlyNoWay):
FlyNoWay.java & FlyWithWings.java
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
2. 耦合进Duck类中
好了,我们已经成功地将飞行行为与嘎嘎叫行为封装到了一个接口的实现关系里,接下来要做的,就是在我们的Duck类中如何耦合进具体的行为。先看看代码吧~
Duck.java:
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public void setFlyBehavior (FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
首先注意到Duck类是一个抽象类,这一点并**非必须**的。此处由于每一个Duck的子类都需要进行特异性的“自我介绍”,所以这里选择了在每个子类中覆盖而不是抽象出具体的“自我介绍接口”。从这里我们也可以稍微看出来一些策略设计模式的缺点哈(索性在这说得了O(∩_∩)O~):
- 如果我们的飞行行为有很多很多种的话,那么我们不得不创建很多很多实现类。
- 在我们新建一个新的鸭子子类的时候,我们必须知道这个子类它具体使用的是哪个行为。
所以为了避免不得不new 很多“自我介绍接口”的实现类,我们直接选择将display()方法声明为abstract,具体就交由子类去覆盖。可见,设计模式并非万能的,我们要根据需要进行取舍!
回到正题,我们先去找我们耦合进去的行为,不难找到如下两个行为:
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
注意到这里利用多态声明为接口,熟悉java就不难理解其中的好处,不再赘述,详情可见《Head First设计模式》Page 12。
对比之前Duck中直接调用fly()的方式,这里我们调用flyBehavior接口的实现对象的fly()方法,并将其封装进新的方法performFly()中:
public void performFly() {
flyBehavior.fly();
}
这也就意味着!只要我们可以动态地改变flyBehavior,那么绑定在对象上的行为就可以改变。幸运的是,我们可以这么做:
public void setFlyBehavior (FlyBehavior fb) {
flyBehavior = fb;
}
setter方法中再次用到了多态,不再赘述。
看到这里大家可能稍微有点蒙,我们不妨跳出来看一下。
开始,我们给所有的鸭子都绑定了一个方法,fly(),并告诉它们:You all can fly!,然后橡皮鸭子就跳出来了:No,I can't ! 然而我们说,对不起,这事我管不了,你自己看着办吧,橡皮鸭表示好吧,那我自己整个方法覆盖一下。结果后来鸭子阵容越来越庞大,需要覆盖的越来越多,代码混乱而庞杂。就有鸭子不满了,说,都怪父类,非得在自个身上搞个什么fly()方法,大家变种那么多,能一样吗。不如,我们把这些东西抽离出吧。于是,一个虚拟的概念“飞行行为”(接口)出现了,鸭子们为了区分,定义了两个“飞行行为”的实现类:会飞,不会飞。事情终于简单了,每个鸭子有自己的“飞行行为胸牌”(FlyBehavior成员),橡皮鸭拿了“我不会飞贴纸”(实例化)贴在胸牌上,大家就都知道它不会飞了,别的鸭子也有自己的胸牌和贴纸。突然有一天,橡皮鸭被改造,身上装了一个火箭发射器,OK,橡皮鸭拿了新的“我会用火箭发射器飞贴纸”(调用setter方法)贴在了胸牌上。
综上所述,我们将变化的部分抽离出来,定义了专门的算法族并分别封装起来,让算法的变化独立于算法的客户。
3.子类实现
子类覆盖Duck,需初始化FlyBehavior,QuackBehavior两个成员,覆盖display()方法。以红头鸭为例:
RedHeadDuck.java
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
public void display() {
System.out.println("I'm a real Red Headed duck");
}
}
最后提一下书中提到的三个设计原则:
- 封装变化
- 针对接口编程,不针对实现编程
- 多用组合,少用继承
回答书中问题
《Head First设计模式》第12页提出了一个问题:
“我不懂你为什么非要把FlyBehavior设计成接口,为何不使用抽象类,这样不就可以使用多态了吗”
开始我也是一脸懵逼,因为我没有读懂书中自带的解释,读了很多遍之后,才明白书在跟我们玩文字游戏:
书中的“接口”并不是我们所理解的接口,而是一种“概念”,代表着可以实现多态的所有supertype(超类型),基本上,按照书中的概念,超类型该如此划分:
超类型————
|————>普通类
|————>抽象超类型
|————>接口
|————>抽象类
所以书中的意思是
- 根据设计原则二,最好使用接口。
- 使用抽象类亦可,但最好不要使用有非抽象方法的抽象类,因为万一某个子类没有覆盖,根据多态会自动调用父类。
- 不推荐使用普通超类,因为最好在使用具体子类对象的时候再实例化,不要在超类中硬编码。
Comparator,Comparable接口与策略设计模式
最后,稍微提一下Comparator、Comparable接口中用到的策略设计模式思想。我们以TreeMap类为例。
首先仅仅看名字,我们就会觉得Comparable应该是个形容词,那么我们就应该理解为“某个对象具有比较的能力”,可以等同于“给Duck的每个子类继承各自的Flyable接口”,也就是说,归根结底,是这个类具有比较的能力。
然后Comparator是个名词“比较器”,这也就意味着,你可以实现我,但是,你本身也必须是个比较器!可以等同于“FlyNoWay实现了FlyBehavior”。
TreeMap底层采用红黑树实现,里面所有的key-value对都是按照key的值排序的。而这个排序的规则来自于其内部成员Comparator,而这个Comparator在外部定义,TreeMap内部无需编写任何比较规则,只需拿外部Comparator对象来用即可。如果要修改TreeMap的比较规则,只需在外部写一个写的Comparator实现类,然后调用构造方法
TreeMap(Comparator<? super K> comparator)
传入即可。
而如果我们让TreeMap实现Comparable接口,那就意味着TreeMap本身具有比较的能力,我们需要在TreeMap内部重写Comparable接口的compareTo()方法,如果我们要修改我们的比较规则,我们能直接修改TreeMap的底层代码吗?当然不能!
所以如果我们想在不改变对象自身的情况下改变对象的行为,我们就要使用策略设计模式,定义外部类去修改!
另外,Comparator接口中是compare(a,b),因为是外部定义的比较器类,所以要传入两个值到比较器,Comparable接口中是compareTo(a),是直接那对象本身与另外一个值比较,所以只需传入一个值。
另外,Collections类里面的很多方法,如:
static <T extends Comparable<? super T>> void sort(List<T> list)
我们可以看出一个问题:虽然对于TreeMap,TreeSet,我们需要修改比较规则,使用策略设计模式,但是如果我们对一个List中所有的元素T进行排序的时候,我们只需T是一个内部定义好比较方法的对象即可,所以这里用的是Comparable。
最后
还是希望自己能坚持吧。
2016.9.29
Head First设计模式——策略设计模式的更多相关文章
- Head First 设计模式 —— 策略设计模式
创建一个能够根据所传递的参数对象的不同而具有不同行为(动态绑定的多态机制)的方法,被称为策略设计模式.
- 15. 星际争霸之php设计模式--策略模式
题记==============================================================================本php设计模式专辑来源于博客(jymo ...
- Java(Android)编程思想笔记02:组合与继承、final、策略设计模式与适配器模式、内部类、序列化控制(注意事项)
1.组合和继承之间的选择 组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式的做. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形.即在新类中嵌入某个对象,让其实 ...
- [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型)
[.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它 ...
- Java设计模式之策略设计模式
1.什么是-策略设计模式 在软件开发中常常遇到这种情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Ha ...
- linkin大话设计模式--策略模式
linkin大话设计模式--策略模式 Strategy [ˈstrætədʒi] 策略 策略模式用于封装系列的算法,这些算法通常被封装在一个称为Context的类中,客户端程序可以自由的选择任何一种 ...
- [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型)
[.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模 ...
- java策略设计模式
1.概述 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化. 其实不要被晦涩难懂的定义所迷惑,策略设计模式实际上就是定义一个接口,只要实现 ...
- 编写JDBC框架:(策略设计模式)
package com.itheima.domain; //一般:实体类的字段名和数据库表的字段名保持一致 //约定优于编码 public class Account { private int id ...
随机推荐
- SQLCMD的介绍
原文:SQLCMD的介绍 sqlcmd -S SERVERNAME -U USERNAME -P PASSWORD -i filename.sql 下面的内容是详细介绍sqlcmd的,有兴趣的朋友可以 ...
- Asp.Net MVC页面静态化功能实现一:利用IHttpModule和ResultFilter
由于公司现在所采用的是一套CMS内容管理系统的框架,所以最近项目中有一个需求提到要求实现页面静态化的功能.在网上查询了一些资料和文献,最后采用的是小尾鱼的池塘提供的 利用ResultFilter实现a ...
- ODBC操作数据库
/*ODBC使用步骤:(ODBC数据源由微软平台提供) * 1.配置ODBC数据源(控制面板->管理工具->ODBC数据源) * 2.加载并注册驱动程序,导入java.sql.*包 * 3 ...
- SpringMVC格式化显示
SpringMVC学习系列(7) 之 格式化显示 在系列(6)中我们介绍了如何验证提交的数据的正确性,当数据验证通过后就会被我们保存起来.保存的数据会用于以后的展示,这才是保存的价值.那么在展示的时候 ...
- 在线web编辑器
真正在线编辑的在线web编辑器 最近正在研究开发一款在线web编辑器架构,这是一款真正傻瓜式的web编辑器,可以在正常浏览页面的情况进行编辑,经过测试,对于一般网页页面来说非常好用方便,操作更简单. ...
- Web API实现POST报文的构造与推送
ASP.NET Web API实现POST报文的构造与推送 毕设和OAuth协议相关,而要理解OAuth协议就必须理解HTTP GET/POST方法.因此研究了一下如何使用Web API或MVC构 ...
- T4 Template Overview
T4 Template Overview T4 Template的组成 指令区:为模板转换引擎提供指令,控制模板如何被处理 template:模板相关的属性,debug是否可以调试:hos ...
- 监控系统Opserver
监控系统Opserver的配置调试 Stack Exchange开源其监控系统Opserver有一段时间了.之前在项目中用过他们的MiniProfile来分析页面执行效率和帮助新人了解项目,当他们 ...
- smartcn与IKanalyzer
开源中文分词框架分词效果对比smartcn与IKanalyzer 项目背景: 某银行呼叫中心工单数据挖掘和分析项目,旨在利用文本计算实现热点聚焦和舆情分析. 一.引言: 中文分词一直是自然语言处理的一 ...
- Java读书笔记1
Java逍遥游记读书笔记 前言 必须先来一句,这是入门级别,高手勿喷~ 写Android的时候总有一些语句不是很理解,其实大部分是Java的内容,所以想系统的学下Java. 这本书——<Java ...