1 概述

1.1 引言

在外出旅游时,很多时候的出行方式都不止一条,通常根据实际情况,比如目的地,预算,旅游时间等确定最适合的出行方式。在软件开发中,也常常会遇到类似的情况,实现某一个功能有多种途径,每一条途径对应一个算法,这时可以使用一种叫做策略模式的设计模式来进行设计。在策略模式中,可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法。

这里每一个封装的算法可以被称之为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类作为规则的定义,每种具体算法对应于一个具体策略类。

策略模式的主要目的是将算法的定义与使用分开,也就是将算法的行为和环境分开,将算法的定义放在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合DIP(依赖倒转原则)。出现新算法时只需要定义一个新的具体策略类即可。

1.2 定义

策略模式:定义一系列算法类,将每一个算法封装起来,并让他们可以相互替换。

策略模式也叫政策模式,是一种对象行为型模式。

1.3 结构图

1.4 角色

  • Context(环境类):使用算法的角色,解决了某个问题时可以采用的多种策略,在环境类维持一个抽象策略类的引用实例,用于定义所采用的策略
  • Strategy(抽象策略类):为支持的算法声明了抽象方法,是所有策略类的父类,可以是抽象类或具体类,也可以是接口,环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法
  • ConcreteStrategy(具体策略类):实现了抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理

2 典型实现

2.1 步骤

  • 定义抽象策略类:一般实现为接口,声明抽象算法
  • 定义具体策略类:实现抽象策略类,实现其中的具体算法
  • 定义环境类:维持一个对抽象策略类的引用,通过setter或构造函数注入具体策略类,调用时通过该抽象引用调用相应算法

2.2 抽象策略类

interface AbstarctStrategy
{void algorithm();
}

这里定义为一个接口,只有一个抽象算法方法。

2.3 具体策略类

class ConcreteStrategy1 implements AbstarctStrategy
{@Overridepublic void algorithm(){System.out.println("具体策略1");}
}class ConcreteStrategy2 implements AbstarctStrategy
{@Overridepublic void algorithm(){System.out.println("具体策略2");}
}

定义两个具体策略类,分别表示不同的算法。

2.4 环境类

class Context
{private AbstarctStrategy strategy;public void setStrategy(AbstarctStrategy strategy){this.strategy = strategy;}public void algorithm(){strategy.algorithm();}
}

通过setter注入具体策略类,在调用环境类的方法时通过抽象策略类调用其中的具体策略类的算法。

2.5 客户端

public static void main(String[] args)
{Context context = new Context();context.setStrategy(new ConcreteStrategy1());context.algorithm();context.setStrategy(new ConcreteStrategy2());context.algorithm();
}

3 实例

设计一个电影票打折系统,有三种不同的打折方式:学生可以享受8折优惠,10周岁以下儿童可以享受减免10元优惠,VIP可以享受半价优惠,使用策略模式进行设计。

设计如下:

  • 环境类:MovieTicket
  • 抽象策略类:Discount
  • 具体策略类:StudentDiscount+VIPDiscount+ChildrenDiscount

首先是抽象策略类:

interface Discount
{double calculate(double price);
}

包含一个计算折扣的方法,接着是具体策略类:

class StudenDiscount implements Discount
{@Overridepublic double calculate(double price){System.out.println("学生票");return price * 0.8;}
}class ChildrenDiscount implements Discount
{@Overridepublic double calculate(double price){System.out.println("儿童票");return price - 10.0;}
}class VIPDiscount implements Discount
{@Overridepublic double calculate(double price){System.out.println("VIP票");return price * 0.5;}
}

三个不同的具体策略类表示三种不同的计算折扣方式,根据需要返回对应的折扣价格。

最后是环境类:

class MovieTicket
{private Discount discount;private double originalPrice;public void setPrice(double price){this.originalPrice = price;}public void setDiscount(Discount discount){this.discount = discount;}public double getDicountPrice(){return discount.calculate(originalPrice);}
}

环境类通过setPrice设定电影票价格后,在通过setDiscount注入具体策略类,最后使用getDiscountPrice获取折扣后的价格。

测试:

public static void main(String[] args)
{MovieTicket movieTicket = new MovieTicket();movieTicket.setPrice(100.0);movieTicket.setDiscount(new StudenDiscount());System.out.println(movieTicket.getDicountPrice());movieTicket.setDiscount(new VIPDiscount());System.out.println(movieTicket.getDicountPrice());movieTicket.setDiscount(new ChildrenDiscount());System.out.println(movieTicket.getDicountPrice());
}

