10.1 策略模式

  Strategy 的意思是 “策略”,指的是与敌军对垒时行军作战的方法。在编程中,我们可以将其理解为 “算法”。
  无论什么程序,其目的都是解决问题。而为了解决问题,我们需要编写特定的算法。使用 Stratety 模式可以整体地替换算法的实现部分。能够整体地替换算法,能让我们轻松地以不同的算法去解决同一个问题,这种模式就是 Stratety 模式。

10.2 示例程序

  示例程序的功能是让电脑玩 “猜拳” 游戏。我们考虑了两种猜拳策略。

  • 第一种策略是 “如果这局猜拳获胜,那么下一局也出一样的手势”,这是一种有些笨的策略
  • 另外一种策略是 “根据上一局的手势从概率上计算出下一局的手势”。

各类的功能如下:

类名 功能
Hand 表示猜拳游戏中的 “手势” 类
Strategy 表示猜拳游戏中策略的类
Player 表示进行游戏猜拳的选手
WinningStrategy 第一种策略类
ProbStrategy 第二种策略类

Strategy 示例程序类图

|| Hand 类

  Hand 类表示猜拳游戏中 “手势” 的类。在其内部,用 int 表示所出的手势, 0 > 石头、1 > 剪刀、2 > 布。
   Hand 类的实例可以通过使用类方法 getHand 来获取。只要将表示手势的值作为参数传递给 getHand 方法,它就会将手势的值所对应的 Hand 类的实例返回给我们,这是一中 Singleton 模式。
  isStrongerThan 方法和 isWeakerThan 方法用于判断猜拳结果。类中,实际负责判断猜拳结果的是 fight 方法,其判断依据是手势的值。其中 (this.handValue + 1) % 3 == h.handValue 的意思是,手势值 + 1 后与 3 的余数是否与需要比较的值相同(如果当前是石头,需要比较的值是剪刀,石头 + 1 后与 3 的余数是剪刀,剪刀 == 剪刀成立,但石头是赢剪刀的,所以这里返回 1 胜)。
  虽然 Hand 类会被其他类使用,但其并非 Strategy 模式中的角色。

/**
* 猜拳游戏中的手势类.
*/
public class Hand {private static int HAND_VALUE_GUU = 0; // 石头private static int HAND_VALUE_CHO = 1; // 剪刀private static int HAND_VALUE_PAA = 2; // 布public static Hand[] hand = {new Hand(HAND_VALUE_GUU),new Hand(HAND_VALUE_CHO),new Hand(HAND_VALUE_PAA)};private static final String[] name = {"石头", "剪刀", "布"};private int handValue; // 猜拳手势值private Hand(int value) {this.handValue = value;}public static Hand getHand(int handValue) {return hand[handValue];}public boolean isStrongerThan(Hand h) { // 是否获胜return fight(h) == 1;}public boolean isWeakerThan(Hand h) { // 是否输了return fight(h) == -1;}private int fight(Hand hand) { // 0:平,1:胜,-1 负if (this == hand) {return 0;}// 石头 > 剪刀, 剪刀 > 布, 布 > 石头// 如果当前是剪刀,对方出的布,则 + 1 除3余数与布相当,返回1,胜if ((this.handValue + 1) % 3 == hand.handValue) {return 1;} else {return -1;}}@Overridepublic String toString() {return name[handValue];}
}
|| Strategy 接口

  Strategy 接口定义了猜拳策略的抽象方法的接口。
  nextHand 方法的作用就是 “获取下一局要出的手势”。调用了该方法后,实现了 Strategy 接口的实现类会想出下一局出什么手势。
  study 方法的作用是学习 “上一局的手势是否获胜了”。如果在上一局中调用 nextHand 方法获胜了,接着就会调用 study(true),反之,则传入 false。

/**
* 定义了猜拳策略的抽象方法接口.
*/
public interface Strategy {// 获取下一个手势的方法Hand nextHand();// 学习上一个手势是否获胜的方法,便于计算策略void study(boolean isWin);
}
|| WinningStrategy 类

  具体的猜拳策略实现类:如果上一局手势获胜了,则下一局出相同的手势,否则,则随机出一个手势。

