设计模式-状态模式,装饰模式、桥接模式、备忘录模式练习
最近看了刘伟老师的设计模式史上最全设计模式导学目录(完整版)_刘伟技术博客-CSDN博客学习了里面的状态模式,做一下后面的练习题。处理对象的多种状态及其相互转换——状态模式(六)_刘伟技术博客-CSDN博客
练习
Sunny软件公司欲开发一款纸牌游戏软件,在该游戏软件中用户角色具有入门级(Primary)、熟练级(Secondary)、高手级(Professional)和骨灰级(Final)四种等级,角色的等级与其积分相对应,游戏胜利将增加积分,失败则扣除积分。入门级具有最基本的游戏功能play() ,熟练级增加了游戏胜利积分加倍功能doubleScore(),高手级在熟练级基础上再增加换牌功能changeCards(),骨灰级在高手级基础上再增加偷看他人的牌功能peekCards()。
试使用状态模式来设计该系统。
首先说一下什么是状态模式:
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
然后分析问题,首先有用户对象,用户可以有多个等级,不同等级有不同的功能,用户等级是根据用户积分来确定的,并且下一个等级是在上一个等级基础上增加一定的功能,。 这里针对不同的等级还可以使用装饰模式来实现。这里来说一下装饰模式的概述
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
扩展系统功能——装饰模式(二)_刘伟技术博客-CSDN博客
我们先定义状态接口,状态接口有三个方法,玩游戏方法play, 加分方法addScore,减分方法reduceScore:
package com.pattern.state.practice;public interface CardState {/*** 玩游戏,赢了加分,输了减分* @param winningProbability 胜率,* @return 结果 true 赢,false 输,*/boolean play(Double winningProbability);/*** 加分* @param cardGame* @param score*/void addScore(PlayerGame cardGame, double score);/*** 减分* @param cardGame* @param score*/void reduceScore(PlayerGame cardGame, double score);
}
然后创建用户对象:
package com.pattern.state.practice;public class PlayerGame {private String name;//姓名,private Double score;//积分。private CardState currencyState;//当前状态。private CardState primaryState, secondaryState, professionalState, finalState;//入门级,熟练级,高手级。 小于100入门级,100到500熟练级,500到1000高手级, 大于1000骨灰级private static double winningProbability = 0.5; //赢的概率public PlayerGame(String name, double score) {this.name = name;System.out.println("玩家" + name + "准备开始!");primaryState = new PrimaryState();secondaryState = new SecondaryState();professionalState = new ProfessionalState();finalState = new FinalState();this.addScore(score);}/*** 玩游戏,赢了加分,输了减分* @param amount*/public void play(double amount){boolean result = currencyState.play(winningProbability);if(result){//赢了加分System.out.println(name + "赢了!");currencyState.addScore(this, amount);}else {//输了减分System.out.println(name + "输了!");currencyState.reduceScore(this, amount);}}public void addScore(Double addScore){if(score == null){score = addScore;}else{score += addScore;}//设置状态setState();System.out.println("当前分数为:" + score + " 当前级别为:" + currencyState.getClass().getSimpleName());}private void setState(){if(score < 100){currencyState = primaryState;}else if(score >= 100 && score < 500){currencyState = secondaryState;}else if(score >= 500 && score < 1000){currencyState = professionalState;}else{currencyState = finalState;}}public String getName() {return name;}public void setName(String name) {this.name = name;}public CardState getCurrencyState() {return currencyState;}public void setCurrencyState(CardState currencyState) {this.currencyState = currencyState;}}
然后创建状态类,入门级,熟练级,高手级,骨灰级,状态之间从低到高依次继承上一级。类图表示为:
入门级:
package com.pattern.state.practice;/*** 入门级*/
public class PrimaryState implements CardState{/*** 这里用随机数模拟输赢, 随机数小于胜率则赢。* @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {double random = Math.random();if (random <= winningProbability){return true;}return false;}@Overridepublic void addScore(PlayerGame cardGame, double score) {cardGame.addScore(score);}@Overridepublic void reduceScore(PlayerGame cardGame, double score) {cardGame.addScore(-score);}
}
熟练级:
package com.pattern.state.practice;/*** 熟练级,特有功能赢了积分加倍。* 重新addScore方法,加倍积分*/
public class SecondaryState extends PrimaryState{/*** 启用积分加倍功能* @param cardGame* @param score*/@Overridepublic void addScore(PlayerGame cardGame, double score) {score = doubleScore(score);System.out.println("启用积分加倍功能!加倍积分:" + score);super.addScore(cardGame, score);}/*** 积分加倍功能* @param score* @return*/private double doubleScore(double score){return score * 2;}
}
高手级:
package com.pattern.state.practice;/*** 高手级,增加换牌功能。 增加换牌功能后胜率会提升*/
public class ProfessionalState extends SecondaryState{/*** 这里用装饰模式实现各种技能。* 这里用随机数模拟输赢, 随机数小于胜率则赢。* 计算之前先使用换牌功能* @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {winningProbability = changeCards(winningProbability);return super.play(winningProbability);}/*** 增加换牌功能,胜率增加, 增加幅度控制在0到0.1之间* @param winningProbability* @return*/private double changeCards(double winningProbability){double random = Math.random();winningProbability = winningProbability + random/10;System.out.println("使用换牌功能,胜率增加!" + winningProbability);return winningProbability;}}
骨灰级:
package com.pattern.state.practice;/*** 骨灰级,增加看牌功能*/
public class FinalState extends ProfessionalState{/*** 这里用随机数模拟输赢, 随机数小于胜率则赢。* 计算之前先使用换牌功能** @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {winningProbability = peekCards(winningProbability);return super.play(winningProbability);}/*** 看牌功能, 胜率增加 增加范围是 0 到 0.2* @param winningProbability* @return*/private double peekCards(double winningProbability){double random = Math.random();winningProbability = winningProbability + random/5;System.out.println("使用看牌功能,胜率增加!" + winningProbability);return winningProbability;}
}
然后测试一下:
package com.pattern.state.practice;public class Client {public static void main(String[] args) {PlayerGame cardGame = new PlayerGame("赌神", 500);for (int i = 0; i < 100; i++) {cardGame.play(100);}}
}
运行结果为:
玩家赌神准备开始!
当前分数为:500.0 当前级别为:ProfessionalState
使用换牌功能,胜率增加!0.5916589286277115
赌神输了!
当前分数为:400.0 当前级别为:SecondaryState
赌神输了!
当前分数为:300.0 当前级别为:SecondaryState
赌神赢了!
启用积分加倍功能!加倍积分:200.0
当前分数为:500.0 当前级别为:ProfessionalState
使用换牌功能,胜率增加!0.555551350633584
赌神输了!
当前分数为:400.0 当前级别为:SecondaryState
赌神赢了!
启用积分加倍功能!加倍积分:200.0
当前分数为:600.0 当前级别为:ProfessionalState
使用换牌功能,胜率增加!0.5450863977166052
赌神赢了!
启用积分加倍功能!加倍积分:200.0
当前分数为:800.0 当前级别为:ProfessionalState
使用换牌功能,胜率增加!0.5449084037248595
赌神赢了!
启用积分加倍功能!加倍积分:200.0
当前分数为:1000.0 当前级别为:FinalState
使用看牌功能,胜率增加!0.5324777583421992
使用换牌功能,胜率增加!0.6043353386294867
赌神输了!
思考:实际游戏中,游戏会有多种多样的,玩家也是不同的。如果玩家玩其他游戏,这个模式就不能用了。这里在针对PlayerGame把玩家,和游戏拆分出来,用桥接模式来完成。
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
这里加一个刘伟老师的桥接模式入口:
处理多维度变化——桥接模式(一)_刘伟技术博客-CSDN博客
这样我们把玩家Player,游戏 Game,状态CardState都抽象成接口。
实例如下:玩家
package com.pattern.state.practiceup.abs;
//玩家
public interface Player {void play(double amount);Double getScore();void setScore(Double score);String getName();void setName(String name);Game getGame();void setGame(Game game);CardState getCurrencyState();void setCurrencyState(CardState currencyState);
}
游戏:
package com.pattern.state.practiceup.abs;/*** 游戏,*/
public interface Game {/*** 玩游戏* @param amount* @param player*/void play(double amount, Player player);/*** 加分数* @param addScore* @param player*/void addScore(Double addScore, Player player);
}
状态:
package com.pattern.state.practiceup.abs;public interface CardState {/*** 玩游戏,赢了加分,输了减分* @param winningProbability 胜率,* @return 结果 true 赢,false 输,*/boolean play(Double winningProbability);/*** 加分* @param cardGame* @param player* @param score*/void addScore(Game cardGame, Player player, double score);/*** 减分* @param cardGame* @param player* @param score*/void reduceScore(Game cardGame,Player player, double score);
}
然后针对接口做实现,这样就可以有不同的玩家,玩不同的游戏。
//斗地主游戏
public class CardGame implements Game {private CardState primaryState, secondaryState, professionalState, finalState;//入门级,熟练级,高手级。 小于100入门级,100到500熟练级,500到1000高手级, 大于1000骨灰级private static double winningProbability = 0.5; //赢的概率public CardGame() {primaryState = new PrimaryState();secondaryState = new SecondaryState();professionalState = new ProfessionalState();finalState = new FinalState();}/*** 玩游戏,赢了加分,输了减分* @param amount* @param player*/@Overridepublic void play(double amount, Player player){CardState currencyState = player.getCurrencyState();boolean result = currencyState.play(winningProbability);if(result){//赢了加分System.out.println(player.getName() + "斗地主赢了!");currencyState.addScore(this, player, amount);}else {//输了减分System.out.println(player.getName() + "斗地主输了!");currencyState.reduceScore(this, player, amount);}}@Overridepublic void addScore(Double addScore, Player player){Double score = player.getScore();if(score == null){score = addScore;}else{score += addScore;}CardState currencyState;if(score < 100){currencyState = primaryState;}else if(score >= 100 && score < 500){currencyState = secondaryState;}else if(score >= 500 && score < 1000){currencyState = professionalState;}else{currencyState = finalState;}player.setCurrencyState(currencyState);player.setScore(score);System.out.println(player.getName() + "当前分数为:" + score + " 当前级别为:" + currencyState.getClass().getSimpleName());}}
玩家:
package com.pattern.state.practiceup;import com.pattern.state.practiceup.abs.CardState;
import com.pattern.state.practiceup.abs.Game;
import com.pattern.state.practiceup.abs.Player;public class PlayerGame implements Player {private String name;//姓名,private Double score;//积分。private CardState currencyState;//当前状态。private Game game; //游戏类型。 纸牌,麻将,骨牌,public CardState getCurrencyState() {return currencyState;}public void setCurrencyState(CardState currencyState) {this.currencyState = currencyState;}public PlayerGame(String name, double score, Game game) {this.name = name;this.game = game;game.addScore(score, this);System.out.println("玩家" + name + "准备开始!");}@Overridepublic void play(double amount){game.play(amount,this);}@Overridepublic Double getScore() {return score;}@Overridepublic void setScore(Double score) {this.score = score;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic Game getGame() {return game;}@Overridepublic void setGame(Game game) {this.game = game;}
}
状态:
/*** 入门级*/
public class PrimaryState implements CardState {/*** 这里用随机数模拟输赢, 随机数小于胜率则赢。* @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {double random = Math.random();if (random <= winningProbability){return true;}return false;}@Overridepublic void addScore(Game cardGame, Player player, double score) {cardGame.addScore(score, player);}@Overridepublic void reduceScore(Game cardGame, Player player, double score) {cardGame.addScore(-score, player);}
}/*** 熟练级,特有功能赢了积分加倍。* 重新addScore方法,加倍积分*/
public class SecondaryState extends PrimaryState {/*** 启用积分加倍功能* @param cardGame* @param player* @param score*/@Overridepublic void addScore(Game cardGame, Player player, double score) {score = doubleScore(score);System.out.println("启用积分加倍功能!加倍积分:" + score);super.addScore(cardGame, player, score);}/*** 积分加倍功能* @param score* @return*/private double doubleScore(double score){return score * 2;}
}/*** 高手级,增加换牌功能。 增加换牌功能后胜率会提升*/
public class ProfessionalState extends SecondaryState {/*** 这里用装饰模式实现各种技能。* 这里用随机数模拟输赢, 随机数小于胜率则赢。* 计算之前先使用换牌功能* @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {winningProbability = changeCards(winningProbability);return super.play(winningProbability);}/*** 增加换牌功能,胜率增加, 增加幅度控制在0到0.1之间* @param winningProbability* @return*/private double changeCards(double winningProbability){double random = Math.random();winningProbability = winningProbability + random/10;System.out.println("使用换牌功能,胜率增加!" + winningProbability);return winningProbability;}}/*** 骨灰级,增加看牌功能*/
public class FinalState extends ProfessionalState {/*** 这里用随机数模拟输赢, 随机数小于胜率则赢。* 计算之前先使用换牌功能** @param winningProbability 胜率,* @return*/@Overridepublic boolean play(Double winningProbability) {winningProbability = peekCards(winningProbability);return super.play(winningProbability);}/*** 看牌功能, 胜率增加 增加范围是 0 到 0.2* @param winningProbability* @return*/private double peekCards(double winningProbability){double random = Math.random();winningProbability = winningProbability + random/5;System.out.println("使用看牌功能,胜率增加!" + winningProbability);return winningProbability;}
}
还可以增加一个打麻将游戏:
public class MahjongGame implements Game {private CardState primaryState, secondaryState, professionalState, finalState;//入门级,熟练级,高手级。 小于100入门级,100到500熟练级,500到1000高手级, 大于1000骨灰级private static double winningProbability = 0.5; //赢的概率public MahjongGame() {primaryState = new PrimaryState();secondaryState = new SecondaryState();professionalState = new ProfessionalState();finalState = new FinalState();}/*** 玩游戏,赢了加分,输了减分* @param amount* @param player*/@Overridepublic void play(double amount, Player player){CardState currencyState = player.getCurrencyState();boolean result = currencyState.play(winningProbability);if(result){//赢了加分System.out.println(player.getName() + "打麻将赢了!");currencyState.addScore(this, player, amount);}else {//输了减分System.out.println(player.getName() + "打麻将输了!");currencyState.reduceScore(this, player, amount);}}@Overridepublic void addScore(Double addScore, Player player){Double score = player.getScore();if(score == null){score = addScore;}else{score += addScore;}CardState currencyState;if(score < 100){currencyState = primaryState;}else if(score >= 100 && score < 500){currencyState = secondaryState;}else if(score >= 500 && score < 1000){currencyState = professionalState;}else{currencyState = finalState;}player.setCurrencyState(currencyState);player.setScore(score);System.out.println(player.getName() + "当前分数为:" + score + " 当前级别为:" + currencyState.getClass().getSimpleName());}}
测试一下:
package com.pattern.state.practiceup;import com.pattern.state.practiceup.abs.Game;public class Client {public static void main(String[] args) {//Game game = new CardGame();Game game = new MahjongGame();PlayerGame cardGame = new PlayerGame("赌神", 500, game);for (int i = 0; i < 100; i++) {cardGame.play(100);}}
}
总结:设计模式就是把事务里面固定的东西拿出来,忽略细节,好的设计会让程序更优雅。
再思考一下,玩游戏过程中有可能会输光所有的积分可以用备忘录模式在输光之后返回上一次的状态重新玩。
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
我们创建一个备忘录对象GameMemento:
package com.pattern.state.practiceup;
import com.pattern.state.practiceup.abs.CardState;
import com.pattern.state.practiceup.abs.Player;
/*** 游戏备忘录*/
public class GameMemento {private Double score;//积分。private CardState currencyState;//当前状态。public GameMemento(Player player) {this.score = player.getScore();this.currencyState = player.getCurrencyState();}public Double getScore() {return score;}public void setScore(Double score) {this.score = score;}public CardState getCurrencyState() {return currencyState;}public void setCurrencyState(CardState currencyState) {this.currencyState = currencyState;}
}
再创建一个备忘录责任人对象Caretaker:
package com.pattern.state.practiceup;
/*** 备忘录责任人*/
public class Caretaker {private GameMemento memento;public GameMemento getMemento() {return memento;}public void setMemento(GameMemento memento) {this.memento = memento;}
}
由Player玩家作为原发器, 在Player里面添加一个创建备忘录的方法createMemento();和一个恢复方法restorMemento(GameMemento memento);
package com.pattern.state.practiceup.abs;import com.pattern.state.practiceup.GameMemento;public interface Player {void play(double amount);Double getScore();void setScore(Double score);String getName();void setName(String name);Game getGame();void setGame(Game game);CardState getCurrencyState();void setCurrencyState(CardState currencyState);GameMemento createMemento();void restorMemento(GameMemento memento);}
再由子类实现这两个方法
package com.pattern.state.practiceup;import com.pattern.state.practiceup.abs.CardState;
import com.pattern.state.practiceup.abs.Game;
import com.pattern.state.practiceup.abs.Player;public class PlayerGame implements Player {private String name;//姓名,private Double score;//积分。private CardState currencyState;//当前状态。private Game game; //游戏类型。 纸牌,麻将,骨牌,//这里也可以让玩家持有 备忘录责任人//private Caretaker caretaker = new Caretaker();public CardState getCurrencyState() {return currencyState;}public void setCurrencyState(CardState currencyState) {this.currencyState = currencyState;}@Overridepublic GameMemento createMemento() {return new GameMemento(this);}/*@Overridepublic void restorMemento() {GameMemento memento = caretaker.getMemento();this.score = memento.getScore();this.currencyState = memento.getCurrencyState();} */@Overridepublic void restorMemento(GameMemento memento) {this.score = memento.getScore();this.currencyState = memento.getCurrencyState();}public PlayerGame(String name, double score, Game game) {this.name = name;this.game = game;game.addScore(score, this);System.out.println("玩家" + name + "准备开始!");}@Overridepublic void play(double amount){//caretaker.setMemento(createMemento());game.play(amount,this);}@Overridepublic Double getScore() {return score;}@Overridepublic void setScore(Double score) {this.score = score;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic Game getGame() {return game;}@Overridepublic void setGame(Game game) {this.game = game;}
}
然后测试一下,为了模拟输光的场景,这里吧游戏的获胜概率给调低。MahjongGame.winningProbability=0.1;
package com.pattern.state.practiceup;import com.pattern.state.practiceup.abs.Game;import java.util.Scanner;public class Client {public static void main(String[] args) {//Game game = new CardGame();Game game = new MahjongGame();Caretaker caretaker = new Caretaker();PlayerGame cardGame = new PlayerGame("赌神", 500, game);for (int i = 0; i < 100; i++) {caretaker.setMemento(cardGame.createMemento());cardGame.play(100);Double score = cardGame.getScore();if(score < 0){System.out.println("分数为负数,是否充值一块钱,返回上一次指定分数,输入Y返回,其他退出");Scanner in = new Scanner(System.in);String command = in.next();if("Y".equalsIgnoreCase(command)){//cardGame.restorMemento();System.out.println("充值成功!");cardGame.restorMemento(caretaker.getMemento());}else{System.out.println(cardGame.getName() + "已破产!游戏结束");break;}}}}
}
运行结果:
赌神当前分数为:500.0 当前级别为:ProfessionalState
玩家赌神准备开始!
使用换牌功能,胜率增加!0.13414493470770883
赌神打麻将输了!
赌神当前分数为:400.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:300.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:200.0 当前级别为:SecondaryState
赌神打麻将赢了!
启用积分加倍功能!加倍积分:200.0
赌神当前分数为:400.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:300.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:200.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:100.0 当前级别为:SecondaryState
赌神打麻将输了!
赌神当前分数为:0.0 当前级别为:PrimaryState
赌神打麻将输了!
赌神当前分数为:-100.0 当前级别为:PrimaryState
分数为负数,是否充值一块钱,返回上一次指定分数,输入Y返回,其他退出
y
充值成功!
赌神打麻将输了!
赌神当前分数为:-100.0 当前级别为:PrimaryState
分数为负数,是否充值一块钱,返回上一次指定分数,输入Y返回,其他退出
y
充值成功!
赌神打麻将输了!
赌神当前分数为:-100.0 当前级别为:PrimaryState
分数为负数,是否充值一块钱,返回上一次指定分数,输入Y返回,其他退出
y
充值成功!
赌神打麻将输了!
赌神当前分数为:-100.0 当前级别为:PrimaryState
分数为负数,是否充值一块钱,返回上一次指定分数,输入Y返回,其他退出
t
赌神已破产!游戏结束
这样游戏就达到了赚钱的目的。实现盈利。
最后来一个总的类图

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