客户端需要明确知道这三种折扣,也就是打折方式由客户端指定,输出如下:

4 JDK中的策略模式

策略模式实用性强,扩展性好,是使用频率较高的设计模式,下面来看看JDK中的典型应用。

Java SE容器布局管理器就是策略模式的一个经典应用案例,基本结构如下:

JavaSE中用户需要对容器对象Container进行布局,在程序运行期间由客户端动态决定一个Container对象如何布局,Java提供了几种不同的布局方式:BorderLayoutFlowLayoutGridLayoutGridBagLayoutCardLayout。在上图结构中:

  • Container充当了环境角色Context
  • LayoutManager充当了抽象策略角色
  • LayoutManager的各个子类充当了具体策略类

Container针对LayoutManager进行编程,无须关心具体布局是什么,这样的设计符合里氏替换原则。

5 与状态模式的不同

状态模式与策略模式很像,下面是两者的结构图:

两者的结构图很相似,但是实际上也有很多的不同:

  • 意图不同:策略模式让客户端指定更换具体策略算法,而状态模式是状态在满足一定条件自动切换,用户无法手动设置状态
  • 负责范围不同:状态模式负责不同状态下对象行为的处理,而策略模式负责具体算法或策略的处理,对于算法来说都有一个明确的目标,都是在做一件事情,比如上面的电影票打折例子,无论选择何种策略,都是为了打折,但是状态模式在不同的状态下做的事情可能不同
  • 封装内容不同:状态模式封装了对象的状态,而策略模式封装了具体的算法或策略
  • 重用性不同:状态是跟对象密切相关的,不能重用,而策略模式的具体策略可以分离出来重用
  • Context的使用不同:状态模式中每个状态持有Context引用,实现状态切换,但是每个策略不持有Context引用,策略只是被Context使用
  • 客户端需要考虑的情况不同:对于状态模式来说,状态模式依赖于其内部状态的变化时内部的行为发生变化,状态是系统自身固有的,由系统本身控制,状态对客户端不透明,客户端不需要考虑系统的状态,也不能直接指定或改变系统的状态切换。但是对于策略模式来说,客户端需要知道所有的策略类,明确各种策略的利弊,对其进行权衡并选择策略,也就是策略需要对客户端透明,需要由客户端考虑使用何种策略

6 主要优点

  • 完美支持OCP:策略模式提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或者行为,也可以灵活提供新的算法或行为
  • 易于管理和复用算法:策略模式提供了管理相关的算法族的办法,策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共代码移到抽象策略类中以简化代码,同时由于算法单独封装在具体策略类中,可以方便复用这些算法
  • 替换继承:策略模式提供了一种替换继承关系的方法,不使用策略模式的话环境类可能有子类,造成算法的使用和定义混在一起,而且使用继承的话无法实现算法或行为在运行时动态切换
  • 避免多重else if:多重选择语句不易维护,因为将选择算法的逻辑以及算法本事实现逻辑混在一起,硬编码在一个巨大的if/else if中,使用策略模式可以避免这种结构

7 主要缺点

  • 策略类需要对客户端透明:客户端必须知道所有的策略类,并自行决定哪一个策略类,也就是客户端需要理解这些算法的区别以便选择适当的算法
  • 策略类数量多:策略模式会造成系统产生很多具体策略类,任何细小的变化都会导致系统增加一个新的具体策略类
  • 客户端无法使用多个策略类:客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩下的功能

8 适用场景

  • 一个系统需要动态在几种算法中选择一种,这些算法类均有统一的接口
  • 一个对象有很多行为,使用策略模式可以将这些行为转移到相应具体策略类中
  • 不希望客户端知道复杂的,与算法相关的数据结构,在具体策略类中对其进行封装

9 总结