/**
* 猜拳策略:如果上一局获胜,则继续出一样的手势,如果失败,则随机出手势.
*/
public class WinningStrategy implements Strategy{private Random rand;private boolean won = false;private Hand prevHand;public WinningStrategy(int seed) {rand = new Random(seed);}@Overridepublic Hand nextHand() {if (!won) {prevHand = Hand.getHand(rand.nextInt(3));}return prevHand;}@Overridepublic void study(boolean isWin) {won = isWin;}
}
|| ProbStrategy 类 [权重分配]

  ProbStrategy 类是另外一个具体的策略,虽然与 WinningStrategy 类一样,也是随机出手势,但是每种手势出现的概率会根据以前的猜拳结果而改变。
  history 字段是一个表,被用于根据过去的胜负来进行概率计算。它是一个二维数组,每个数组下标的意思如下。
  history[上一局出的手势][这一局出的手势]
  这个表达式的值越大,表示过去的胜利越高。
  例如,假如我们上一局出的是石头

  • history[0][0] 两局分别出石头、石头时胜了的次数
  • history[0][1] 两局分别出石头、剪刀时胜了的次数
  • history[0][2] 两局分别出石头、布 时胜了的次数

  那么我们就可以根据这3个表达式的值从概率上计算出下一局出什么。简而言之,就是先计算 3 个表达式的值的和,然后再从 0 与这个和之间取一个随机数,并据此决定下一局应该出什么。例如:

  • history[0][0] 是 3
  • history[0][1] 是 5
  • history[0][1] 是 7

  那么,下一局出 石头、剪刀、布的比率就是 3:5:7 来决定。然后在 0 至 15 之间取一个随机数,根据所在区间来选择对应的手势。

/**
* 策略:根据手势赢面的权重,生成不同的手势.
* 手势出现的概率歌剧以前猜拳的概率结果而改变
*/
public class ProbStrategy implements Strategy{private Random rand;private int prevHandValue = 0;private int currentHandValue = 0;// history[0][0] - 两次分别出 石头 石头 的获胜次数// history[0][1] - 两次分别出 石头 剪刀 的获胜次数// history[0][2] - 两次分别出 石头 布   的获胜次数// 同理还有 剪刀 、 布 的情况private int[][] history = {{1,1,1},{1,1,1},{1,1,1}};public ProbStrategy(int seed) {this.rand = new Random(seed);}@Overridepublic Hand nextHand() {// 根据权重范围,挑选随机值所在的区间,获取手势int bet = rand.nextInt(getSum(currentHandValue));int handValue;if (bet < history[currentHandValue][0]) {handValue = 0;} else if (bet < (history[currentHandValue][0] + history[currentHandValue][1])) {handValue = 1;} else {handValue = 2;}prevHandValue = currentHandValue;currentHandValue = handValue;return Hand.getHand(currentHandValue);}// 计算当前猜拳情况下各可能的获胜总和// 比如当前是1,则计算 1 0 , 1 1 , 1 2 的总获胜次数private int getSum(int hv) {int sum = 0;for (int i = 0; i < 3; i++) {sum += history[hv][i];}return sum;}@Overridepublic void study(boolean isWin) {if (isWin) {history[prevHandValue][currentHandValue]++; // 获胜,则当前记录 + 1,否则另外两种都加1} else {history[prevHandValue][(currentHandValue + 1) % 3]++;history[prevHandValue][(currentHandValue + 2) % 3]++;}}
}
|| Player 类

  Player 类是表示进行猜拳游戏的选手的类。生成时,需要向其传递 “姓名” 和 “策略”。包含了胜、负和平局的处理方法。

