原文定义:
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变其类【DP】
UML结构图:
背景:
看到此模式,酝酿了好久才决定对状态模式进行总结。光看原文定义,实在没有获取到什么有用的价值。
第一眼看到状态模式,感觉这不就是一个简单工工厂模式吗?但是仔细看看其他人的博客,发现状态模式和简单工厂模式还是有一定的区别的,最明显的是Context类持有了State,这一点和简单工厂区别很大。但是我发现状态模式又和策略模式很像,或者说是太像了。同样有抽象接口,具体实现类,Context上下文类, 以及Context里面也含有一个State抽象,简直一摸一样啊。而且网上其他很多人的博客在解释状态模式的时候,用的分明就是策略模式的例子,真的很无解!
思量再三,对《大话设计模式》中对状态模式进行反复的研究,并且上网找了很多的微博进行阅读和理解,最终才有一点收获,并且再次进行总结。
其实看了很多的微博以后,再次来理解定义的时候,我们还是能获取一点有用的信息的:
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变其类【DP】
1、"对象的内在状态":其实这和我们在策略模式中理解的具体策略,在简单工厂中的具体算法是类似的,称呼不同而已。
2、“当一个对象的内在状态改变时允许改变其行为”: 举例说A对象内部持有的B改变了,此时允许B改变A的具体行为操作,即状态控制行为。
适用性
在下面的两种情况下均可使用State模式:
1) • 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
2) • 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
下面分享我在网上找到的一个关于投票的优秀案例,我对其进行改造,看起来更加的符合状态模式定义:
考虑一个在线投票系统的应用,要实现控制同一个用户只能投一票,如果一个用户反复投票,而且投票次数超过5次,则判定为恶意刷票,要取消该用户投票的资格,当然同时也要取消他所投的票;如果一个用户的投票次数超过8次,将进入黑名单,禁止再登录和使用系统。
要使用状态模式实现,首先需要把投票过程的各种状态定义出来,根据以上描述大致分为四种状态:正常投票、反复投票、恶意刷票、进入黑名单。然后创建一个投票管理对象(相当于Context)。
投票的抽象接口:
package com.sjmx.state; public interface VoteState { /** * 处理状态对应的行为 * * @param user * 投票人 * @param voteItem * 投票项 * @param voteManager * 投票上下文,用来在实现状态对应的功能处理的时候, 可以回调上下文的数据 */ public void vote(String user, String voteItem, VoteManager voteManager,int count); }
具体状态类——正常投票:
package com.sjmx.state; /* * * 具体状态类——正常投票 */ public class NormalVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager,int count) { // 正常投票,记录到投票记录中 if(1 == count){ voteManager.getMapVote().put(user, voteItem); System.out.println("恭喜投票成功,投票内容为:" + voteItem); voteManager.state = new RepeatVoteState(); } } }
具体状态类——重复投票:
package com.sjmx.state; /* * * 具体状态类——重复投票 */ public class RepeatVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager,int count) { // 重复投票,暂时不做处理 if(count > 1 && count < 5){ System.out.println("请不要重复投票,投票内容为:" + voteItem); }else{ voteManager.state = new SpiteVoteState(); voteManager.voteByChange(user, voteItem, count); } } }
恶意投票:
package com.sjmx.state; public class SpiteVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager,int count) { // 恶意投票,取消用户的投票资格,并取消投票记录 if(count >= 5 && count < 8) { System.out.println("count:" + count); String str = voteManager.getMapVote().get(user); if (str != null) { voteManager.getMapVote().remove(user); } System.out.println("你有恶意刷屏行为,取消投票资格!-----投票内容为:" + voteItem); }else{ voteManager.state = new BlackVoteState(); voteManager.voteByChange(user, voteItem, count); } } }
记录黑名单中,禁止登录系统:
package com.sjmx.state; public class BlackVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager,int count) { // 记录黑名单中,禁止登录系统 System.out.println("进入黑名单,将禁止登录和使用本系统,投票内容为:" + voteItem); } }
状态管理类:
package com.sjmx.state; import java.util.HashMap; import java.util.Map; public class VoteManager { // 持有状体处理对象 public VoteState state = null; // 记录用户投票的结果,Map对应Map<用户名称,投票的选项> public MapmapVote = new HashMap (); // 记录用户投票次数,Map 对应Map<用户名称,投票的次数> public MapmapVoteCount = new HashMap (); public VoteManager(String user) { state = new NormalVoteState(); } /** * 获取用户投票结果的Map */ public Map getMapVote() { return mapVote; } /** * 投票 * @param user 投票人 * @param voteItem投票的选项 */ public void vote(String user, String voteItem) { // 1.为该用户增加投票次数 // 从记录中取出该用户已有的投票次数 Integer oldVoteCount = mapVoteCount.get(user); if (oldVoteCount == null) { oldVoteCount = 0; } oldVoteCount += 1; mapVoteCount.put(user, oldVoteCount); this.voteByChange(user, voteItem, oldVoteCount); } public void voteByChange(String user, String voteItem, int count){ // 然后转调状态对象来进行相应的操作 state.vote(user, voteItem, this,count); } }
客户端:
package com.sjmx.state; public class Client { public static void main(String[] args) { VoteManager vm = new VoteManager("jack"); for(int i=1;i<11;i++){ vm.vote("u1","A"+i); } } }
运行结果:
恭喜投票成功,投票内容为:A1 请不要重复投票,投票内容为:A2 请不要重复投票,投票内容为:A3 请不要重复投票,投票内容为:A4 count:5 你有恶意刷屏行为,取消投票资格!-----投票内容为:A5 count:6 你有恶意刷屏行为,取消投票资格!-----投票内容为:A6 count:7 你有恶意刷屏行为,取消投票资格!-----投票内容为:A7 进入黑名单,将禁止登录和使用本系统,投票内容为:A8 进入黑名单,将禁止登录和使用本系统,投票内容为:A9 进入黑名单,将禁止登录和使用本系统,投票内容为:A10
案例2:模拟宾馆预定
package com.net.sjms.state; public interface Destine { public void fixHotel(Hotel hotel, Manager manager); }
package com.net.sjms.state; public class Free implements Destine{ @Override public void fixHotel(Hotel hotel, Manager manager) { if(State.free.toString().equals(hotel.getState())) { System.out.println("房间状态为:" + hotel.getState() + ", 预定成功"); hotel.setState(State.full.toString()); }else { manager.dest = new Full(); manager.desHotel(); } } }
package com.net.sjms.state; public class Full implements Destine { @Override public void fixHotel(Hotel hotel, Manager manager) { if(State.full.toString().equals(hotel.getState())) { System.out.println("对不起,房间已满,无法完成预定!"); }else{ manager.dest = new Free(); manager.desHotel(); } } }
房间状态:
package com.net.sjms.state; public enum State { free,full }
package com.net.sjms.state; public class Hotel { private String name; public String state; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
上下文类:
package com.net.sjms.state; public class Manager { public Destine dest; public Hotel hotel; public Manager() { this.dest = new Free(); this.hotel = new Hotel(); this.hotel.setState(State.free.toString()); } public void desHotel() { dest.fixHotel(hotel,this); } }
客户端:
package com.net.sjms.state; public class Client { public static void main(String[] args) { final Manager manager = new Manager(); //启动另一个线程,模拟一段时间以后,房客会退房 new Thread(new Runnable() { @Override public void run() { while(true) { try { Thread.sleep(500); manager.hotel.setState(State.free.toString()); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); for(int i=0; i <30; i++) { try { manager.desHotel(); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
房间状态为:free, 预定成功
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!
对不起,房间已满,无法完成预定!