设计模式学习笔记(二十四):策略模式相关推荐

  1. 步步为营 .NET 设计模式学习笔记 三、Strategy(策略模式)

    策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化.(原文:The Strategy Pattern defines a fami ...

  2. 【OS学习笔记】十四 保护模式二:段描述符

    上一篇文章初步进入保护模式的学习.首先学习了全局描述符表GDT.点击链接查看上一篇文章:全局描述符表 本篇文章继续学习,GDT中存放的条目:描述符,确切的说是段描述符.学习段描述符的作用以及段描述符的 ...

  3. 设计模式学习笔记二:简单工厂模式

    含义: 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创 ...

  4. Mr.J-- jQuery学习笔记(二十四)--剖析jQuery源码--extend

    定义和用法 jQuery.extend() 函数用于将一个或多个对象的内容合并到目标对象. 注意:1. 如果只为$.extend()指定了一个参数,则意味着参数target被省略.此时,target就 ...

  5. [傅里叶变换及其应用学习笔记] 二十四. 级联,脉冲响应

    我们上节课学习了 在离散有限维空间中,任何线性系统都是通过矩阵间的相乘得到的 在连续无限维空间中,任何线性系统都是通过对核函数的积分得到的 脉冲响应(impulse response) 级联线性系统( ...

  6. java沙盒模式_JavaScript学习笔记(二十五) 沙箱模式

    沙箱模式(Sandbox Pattern) 沙箱模式可以避免命名空间的一些缺点(namespacing pattern),比如: 依赖一个唯一全局的变量作为程序的全局符号.在命名空间模式中,没有办法存 ...

  7. 立创eda学习笔记二十四:拼板

    这里主要是两部分:自带拼板和手动拼板,软件自带拼板功能,那么手动拼板当然就是自己重新画图拼板了. 一般用自带拼板功能就可以了,把单板画好之后很容易就拼好了,完全不用动任何器件和丝印编号,单板会被理解成 ...

  8. 【OS学习笔记】十六 保护模式四:进入保护模式与在保护模式下访问内存的汇编代码

    本文记录的是之前四篇文章所对应的汇编代码.四篇文章分别是: [OS学习笔记]十二 现代处理器的结构和特点 [OS学习笔记]十三 保护模式一:全局描述符表(GDT) [OS学习笔记]十四 保护模式二:段 ...

  9. Windows保护模式学习笔记(十四)—— 阶段测试

    Windows保护模式学习笔记(十四)-- 阶段测试 题目一 解题步骤 题目二 解题步骤 题目一 描述:给定一个线性地址,和长度,读取内容 int ReadMemory(OUT BYTE* buffe ...

  10. QT学习笔记(十四):QLayout的属性介绍

    QT学习笔记(十四):QLayout的属性介绍 主要包括QBoxLayout.和QGridLayout以及QFormLayout等的参数类似. 我主要说明一下QGridLayout在QtDesigne ...

最新文章

  1. 《晓肚知肠:肠菌的小心思》荣获“2018年度中国好书”奖
  2. java伪协议_JavaScript中伪协议 javascript:使用探讨
  3. javascript总结9:JavaScript三目运算符
  4. java 课后习题 三角形面积计算
  5. Spring核心知识
  6. kettlejava脚本的api_kettle java api 执行 Kettle 的作业和 转换
  7. 第十届泰迪杯数据挖掘B题:电力系统负荷预测分析--解题思路与部分代码01
  8. 如何显示或隐藏mac文件后缀名
  9. 把一个人的特点写具体作文_五年级把一个人的特点写具体作文500字5篇
  10. 常用贷款词汇英文名称
  11. 周末作业-循环练习题(1)
  12. 豆瓣电台WP7客户端 开发记录2
  13. Lattice系列FPGA入门相关0(Lattice与Altera、Xilinx对比及入门)
  14. MySQL内部搜索引擎
  15. 菜鸟看马云卸任以及未来的阿里巴巴
  16. mysql 阿里插件_MySQL插件调用
  17. java 加密指定位数字符串
  18. 4月1-5号在武汉举行第五届全国Revit开发中高级实战训练营
  19. 程序是为了让人更省事
  20. Android生成二维码--拍照或从相册选取图片

热门文章

  1. imfilter使用方法
  2. 软件测试修炼之道(转载)
  3. Cocoapods安装与SVN使用
  4. Web3中文|“你们眼中的互联网革命,是我生活的日常”
  5. Pwn_7 ROP (2)
  6. 【实训01】基于视图的访问控制
  7. (快速幂算法+高精度)洛谷P1045 麦森数
  8. redis缓存技术学习 www.iigrowing.cn/redis-huan-cun-ji-shu-xue-xi.html
  9. 黑猴子的家:Ubuntu下载
  10. python 决策_Python 卡方决策