状态模式
状态模式归属于行为设计模式,让我们能在一个对象内部状态变化的时候改变其行为,使其看上去像变成了另外一个对象。
以下这张图展示了某视频网站根据用户等级设置允许观看的视频类型
这个场景就十分适合状态模式
不使用状态模式
先来看第一版的不使用状态模式的代码
User
package com.zhima.statepattern.v1;
public class User {
private String name;
private String state;
public void play720PVideo() {
System.out.printf("%s播放720P视频%n", name);
}
public void play1080PVideo() {
if (state.equals("guest")) {
throw new RuntimeException("游客无法播放1080P视频,请注册会员");
}
System.out.printf("%s播放1080P视频%n", name);
}
public void play4KVideo() {
if (!state.equals("vip")) {
throw new RuntimeException("普通会员无法播放4K视频,请充值成为VIP用户");
}
System.out.printf("%s播放4K视频%n", name);
}
public User(String name, String state) {
this.name = name;
this.state = state;
}
public void setState(String state) {
this.state = state;
}
}
Main
package com.zhima.statepattern.v1;
public class Main {
public static void main(String[] args){
User user = new User("芝麻","guest");
user.setState("member");
// user.setState("vip");
testPlayVideo(user);
}
private static void testPlayVideo(User user) {
user.play720PVideo();
user.play1080PVideo();
user.play4KVideo();
}
}
可以看到如果修改了一系列状态的话,需要对User中的if代码块做一系列的判断处理
状态模式优化
下面使用状态模式优化上述方案
UserState
package com.zhima.statepattern.v2;
public abstract class UserState {
protected UserContext userContext;
public void setUserContext(UserContext userContext){
this.userContext = userContext;
}
protected abstract void play720PVideo();
protected abstract void play1080PVideo();
protected abstract void play4KVideo();
}
UserContext
package com.zhima.statepattern.v2;
public class UserContext {
// 用户名
private String name;
private UserState userState;
public UserContext(String name) {
this.name = name;
userState = UserStateConstant.GUEST.getUserState();
userState.setUserContext(this);
}
public void registerMember(){
userState = UserStateConstant.MEMBER.getUserState();
userState.setUserContext(this);
}
public void openVIP(){
userState = UserStateConstant.VIP.getUserState();
userState.setUserContext(this);
}
public void play720PVideo() {
userState.play720PVideo();
}
public void play1080PVideo() {
userState.play1080PVideo();
}
public void play4KVideo() {
userState.play4KVideo();
}
public String getName() {
return name;
}
}
GuestState
package com.zhima.statepattern.v2;
public class GuestUser extends UserState{
@Override
protected void play720PVideo() {
System.out.printf("%s播放720P视频%n",userContext.getName());
}
@Override
protected void play1080PVideo() {
throw new RuntimeException("游客无法播放1080P视频,请注册会员");
}
@Override
protected void play4KVideo() {
throw new RuntimeException("游客无法播放4K视频");
}
}
MemberUser
package com.zhima.statepattern.v2;
public class MemberUser extends UserState{
@Override
protected void play720PVideo() {
System.out.printf("%s播放720P视频%n",userContext.getName());
}
@Override
protected void play1080PVideo() {
System.out.printf("%s播放1080P视频%n",userContext.getName());
}
@Override
protected void play4KVideo() {
throw new RuntimeException("普通会员无法播放4K视频,请充值成为VIP用户");
}
}
VIPMemberState
package com.zhima.statepattern.v2;
public class VIPMemberState extends UserState{
@Override
protected void play720PVideo() {
System.out.printf("%s播放720P视频%n",userContext.getName());
}
@Override
protected void play1080PVideo() {
System.out.printf("%s播放1080P视频%n",userContext.getName());
}
@Override
protected void play4KVideo() {
System.out.printf("%s播放4K视频%n",userContext.getName());
}
}
UserStateConstant
也可以不设计本类,本类主要是考虑复用用户状态类(多线程环境中,需要将UserState中的userContext用ThreadLocal包裹保证线程安全)
package com.zhima.statepattern.v2;
public enum UserStateConstant {
GUEST(new GuestUser()),
MEMBER(new MemberUser()),
VIP(new VIPMemberState());
private UserState userState;
UserStateConstant(UserState userState) {
this.userState = userState;
}
public UserState getUserState() {
return userState;
}
}
Main
package com.zhima.statepattern.v2;
public class Main {
public static void main(String[] args) {
UserContext userContext = new UserContext("芝麻");
testPlayVideo(userContext);
userContext.registerMember();
testPlayVideo(userContext);
userContext.openVIP();
testPlayVideo(userContext);
}
private static void testPlayVideo(UserContext user) {
user.play720PVideo();
user.play1080PVideo();
user.play4KVideo();
}
}
总结
优点
- 减少if语句的嵌套,代码更加优雅
- 将所有与行为相关状态的行为都放在一个类中,方便管理
缺点
- 如果状态很多的话,状态行为类的数量会增加
- 可能会违反了开闭原则,因为增加了状态后,可能要修改上下文对象
与策略模式的区别
- 策略模式是将策略反转给用户决定使用
- 状态模式可以理解为将策略内置在了对象中,根据状态切换不同的执行策略