/**
* 猜拳参赛选手.
*/
public class Player {private String name;private Strategy strategy;private int winCount;private int loseCount;private int gameCount;public Player(String name, Strategy strategy) {this.name = name;this.strategy = strategy; // 策略赋予}public Hand nextHand() {return strategy.nextHand();}public void win() {strategy.study(true);winCount++;gameCount++;}public void lose() {strategy.study(false);loseCount++;gameCount++;}public void even() {gameCount++;}@Overridepublic String toString() {return "[" + name + ":" + gameCount + " games," + winCount + " win," + loseCount + " lose" + "]";}
}
|| Main 类

  负责使用以上类让电脑进行猜拳游戏。进行比赛,然后显示比赛结果。

public class Main {public static void main(String[] args) {if (args.length != 2) {System.out.println("Usage: java Main randomSeed randomSeed2");System.out.println("Example: java Main 314 15");System.exit(0);}int seed1 = Integer.parseInt(args[0]);int seed2 = Integer.parseInt(args[1]);Player taro = new Player("Taro", new WinningStrategy(seed1));Player hana = new Player("Hana", new ProbStrategy(seed2));for (int i = 0; i < 10000; i++) {Hand taroHand = taro.nextHand();Hand hanaHand = hana.nextHand();if (taroHand.isStrongerThan(hanaHand)) {System.out.println("Winner:" + taro);taro.win();hana.lose();} else if (taroHand.isWeakerThan(hanaHand)) {System.out.println("Winner:" + hana);taro.lose();hana.win();} else {System.out.println("Even...");taro.even();hana.even();}}System.out.println("Total result:");System.out.println(taro);System.out.println(hana);}
}

运行结果:

...
Winner:[Hana:9993 games,3488 win,3165 lose]
Winner:[Taro:9994 games,3165 win,3489 lose]
Winner:[Taro:9995 games,3166 win,3489 lose]
Winner:[Hana:9996 games,3489 win,3167 lose]
Even...
Even...
Even...
Total result:
[Taro:10000 games,3167 win,3490 lose]
[Hana:10000 games,3490 win,3167 lose]

10.3 Strategy 模式中的登场角色

  ◆ Strategy (策略)
  Strategy 角色负责实现策略所必须的接口(API)。在示例程序中,由 Strategy 接口扮演此角色。
  ◆ ConcreteStrategy (具体的策略)
  ConcreteStrategy 角色负责实现 Strategy 角色的接口,即负责实现具体的策略(战略、方法、算法)。在示例程序中由 WinningStrategy 和 ProbStrategy 类扮演此角色。
  ◆ Context (上下文)
  负责使用 Strategy 角色。Context 角色保存了 ConcreteStrategy 角色的实例,并使用 ConcreteStrategy 角色去实现需求(总之还是需要调用 Strategy 角色的接口)。在示例程序中,由 Player 类扮演此角色。

Strategy 模式UML图

10.4 拓展思路的要点

|| 为什么需要特意编写 Strategy 角色

  通常在编程时算法会被写在具体的方法中。Strategy 模式却特意的将算法与其他部分分离开来,只是定义了与算法相关的接口,然后在程序中以委托的方式来使用算法。
  这样看起来好像程序变复杂了,其实不然。例如,我们想要通过改善算法来提高算法的处理速度时,如果使用了 Strategy 模式,就不必修改 Strategy 角色的接口了。仅仅修改 ConcreteStrategy 角色即可。而且,使用委托这种弱关系可以很方便地整体替换算法。
  比如,使用 Strategy 模式编写象棋程序时,可以方便地根据棋手的选择切换 AI 例程的水平。

|| 程序运行时也可以切换策略

  如果使用 Strategy 模式,在程序运行时也可以切换 ConcreteStrategy 角色。例如,在内存容量少的运行环境中使用速度慢但节约内存的策略,内存容量多的运行环境中则可以使用 速度快但耗内存的策略。
  还可以使用某种算法来 “验算” 另外一种算法。例如,某一个 高速但可能存在 Bug 的算法 和 低速但计算准确的算法,然后让后者去验算前者的计算结果。
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

Strategy (策略)模式相关推荐

  1. Java的设计模式----strategy(策略模式)

    设计模式: 一个程序员对设计模式的理解: "不懂"为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的"复杂"恰恰就是设计模式的精 ...

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

