5.4 状态模式
5.4.1 状态模式的定义
1.模式动机:有些对象具有多种状态,这些状态在某些情况下能够相互转换,对象在不同的状态下将具有不同的行为,将拥有状态的对象中和状态的行为分离。
2.模式定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类
5.4.2 状态模式的结构与分析
- 环境类是指拥有状态的对象,可以对状态进行切换。
- 抽象状态类是具体状态类的父类,各状态的行为封装在具体的状态类中,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
public abstract class State {
public abstract void handle();
}
public class ConcreteState extends State{
@Override
public void handle() {
}
}
public class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle();
}
}
5.4.3 状态模式的案例
在某论坛系统中,用户可以发表留言,发表留言将增加积分;用户也可以回复留言,回复留言也将增加积分;用户还可以下载文件,下载文件将扣除积分。该系统用户分为三个等级,分别是新手、高手和专家,这三个等级对应三种不同的状态,这三种状态分别定义如下:
(1) 如果积分小于100分,则为新手状态,用户可以发表留言、回复留言,但是不能下载文件。如果积分大于等于1000分,则转换为专家状态;如果积分大于等于100分,则转换为高手状态。
(2) 如果积分大于等于100分但小于1000分,则为高手状态,用户可以发表留言、回复留言,还可以下载文件,而且用户在发表留言时可以获取双倍积分。积分小于100分,转换为新手状态;如果积分大于等于1000分,则转换为专家状态;如果下载文件后积分小于0,则不能下载该文件。
(3) 如果积分大于等于1000分,则为专家状态,用户可以发表留言、回复留言和下载文件,用户除了在发表留言时可以获取双倍积分外,下载文件只扣除所需积分的一半。积分小于100分,则转换为新手状态;积分小于1000分,但大于等于100,则转换为高手状态;如果下载文件后积分小于0,则不能下载该文件。
由于上述类图将point放到了状态方面,不好理解,下面代码将point放到ForumAccount中
public class ForumAccount {
private String username;
private State state;
private int point;
public ForumAccount(String username) {
this.username = username;
this.point = 0;
this.state = new PrimaryState(this);
}
public void writeNote(int point) {
state.writeNote(point);
}
public void replyNote(int point) {
state.replyNote(point);
}
public void downloadNote(int point) {
state.downloadNote(point);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public State getState() {
System.out.println(state.getStateName());
return state;
}
public void setState(State state) {
this.state = state;
}
public int getPoint() {
return point;
}
public void setPoint(int point) {
this.point = point;
}
}
public abstract class State {
protected ForumAccount forumAccount;
protected String stateName;
public State(ForumAccount forumAccount) {
this.forumAccount = forumAccount;
}
public abstract void writeNote(int point);
public abstract void replyNote(int point);
public abstract void downloadNote(int point);
public void updateState(int point) {
if (point >= 1000) {
forumAccount.setState(new HighState(forumAccount));
} else if (point >= 100) {
forumAccount.setState(new MiddleState(forumAccount));
} else {
forumAccount.setState(new PrimaryState(forumAccount));
}
}
public String getStateName() {
return stateName;
}
}
public class PrimaryState extends State{
public PrimaryState(ForumAccount forumAccount) {
super(forumAccount);
this.stateName = "新手级别";
}
@Override
public void writeNote(int point) {
System.out.println("新手版-写评论");
forumAccount.setPoint(forumAccount.getPoint() + point);
updateState(forumAccount.getPoint());
}
@Override
public void replyNote(int point) {
System.out.println("新手版-回复评论");
forumAccount.setPoint(forumAccount.getPoint() + point);
updateState(forumAccount.getPoint());
}
@Override
public void downloadNote(int point) {
System.out.println("新手状态-无法下载文件");
}
}
public class MiddleState extends State{
public MiddleState(ForumAccount forumAccount) {
super(forumAccount);
this.stateName = "高手级别";
}
@Override
public void writeNote(int point) {
System.out.println("高手版-写评论");
forumAccount.setPoint(forumAccount.getPoint() + point);
updateState(forumAccount.getPoint());
}
@Override
public void replyNote(int point) {
System.out.println("高手版-回复评论");
forumAccount.setPoint(forumAccount.getPoint() + point * 2);
updateState(forumAccount.getPoint());
}
@Override
public void downloadNote(int point) {
if (point > forumAccount.getPoint()) {
System.out.println("积分不足,不能下载");
} else {
System.out.println("高手版-下载文件");
forumAccount.setPoint(forumAccount.getPoint() - point);
updateState(forumAccount.getPoint());
}
}
}
public class HighState extends State {
public HighState(ForumAccount forumAccount) {
super(forumAccount);
this.stateName = "专家级别";
}
@Override
public void writeNote(int point) {
System.out.println("专家版-写评论");
forumAccount.setPoint(forumAccount.getPoint() + point);
updateState(forumAccount.getPoint());
}
@Override
public void replyNote(int point) {
System.out.println("专家版-回复评论");
forumAccount.setPoint(forumAccount.getPoint() + point * 2);
updateState(forumAccount.getPoint());
}
@Override
public void downloadNote(int point) {
if (point / 2 > forumAccount.getPoint()) {
System.out.println("积分不足,不能下载");
} else {
System.out.println("专家版-下载文件");
forumAccount.setPoint(forumAccount.getPoint() - point / 2);
updateState(forumAccount.getPoint());
}
}
}
5.4.4 状态模式的优缺点
优点 | 缺点 |
---|---|
1.封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是需要冗长的if-else判断 | 1.会增加系统中类和对象的个数,导致系统运行开销增大 |
2.将所有与某个状态有关的行为放到一个类中,注入一个不同的状态对象即可使环境对象拥有不同行为 | 2.结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度 |
3.允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块 | 3.对开闭原则的支持并不太好 |
4.可让多个环境对象共享一个状态对象,从而减少系统中对象的个数 |
5.4.5 状态模式的适用场景
-
对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
-
在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强
5.5 策略模式
5.5.1 策略模式的定义
1.模式动机:解决某一问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便地更换算法或者增加新的算法。
2.模式定义:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。
5.5.2 策略模式的结构与分析
-
它将每一个算法封装在一个称为具体策略的类中,同时为其提供统一的抽象策略类,而使用这些算法完成某一业务功能的类称为环境类。
-
策略模式实现了算法定义和算法使用的分离,它通过继承和多态的机制实现对算法族的使用和管理,是一种简单易用的对象行为型模式。
-
各算法是由客户端决定的,不是策略模式自己决定的。
public abstract class Strategy {
public abstract void algorithm();
}
public class ConcreteStrategy extends Strategy{
@Override
public void algorithm() {
}
}
public class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void request() {
strategy.algorithm();
}
}
5.5.3 策略模式的案例
public class Person {
private String name;
private TravelStrategy travelStrategy;
public Person(String name) {
this.name = name;
}
public void setTravelStrategy(TravelStrategy travelStrategy) {
this.travelStrategy = travelStrategy;
}
public void travel() {
System.out.print(this.name);
travelStrategy.travel();
}
}
public interface TravelStrategy {
public void travel();
}
public class TrainStrategy implements TravelStrategy{
@Override
public void travel() {
System.out.println("乘坐火车去旅行");
}
}
public class Main {
public static void main(String[] args) {
TravelStrategy bean = (AirPlaneTravelStrategy) XMLUtil.getBean();
Person person = new Person("xyx");
person.setTravelStrategy(bean);
person.travel();
}
}
5.5.4 策略模式的优缺点
优点 | 缺点 |
---|---|
1.符合开闭原则,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为 | 1.客户端必须知道所有的策略类,并自行决定使用哪一个策略类 |
2.提供了管理相关的算法族的办法,避免多重条件选择语句 | 2.将造成系统产生很多具体策略类 |
3.提供了一种可以替换继承关系的办法 | 3.无法同时在客户端使用多个策略类 |
5.5.5 策略模式的适用场景
-
一个系统需要动态地在几种算法中选择一种
-
避免使用难以维护的多重条件选择语句
-
不希望客户端知道复杂的、与算法相关的数据结构,提高算法的保密性与安全性
5.5.6 状态模式和策略模式的对比
状态模式 | 策略模式 | |
---|---|---|
环境类状态个数 | 多个状态,可以相互转化 | 存在一个状态,一旦选择不能随意改变 |
环境类与状态类关系 | 若环境类会影响状态的改变,为双向关联关系 | 单向的关联关系 |
客户端 | 无需知道具体状态,会根据行为自动切换 | 需要知道所选策略 |
行为 | 各状态下有多个行为 | 一个行为的多种实现方式 |