(二十一)行为型模式-状态模式

itmahy
itmahy
发布于 2024-01-19 / 66 阅读
0
0

(二十一)行为型模式-状态模式

状态模式

状态模式(State Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

通俗地说,状态模式就是一个关于多态的设计模式。

如果一个对象有多种状态,并且每种状态下的行为不同,一般的做法是在这个对象的各个行为中添加 if-else 或者 switch-case 语句。但更好的做法是为每种状态创建一个状态对象,使用状态对象替换掉这些条件判断语句,使得状态控制更加灵活,扩展性也更好。

举个例子,力扣的用户有两种状态:普通用户和 PLUS 会员。PLUS 会员有非常多的专享功能,其中“模拟面试”功能非常有特色,我们便以此为例。

  • 当普通用户点击模拟面试功能时,提示用户:模拟面试是 Plus 会员专享功能;

  • 当 PLUS 会员点击模拟面试功能时,开始一场模拟面试。

先来看看不使用状态模式的写法,看出它的缺点后,我们再用状态模式来重构代码。

首先定义一个用户状态枚举类:

public enum State {
    NORMAL, PLUS
}

NORMAL 代表普通用户状态,PLUS 代表 PLUS 会员状态。

用户的功能接口:

public interface IUser {
    void mockInterview();
}

本例中我们只定义了一个模拟面试的方法,实际开发中这里可能会有许许多多的方法。

用户状态切换接口:

public interface ISwitchState {
    void purchasePlus();
​
    void expire();
}

此接口中定义了两个方法:purchasePlus方法表示购买 Plus 会员,用户状态变为 PLUS 会员状态,expire 方法表示会员过期,用户状态变为普通用户状态。

力扣用户类:

public class User implements IUser, ISwitchState {
    private State state = State.NORMAL;
​
    @Override
    public void mockInterview() {
        if (state == State.PLUS) {
            System.out.println("开始模拟面试");
        } else {
            System.out.println("模拟面试是 Plus 会员专享功能");
        }
    }
​
    @Override
    public void purchasePlus() {
        state = State.PLUS;
    }
​
    @Override
    public void expire() {
        state = State.NORMAL;
    }
}

用户类实现了 IUser接口,IUser接口中的每个功能都需要判断用户是否为 Plus 会员,也就是说每个方法中都有if (state == State.PLUS) {} else {}语句,如果状态不止两种,还需要用上 switch-case 语句来判断状态,这就是不使用状态模式的弊端:

  • 判断用户状态会产生大量的分支判断语句,导致代码冗长;

  • 当状态有增加或减少时,需要改动多个地方,违反开闭原则。

在《代码整洁之道》、《重构》两本书中都提到:应使用多态取代条件表达式。接下来我们就利用多态特性重构这份代码。为每个状态新建一个状态类,普通用户:

class Normal implements IUser {
​
    @Override
    public void mockInterview() {
        System.out.println("模拟面试是 Plus 会员专享功能");
    }
}

PLUS 会员:

class Plus implements IUser {
​
    @Override
    public void mockInterview() {
        System.out.println("开始模拟面试");
    }
}

每个状态类都实现了 IUser 接口,在接口方法中实现自己特定的行为。

用户类:

class User implements IUser, ISwitchState {
​
    IUser state = new Normal();
​
    @Override
    public void mockInterview() {
        state.mockInterview();
    }
​
    @Override
    public void purchasePlus() {
        state = new Plus();
    }
​
    @Override
    public void expire() {
        state = new Normal();
    }
}

可以看到,丑陋的状态判断语句消失了,无论IUser 接口中有多少方法,User 类都只需要调用状态类的对应方法即可。

客户端测试:

public class Client {
​
    @Test
    public void test() {
        // 用户初始状态为普通用户
        User user = new User();
        // 输出:模拟面试是 Plus 会员专享功能
        user.mockInterview();
​
        // 用户购买 Plus 会员,状态改变
        user.purchasePlus();
        // 输出:开始模拟面试
        user.mockInterview();
​
        // Plus 会员过期,变成普通用户,状态改变
        user.expire();
        // 输出:模拟面试是 Plus 会员专享功能
        user.mockInterview();
    }
}

可以看到,用户状态改变后,行为也随着改变了,这就是状态模式定义的由来,它的优点是:将与特定状态相关的行为封装到一个状态对象中,使用多态代替 if-else 或者 switch-case 状态判断。缺点是必然导致类增加,这也是使用多态不可避免的缺点。



评论