    目录 前言 1 定义 2 适用性 3 结构 3.1 结构图 3.2 参与者 4 实际应用举例 4.1 Context--List列表和StuContext 4.2 具体策略:ConcreteStrat ...

  3. [设计模式] —— Strategy 策略模式

    文章目录 Strategy 策略模式 动机 定义 示例代码 结构图 总结 Strategy 策略模式 组件协作模式通过晚绑定,来实现框架与应用程序之间的松耦合.是框架和引用程序协作常用的. 动机 某些 ...

  4. 【设计模式】2.Strategy 策略模式

    Strategy 策略模式 动机(Motivation) 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂:而且有时候支持不使用的算法也 ...

  5. 设计模式之Strategy策略模式

    文章目录 前言 一.Strategy策略模式 二.策略模式原则 三.使用场景 1.先做比较练习 2.为一组对象排序 3.使用Lambda表达式的方式 前言 本人对于设计模式的学习,仅供参考! 一.St ...

  6. Strategy策略模式

    策略模式定义了一系列算法,把它们一个个封装起来,并且使它们可相互替换.该模式可使得算法能独立于使用它的客户而变化.Strategy模式是行为模式,正因为他是一种行为模式,所以他不是用来解决类的实例化的 ...

  7. 设计模式学习笔记--Strategy 策略模式

    所谓策略模式(Strategy Pattern),就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用:如果将策略.具体的算法和行为,编码在某个类或客户程序内部,将导至 ...

  8. Strategy 策略模式

    意图 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换.本模式使得算法可独立于使用它的客户而变化. 动机 策略模式为了适应不同的需求,只把变化点封装了,这个变化点就是实现不同需求的算法, ...

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

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

  10. 12种行为模式 之2 STRATEGY 策略模式

    [b]策略模式的组成[/b] 1):抽象策略角色: 通常由一个接口或者抽象类实现. 2):具体策略角色:包装了相关的算法和行为. 3):环境角色:持有一个策略类的引用,最终给客户端(上层模块)调用. ...

最新文章

  1. 重磅!2020年度国家杰青、优青填报界面取消“论文收录与被引统计表”
  2. 流水线经典讲解!!!!!
  3. 项目不需要SVN控制的时候,该怎么办
  4. 对数组下面的数组截取_numpy数组不同索引方式的区别
  5. 《Go语言程序设计》 读书笔记 (八) 包
  6. 工作的准备:atoi,itoa,strcpy,memcpy,strcmp,二分查找,strcat
  7. 关于在阅读nnUNet代码中的一些小细节的记录(二)
  8. Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程
  9. MUI全国城市区县级联json转换sql建表
  10. Android 最新所有框架
  11. 基于麒麟座开发板2.0的MQTT实现例程
  12. 常用的14个获取数据的网站。
  13. 【英语】动词时态与语态
  14. 文件夹如何去除SVN的标记符号
  15. 程序猿职业规划-分析篇
  16. CMD实用指令汇总收集(持续更新中)
  17. 优秀的持久层框架-Mybatis(上)
  18. CodeBlocks调试简要教程
  19. 企业涉及有外资的ICP许可证怎么办理?能不能办理?今天这篇文章讲解下外商投资经营电信业务申请的注意事项。
  20. 读书笔记 | 理解现代经济学

热门文章

  1. 老年手机英文改中文_老年人使用智能手机(九)不会英语不用怕,装一款软件就能出国啦...
  2. 搜狐号按作者火车头采集规则
  3. Unable to install breakpoint in
  4. 【LeetCode】﹝并查集ி﹞连通分量个数(套用模板一直爽)
  5. [网络验证破解]某外挂验证转本地化
  6. WPF使用Live Chart之动态更新数据
  7. 人工智能专家:AI并不像你想象的那么先进
  8. GPU Skinning旋转指定骨骼
  9. 5G中传和回传的承载解决方案
  10. 视频监控系统上云解决方案EasyCVR集成海康EHome私有协议系列——文件查找操作流程