状态模式
# 状态模式
状态模式通过将对象的行为封装在不同的状态对象中,使得对象能够根据内部状态的改变而改变行为,从而实现了对象的状态和行为的松耦合。
# 定义
状态模式是一种行为设计模式,它允许对象在内部状态发生改变时改变其行为,看起来好像是对象在运行时改变了其类。状态模式将对象的行为封装在不同的状态对象中,使得对象根据其当前状态而具有不同的行为。
状态模式和策略模式的区别
状态模式(State Pattern)和策略模式(Strategy Pattern)的主要区别在于它们关注的问题领域和解决的问题类型。状态模式关注对象内部的状态变化,策略模式关注选择和切换不同的算法或策略。
状态模式关注对象的内部状态的变化和行为的变化,它通过封装状态为独立类来实现状态之间的切换。而策略模式关注选择和切换不同的算法或策略,它通过封装算法或策略为独立类来实现策略的选择和切换。
# 结构
上下文(Context):上下文是拥有状态的对象,它维护一个对抽象状态对象的引用,并在运行时根据当前状态调用相应的方法。上下文类提供了一些公共方法,供客户端代码调用以改变状态。
抽象状态(Abstract State):抽象状态是一个抽象类或接口,定义了状态对象的共同接口和方法。它包含了在不同状态下可能发生的行为,并定义了在上下文中触发状态转换的方法。
具体状态(Concrete State):具体状态是抽象状态的子类,它实现了抽象状态定义的方法,并根据当前状态下的需求提供具体的行为。每个具体状态类负责管理在特定状态下对象的行为。
- img: https://bitouyun.com/images/design-pattern/state.png
link: https://bitouyun.com/images/design-pattern/state.png
name: 状态模式
2
3
# 优点
提高可维护性:状态模式将每个状态的行为封装在具体状态类中,使得每个状态都可以独立变化,易于添加新的状态或修改现有状态的行为。
提高可扩展性:由于状态的变化和行为的变化被封装在具体状态类中,因此在需要添加新的状态或修改现有状态的行为时,不需要修改上下文类的代码,符合开闭原则。
消除条件语句:状态模式通过将状态的判断逻辑转移到状态类内部,减少了在上下文类中使用大量的条件语句,提高了代码的可读性和可维护性。
易于理解和调试:状态模式将状态转换逻辑集中到具体状态类中,使得状态的转换变得可见和可控,易于理解和调试。
# 缺点
增加了类和对象的数量:引入状态模式会增加具体状态类的数量,可能会导致类的数量增加,从而增加系统的复杂性。
状态切换的开销:在状态模式中,状态的切换需要在上下文类和具体状态类之间进行通信,可能会增加一定的开销,特别是当状态比较多或者状态切换比较频繁时。
# 应用场景
状态模式适用于具有多个状态和状态转换的场景,可以提高代码的可读性、可维护性和扩展性。但是,在状态比较简单或者状态转换较少的情况下,引入状态模式可能会增加不必要的复杂性。
对象的行为取决于其内部状态,并且该行为在运行时可能发生改变。
有多个状态且状态之间存在复杂的转换关系。
需要消除条件语句,并且条件语句的判断逻辑与状态相关。
需要在运行时动态地添加新的状态或修改现有状态的行为。
需要将状态的转换过程可见和可控,方便理解和调试。
# 示例代码
场景
系统用户登录状态的管理,包含未登录、已登录、已登出状态。应用程序可以根据用户登录状态执行不同的行为,并且状态的转换被封装在具体状态类中,使得状态变化的管理更加清晰和灵活。
/**
* 登录状态接口
* 未登录、已登录、已登出
*/
public interface LoginState {
// 登录
void login(String username, String password);
// 登出
void logout();
}
2
3
4
5
6
7
8
9
10
11
/**
* 未登录状态
* 具体状态类
*/
@Slf4j
public class NotLoggedInState implements LoginState {
@Override
public void login(String username, String password) {
if (username.equals("admin") && password.equals("123456")) {
log.info("登录成功");
} else {
log.info("登录失败");
}
}
@Override
public void logout() {
log.info("无法登出,请先登录系统");
}
}
/**
* 已登录状态
* 具体实现类
*/
@Slf4j
public class LoggedInState implements LoginState {
@Override
public void login(String username, String password) {
log.info("无法登录,用户已登录");
}
@Override
public void logout() {
log.info("用户登出");
}
}
/**
* 登录失败状态
* 具体状态类
*/
@Slf4j
public class LoginFailedState implements LoginState {
@Override
public void login(String username, String password) {
if (username.equals("admin") && password.equals("123456")) {
log.info("登录成功");
} else {
log.info("登录失败");
}
}
@Override
public void logout() {
log.info("无法登出,用户登录失败");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 用户上下文
*/
public class UserContext {
// 持有登录状态接口引用
private LoginState loginState;
public UserContext() {
// 初始状态为:未登录状态
this.loginState = new NotLoggedInState();
}
// 改变用户登录状态
public void setLoginState(LoginState loginStatte) {
this.loginState = loginStatte;
}
// 登录
public void login(String username, String password) {
loginState.login(username, password);
}
public void logout() {
loginState.logout();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 测试类
*/
@Slf4j
public class Client {
public static void main(String[] args) {
LoginState loggedInState = new LoggedInState();
LoginState loginFailedState = new LoginFailedState();
UserContext userContext = new UserContext();
log.info("------(初始)未登录状态------");
userContext.login("admin", "123456");
userContext.logout();
log.info("------登录状态------");
userContext.setLoginState(loggedInState);
userContext.login("admin", "123456");
userContext.logout();
log.info("------登录失败状态------");
userContext.setLoginState(loginFailedState);
userContext.login("admin", "123456");
userContext.logout();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Make sure to add code blocks to your code group