《Head First 设计模式》(点击查看详情

1、写在前面的话

之前在列书单的时候,看网友对于设计模式的推荐里说,设计模式的书类别都大同小异,于是自己就选择了Head First系列,基于之前读过《Head First Java》,被文中生动的图片和讲解所吸引,相信这本书也能带给我更好的体验,关键在于,设计模式作为Java中更巧妙的部分,需要的不光是知道它是什么,更应该去理解为什么,才能更好地去掌握和应用,而Head First系列图书正是为了如此。

对于这本书的读书笔记,就以每个设计模式来分章进行记录吧,好了,不多叨叨,正式进入读书学习章节吧。

2、策略模式

我们知道,单纯利用继承来提供父类其下子类的各种行为,是难以维护的,我们无法确保所有子类的行为。比如我们有一只鸭子Duck作为父类,写下了fly()方法,可是涉及到例如玩具鸭ToyDuck作为子类的时候,它并不会飞啊?

那么这样,我们干脆把一些方法独立出来做成接口,比如一个Flyable的接口,让会飞的鸭子去实现它,玩具鸭不实现它不就行了,那么这个设计怎么样?

然而这个方法也并不是那么好,接口的方式虽然可以解决部分问题,可是接口不具有实现代码,这样一来,我们对于fly()就无法达到代码复用的目的,这意味着,如果某时我们需要修改一个方法(比如飞行是利用翅膀,随着科技化现在鸭子是用火箭飞行了),你必须从接口往下追踪并修改每个定义该行为的类,不仅工作量重复,更可能不小心造成新的错误。

2.1 剥离变化之处

为了解决这种问题,我们在设计之初要利用好一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起 
1
 
1
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起 
(这个概念很简单,却几乎是每个设计模式的精神所在,所有模式都提供了一套方法让“系统中某部分改变不会影响其他部分”)

我们回到刚才的鸭子案例,随着鸭子的不同fly()可能不同,那么我们把它提取出来,建立新的类(接口)来代表这个行为


2.2 接口编程,独立实现

为了动态地决定鸭子的飞行行为,我们要利用到第二个设计原则:
针对接口编程,而不是针对实现编程
1
 
1
针对接口编程,而不是针对实现编程

我们利用接口来代表行为,比如FlyBehavior接口,这一次,我们不让鸭子类来实现这个接口,而是专门制造一组其他类来实现接口,这个类我们称之为“行为类”,即由行为类实现接口而不是Duck类来实现。这样一来,“实现”不必死死地捆绑在子类中。

这样的好处在于:
  • 动作可以被其他对象复用,因为独立为动作,而跟鸭子类无关了,比如麻雀也可以使用fly()了
  • 我们可以新增行为,且不影响既有行为类,也不影响使用飞行行为的鸭子类

2.3 整合

做完这些,你可能要问了,行为既然委托给别人了,不在鸭子身上了,那么我们如何整合鸭子的行为呢?

  • 将剥离部分作为实例变量,加入到类中(即把FlyBehavior接口作为变量设置到Duck类中)
  • 设置类似原来fly()的方法performFly(),调用已经作为实例变量的接口中定义的方法fly()
public class Duck {
protected FlyBehavior flyBehavior;
private String name;
private int size; public String performFly() {
return flyBehavior.fly();
} public String swim() {
return "Swimming ~ ~ ~";
}
}
13
 
1
public class Duck {
2
    protected FlyBehavior flyBehavior;
3
    private String name;
4
    private int size;
5

6
    public String performFly() {
7
        return flyBehavior.fly();
8
    }
9

10
    public String swim() {
11
        return "Swimming ~ ~ ~";
12
    }
13
}

  • 其他类实现行为接口
//通过翅膀飞行
public class FlyingByWings implements FlyBehavior { public String fly() {
return "Flying By Wings~";
}
}
7
 
1
//通过翅膀飞行
2
public class FlyingByWings implements FlyBehavior {
3

4
    public String fly() {
5
        return "Flying By Wings~";
6
    }
7
}
//通过火箭飞行
public class FlyingByRocket implements FlyBehavior { public String fly() {
return "Flying By super-cool Rocket!!!";
}
}
7
 
1
//通过火箭飞行
2
public class FlyingByRocket implements FlyBehavior {
3

4
    public String fly() {
5
        return "Flying By super-cool Rocket!!!";
6
    }
7
}

  • 子类初始化时设定想要的实例变量

public class RocketDuck extends Duck {

    public RocketDuck() {
flyBehavior = new FlyingByRocket();
} }
7
 
1
public class RocketDuck extends Duck {
2

3
    public RocketDuck() {
4
        flyBehavior = new FlyingByRocket();
5
    }
6

7
}

  • 编辑测试类
public class Test {
public static void main(String[] args) {
RocketDuck rocketDuck = new RocketDuck();
System.out.println(rocketDuck.performFly());
}
}
6
 
1
public class Test {
2
    public static void main(String[] args) {
3
        RocketDuck rocketDuck = new RocketDuck();
4
        System.out.println(rocketDuck.performFly());
5
    } 
6
}
 
另外,我们还可以在父类Duck中对行为接口设置setter,这样一来,我们随时都可以动态地设定方法了!

public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
3
 
1
public void setFlyBehavior(FlyBehavior flyBehavior) {
2
    this.flyBehavior = flyBehavior;
3
}
 

3、模式总结

回忆一下我们从最开始的想法到现在的方式:
  • 最开始,我们直接让子类继承父类来复用其方法,但是子类不同对于方法也不同,类的行为划分不清,同时方法覆盖的方式并不灵活;
  • 然后,改变成通过实现接口来实现行为,行为划分清晰,可无法使用代码复用,后期维护困难;
  • 最终,我们利用接口和多态原理,剥离行为,让一些专门的类来实现行为接口,让不同的子类去初始化不同的行为(实现类调用接口引用),完美解决了问题。

第三种的方式优秀在于,父类是 “有一个行为类型”,这种和继承不同的是,行为不是继承而来的,而是和适当的对象 “组合” 而来的,这里,也利用到了我么要说到的第三个设计模式:

多用组合,少用继承
1
 
1
多用组合,少用继承

最后,我们来看看策略模式的正儿八经的定义吧:

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。
x
 
1
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
2

3
策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。
 
其适用性常在于:
  • 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种;
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 / 时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式;
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构;
  • 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

策略模式的场景示例:游戏中,每个角色只能使用一种武器,在游戏过程中可以更换武器:

 


4、本文涉及的设计原则汇总

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承

5、更多相关好文推荐


6、文章示例源码下载


《Head First 设计模式》[01] 策略模式的更多相关文章

  1. python设计模式之策略模式

    每次看到项目中存在大量的if else代码时,都会心生一丝不安全感. 特别是产品给的需求需要添加或者更改一种if条件时,生怕会因为自己的疏忽而使代码天崩地裂,哈哈,本文的目的就是来解决这种不安全感的, ...

  2. 设计模式:策略模式(Strategy)

    定   义:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化, 不会影响到使用算法的客户. 示例:商场收银系统,实现正常收费.满300返100.打8折.......等不同收费 ...

  3. PHP设计模式之策略模式

    前提: 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查 找.排序等,一种常用的方法是硬编码(Hard Cod ...

  4. JavaScript设计模式之策略模式(学习笔记)

    在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选 ...

  5. 乐在其中设计模式(C#) - 策略模式(Strategy Pattern)

    原文:乐在其中设计模式(C#) - 策略模式(Strategy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 策略模式(Strategy Pattern) 作者:webabc ...

  6. JavaScript设计模式之策略模式

    所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...

  7. 【设计模式】【应用】使用模板方法设计模式、策略模式 处理DAO中的增删改查

    原文:使用模板方法设计模式.策略模式 处理DAO中的增删改查 关于模板模式和策略模式参考前面的文章. 分析 在dao中,我们经常要做增删改查操作,如果每个对每个业务对象的操作都写一遍,代码量非常庞大. ...

  8. [design-patterns]设计模式之一策略模式

    设计模式 从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景.那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不 ...

  9. 设计模式入门,策略模式,c++代码实现

    // test01.cpp : Defines the entry point for the console application.////第一章,设计模式入门,策略模式#include &quo ...

  10. 设计模式之策略模式和状态模式(strategy pattern & state pattern)

    本文来讲解一下两个结构比较相似的行为设计模式:策略模式和状态模式.两者单独的理解和学习都是比较直观简单的,但是实际使用的时候却并不好实践,算是易学难用的设计模式吧.这也是把两者放在一起介绍的原因,经过 ...

随机推荐

  1. SpringMVC 文件上传(Multipart)

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 平时用的上传一般是输入流和输出流上传,今天使用的是transferTo方法: Multipart请求是在控制器实例 ...

  2. 编译64位cu文件的设置

     作者:朱金灿 来源:http://blog.csdn.net/clever101 CUDA(ComputeUnified Device Architecture),是显卡厂商NVIDIA推出的运 ...

  3. CADO SAP tcode - Time Sheet: Display Data

    CADO (Time Sheet: Display Data) is a standard SAP transaction code available within R/3 SAP systems ...

  4. 项目报错:Caused by: java.lang.ClassNotFoundException: Didn't find class "..."on path: DexPathList

    项目报错: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.eshore.njb.MyApplicat ...

  5. 并发容器(四)ConcurrentHashMap 深入解析(JDK1.6)

      这篇文章深入分析的是 JDK1.6的 ConcurrentHashMap 的实现原理,但在JDK1.8中又改进了 ConcurrentHashMap 的实现,废弃了 segments.虽然是已经被 ...

  6. go语言浮点数

    package main import "fmt" func main() { var num,num1 float32 num = 10 num1 = 100 fmt.Print ...

  7. SQL Server中数据库文件的存放方式,文件和文件组 (转载)

    简介 在SQL SERVER中,数据库在硬盘上的存储方式和普通文件在Windows中的存储方式没有什么不同,仅仅是几个文件而已.SQL SERVER通过管理逻辑上的文件组的方式来管理文件.理解文件和文 ...

  8. 使用动态SQL创建数据库

    /*其实我也搞不懂为什么要用SQL来创建,明明SQL Server有图形化创建数据库多省事啊!*/USE master; ​DECLARE @sqlstr nvarchar(max)/*定义一个变量* ...

  9. MySQL案例08:MySQL Scheduler Events带来的风险

    定时任务是我们开发.运维人员经常用到的,比如cron,job,schedule,events scheduler等都是为了方便我们重复执行某项工作而无需人工参与而设计,这里我要说的是MySQL数据库本 ...

  10. 从零起步做到Linux运维经理,你必须管好的23个细节

    不想成为将军的士兵,不是好士兵-拿破仑 如何成为运维经理?成为运维经理需要什么样的能力?我想很多运维工程师都会有这样的思考和问题. 如何成为运维经理.一般来说,运维经理大概有两种出身,一种是从底层最基 ...