设计模式:Strategy模式

策略模式,类似算法,属于对象的行为模式,针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换,使得算法可以在不影响到客户端的情况下发生变化

下面有一个用了策略模式的例子,有点复杂,功能是让电脑玩猜拳游戏,有点自我学习的感觉

这里考虑两种猜拳的策略:

第一种策略是如果这局猜拳获胜,那么下一局也出一样的手势,WinningStrategy

第二种策略是根据上一局的手势从概率上计算出下一局的手势,ProbStrategy

相关类和接口如下:

Hard:表示猜拳游戏中的手势的类

Strategy:表示猜拳游戏中的策略的类

WinningStrategy:表示如果这局猜拳获胜,那么下一局也出一样的手势这一策略的类

ProbStrategy:表示根据上一局的手势从概率上计算出下一局的手势从之前的猜拳结果计算下一局出各种拳的概率,这一策略的类

Player:表示进行猜拳游戏的选手的类

Main:main,测试类

Hard类,表示猜拳游戏中手势的类

HANDVALUE_XXX表示所出的手势,0表示石头,1表示剪刀,2表示布

创建3个Hand类的实例,并保存在hand数组里

name表示猜拳中手势对应的字符串

handValue表示猜拳中手势的值

getHand返回Hand类的实例;将表示手势的值作为参数传递给getHand,就会将手势的值所对应的Hand类的实例返回

isStrongerThan方法和isWeakerThan方法用于判断猜拳结果,比如手势hand1和hand2,通过hand1.isStrongerThan(hand2)和hand1.isWeakerThan(hand2)来判断猜拳结果;在该类的内部,最终调用的是fight方法,判断依据是手势的值

因为石头剪刀布,石头WIN剪刀,剪刀WIN布,布WIN石头,对应就是0WIN1,1WIN2,2WIN0,那么当(this + 1) % 3 == h,那么this比h更Strong;this == h,无视;当(this + 1) % 3 != h,那么this比h更Weak

Hard类

package strategy;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Hand
* Author: lihui
* Date: 2018/5/6 14:19
*/

public class Hand {
public static final int HANDVALUE_GUU = 0;
public static final int HANDVALUE_CHO = 1;
public static final int HANDVALUE_PAA = 2;
public static final Hand[] hand = {
new Hand(HANDVALUE_GUU),
new Hand(HANDVALUE_CHO),
new Hand(HANDVALUE_PAA),
};
private static final String[] name = {
" 石头 ",
" 剪刀 ",
" 布 ",
};
private int handValue;

private Hand(int handValue) {
this.handValue = handValue;
}

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;
}

public int fight(Hand h) {
if (this == h) {
return 0;
} else if ((this.handValue + 1) % 3 == h.handValue) {
return 1;
} else {
return -1;
}
}

public String toString() {
return name[handValue];
}
}

Strategy接口,定义了猜拳策略的抽象方法的接口

nextHand方法作用是获取下一局要出的手势,调用该方法后,实现了Strategy接口的类会思考下一局出什么手势

study方法的作用是学习上一局的手势是否获胜了;如果上一局中调用nextHand方法获胜了,就接着调用study(true),如果输了,就接着调用study(false);这样Strategy接口的实现类就会改变自己的内部状态,从而为下一次nextHand被调用时究竟是返回石头剪刀布提供判断依据

package strategy;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Strategy
* Author: lihui
* Date: 2018/5/6 14:20
*/

public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}

WinningStrategy类,实现了Strategy接口的nextHand和study两个方法

这个类的猜拳策略比较笨,如果上一局手势WIN,那么下一局手势继续出上一局的手势;如果上一局手势输了,下一局手势随机

由于WinningStrategy类中需要使用到随机数,因此在random字段中保存Random实例,random字段是类的一个随机数生成器

在won字段中保存上一局猜拳的输赢结果;如果上一局猜拳获胜,won值为true;如果输了,won值为false

prevHand字段中保存上一局出的手势

package strategy;

import java.util.Random;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: WinningStrategy
* Author: lihui
* Date: 2018/5/6 14:20
*/

public class WinningStrategy implements Strategy {
private Random random;
private boolean won = false;
private Hand prevHand;
public WinningStrategy(int seed) {
random = new Random(seed);
}

public Hand nextHand() {
if (!won) {
prevHand = Hand.getHand(random.nextInt(3));
}
return prevHand;
}

public void study(boolean win) {
won = win;
}
}

ProbStrategy类,另一种具体策略,虽然和WinningStrategy类一样,随机出手势,但是每种手势出现的概率会根据之前猜拳的结果而改变

history字段是一个表,被用于根据过去的胜负来进行概率计算,二维数组:

history[上一局出的手势][这一局出的手势]

这个表达式的值越大,表示过去的胜率越高

假如上一局出的是石头:

history[0][0]:两局分别出石头,石头时胜了的次数

history[0][1]:两局分别出石头,剪刀时胜了的次数

history[0][2]:两局分别出石头,布胜了的次数

可以根据history[0][0],history[0][1],history[0][2]这3个表达式的值从概率上计算出下一局出什么,也就是计算这三个表达式值的和getSum方法,再从0与这个和之间取一个随机数,并据此来决定下一局应该出什么,nextHand方法

比如

history[0][0] = 3

history[0][1] = 5

history[0][2] = 7

那么下一局具体出什么,会以石头,剪刀,布的比率3:5:7来决定,和为15,所以就在0~14之间取一个随机数

如果该随机数在0~2之间,那么出石头

如果该随机数在3~7之间,那么出剪刀

如果该随机数在8~14之间,那么出布

study方法会根据nextHand方法返回的手势的胜负结果来更新history字段中的值

ProbStrategy类:

package strategy;

import java.util.Random;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: ProbStrategy
* Author: lihui
* Date: 2018/5/6 14:21
*/

public class ProbStrategy implements Strategy {
private Random random;
private int prevHandValue = 0;
private int currentHandValue = 0;
private int[][] history = {
{ 1, 1, 1, },
{ 1, 1, 1, },
{ 1, 1, 1, },
};
public ProbStrategy(int seed) {
random = new Random(seed);
}

public Hand nextHand() {
int bet = random.nextInt(getSum(currentHandValue));
int handValue = 0;
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(handValue);
}

private int getSum(int hv) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += history[hv][i];
}
return sum;
}

public void study(boolean win) {
if (win) {
history[prevHandValue][currentHandValue]++;
} else {
history[prevHandValue][(currentHandValue + 1) % 3]++;
history[prevHandValue][(currentHandValue + 2) % 3]++;
}
}
}

Player类,表示进行猜拳游戏选手的类

创建Player类的实例,需要传姓名和策略两个参数

nextHand方法用来获取下一局手势的方法,但实际上决定下一局手势的是各个策略

Player类的nextHand方法的返回值就是策略的nextHand方法的返回值;nextHand方法将自己的工作委托给Strategy,这就形成了一种委托关系

在决定下一局要出的手势时,需要知道之前各局的胜负平,也就是win,lose,even结果,因此Player类会通过strategy字段调用study方法,然后study方法会改变策略的内部状态

winCount,loseCount,gameCount记录选手的猜拳结果

package strategy;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Player
* Author: lihui
* Date: 2018/5/6 14:21
*/

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++;
}

public String toString() {
return "[" + name + ":" + gameCount + " game, " + winCount + " win, " + loseCount + " lose" + "]";
}
}

Main类,负责使用以上类让电脑进行猜拳游戏,两位选手进行100局比赛,然后显示结果

姓名:Taro,策略WinningStrategy

姓名:Hana,策略ProbStrategy

Main:

package strategy;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Main
* Author: lihui
* Date: 2018/5/6 14:21
*/

public class Main {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: java Main randomseed1 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 player1 = new Player("Taro", new WinningStrategy(seed1));
Player player2 = new Player("Hana", new ProbStrategy(seed2));
for (int i = 0; i < 100; i++) {
Hand nextHand1 = player1.nextHand();
Hand nextHand2 = player2.nextHand();
if (nextHand1.isStrongerThan(nextHand2)) {
System.out.println("Winner:" + player1);
player1.win();
player2.lose();
} else if (nextHand1.isWeakerThan(nextHand2)) {
System.out.println("Winner:" + player2);
player1.lose();
player2.win();
} else {
System.out.println("Even...");
player1.even();
player2.even();
}
}
System.out.println("Total result:");
System.out.println(player1.toString());
System.out.println(player2.toString());
}
}

 

策略模式的角色:

Strategy:策略,例子中的Strategy接口;负责决定实现策略所必需的接口

ConcreteStrategy:具体的策略,例子中的WinnerStrategy类和ProbStrategy类;负责实现具体的策略(战略,方向,方法和算法)

Context:上下文,例子中的Player类;保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求

 

特意编写Strategy角色:

Strategy模式将算法和其它部分分离开来,只定义了与算法相关相关的接口,然后在程序中以委托的方式来使用算法;假如我们需要通过改善算法来提高算法的处理性能时,如果使用了Strategy模式,就不需要修改Strategy角色的接口,仅仅修改ConcreteStrategy角色即可,而且使用委托这种弱关联关系可以很方便地整体替换算法

 

发表回复