• 【设计模式实战】状态模式:原理篇


    前言

    小明开发的应用,账号有登录和未登录状态,现在要求点击个人中心头像时,如果是登录状态时,跳转到个人资料界面,如果是未登录状态时,跳转到登录界面。

    小明是这样写的:

    
    public class AccountManager {
        public static final int STATE_UNLOGIN = 0;//未登录状态
        public static final int STATE_LOGIN = 1;//已登录状态
    
        private int currentState;//当前状态
    
        public AccountManager() {
    
        }
    
        /**
         * 设置状态
         */
        public void setCurrentState(int state) {
            this.currentState = state;
            if (currentState == STATE_UNLOGIN) {
                System.out.println("设置为未登录状态");
            } else if (currentState == STATE_LOGIN) {
                System.out.println("设置为登录状态");
            }
        }
    
        private void gotoLogin() {
            System.out.println("跳转到登录界面");
        }
    
        private void gotoPersonInfo() {
            System.out.println("跳转到个人中心界面");
        }
    
        /**
         * 点击头像的操作
         */
        public void onClickHeadPortrait() {
            System.out.println("点击头像==>");
            if (currentState == STATE_UNLOGIN) {
                gotoLogin();
            } else if (currentState == STATE_LOGIN) {
                gotoPersonInfo();
            }
        }
        
    }
    
    • 1
    • 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

    开始测试

        public void test() {
            AccountManager accountManager = new AccountManager();
            //登录
            accountManager.setCurrentState(AccountManager.STATE_LOGIN);
            accountManager.onClickHeadPortrait();
            //退出登录
            accountManager.setCurrentState(AccountManager.STATE_UNLOGIN);
            accountManager.onClickHeadPortrait();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出结果

    设置为登录状态
    点击头像==>
    跳转到个人中心界面
    设置为未登录状态
    点击头像==>
    跳转到登录界面
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    小明的领导一看,说这不是和上一次策略模式一样,如果要增加一个冻结状态、黑名单状态,分别要跳到解冻界面和申诉界面,岂不是要改动AccountManager类?AccountManager越来越复杂,充斥着各种if…else,违反开闭原则,扩展性很低。

    领导建议这种因为不同状态,而导致不同结果的情景,可以使用状态模式代替。

    使用状态模式改造

    首先我们需要把状态抽象出来,将它标准化,做成抽象类。

    
    public abstract class IAccountState {
    
        protected AccountContext context;
    
        public AccountContext getContext() {
            return context;
        }
    
        public void setContext(AccountContext context) {
            this.context = context;
        }
    
        /**
         * 点击头像
         */
        public abstract void onClickHeadPortrait();
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    具体类继承抽象类

    public class UnLoginState extends IAccountState {
    
        @Override
        public void onClickHeadPortrait() {
            System.out.println("点击头像跳转到登录界面");
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class LoginState extends IAccountState {
    
        @Override
        public void onClickHeadPortrait() {
            System.out.println("点击头像跳转到个人中心界面");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    环境类

    public class AccountContext {
    
        public static final IAccountState STATE_LOGIN = new LoginState();
        public static final IAccountState STATE_UNLOGIN = new UnLoginState();
        public static final IAccountState STATE_FROZEN = new FrozenState();
    
        private IAccountState currentState = STATE_UNLOGIN;
    
        public AccountContext() {
            STATE_LOGIN.setContext(this);
            STATE_UNLOGIN.setContext(this);
            STATE_FROZEN.setContext(this);
        }
    
        private void setCurrentState(IAccountState state) {
            this.currentState = state;
        }
    
        public IAccountState getCurrentState() {
            return currentState;
        }
    
        /**
         * 登录成功
         */
        public void loginSuccess() {
            setCurrentState(STATE_LOGIN);
        }
    
        /**
         * 冻结账号
         */
        public void frozen() {
            setCurrentState(STATE_FROZEN);
        }
    
        /**
         * 退出登录
         */
        public void exit() {
            setCurrentState(STATE_UNLOGIN);
        }
    
        /**
         * 点击头像
         */
        public void onClickHeadPortrait() {
            currentState.onClickHeadPortrait();
        }
    }
    
    
    • 1
    • 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

    开始测试

        public void test() {
            //初始化
            AccountContext controller = new AccountContext();
    
            //登录成功
            controller.loginSuccess();
            controller.onClickHeadPortrait();
    
            //退出登录
            controller.exit();
            controller.onClickHeadPortrait();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出结果

    点击头像跳转到个人中心界面
    点击头像跳转到登录界面
    
    • 1
    • 2

    当我们想增加一个冻结状态时

    public class FrozenState extends IAccountState {
    
        @Override
        public void onClickHeadPortrait() {
            System.out.println("点击头像跳转到解冻界面");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
     public void test() {
     			 //初始化
            AccountContext controller = new AccountContext();
    
            //登录成功
            controller.loginSuccess();
            controller.onClickHeadPortrait();
    
            //冻结账号
            controller.frozen();
            controller.onClickHeadPortrait();
    
            //退出登录
            controller.exit();
            controller.onClickHeadPortrait();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    点击头像跳转到个人中心界面
    点击头像跳转到解冻界面
    点击头像跳转到登录界面
    
    • 1
    • 2
    • 3

    状态模式讲解

    看一下状态模式的UML图
    在这里插入图片描述
    State,抽象状态角色:抽象角色,通常使用接口或者抽象类实现。
    ConcreteState,具体状态角色:实现了抽象角色的类,从而达到不同状态下的不同行为。
    Context,环境角色,持有抽象角色的引用,给客户端调用。

    意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
    主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

    状态模式和策略模式

    看下策略模式的UML图:
    在这里插入图片描述

    和状态模式一模一样,如何区分它们呢?

    根据场景区分。策略是侧重算法的替换,状态是通过状态改变行为。

    策略模式的使用场景:

    1. 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
    2. 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
    3. RecyclerView中的 LayoutManager。

    状态模式的使用场景:

    1. 打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
    2. 自动售卖机,有四个状态,分别是:没有硬币没有饮料、有硬币没有饮料、有硬币有饮料、没有硬币有饮料。有饮料时投币按按钮分派饮料(没有硬币有饮料<->有硬币有饮料),无饮料时投币按按钮返还硬币(没有饮料没有硬币<->有硬币没有饮料)。还有其他状态的行为,如在有硬币有饮料的时候,在投币,会拒绝,直接返还硬币等。
    3. 订单的各种状态,如待支付、未支付、支付中、支付失败
    4. 任务的各种状态,如待指派、待接受、待完成、已取消
    5. 在游戏系统中,人物有待机、走动、跳跃、释放技能中、死亡等状态,每个状态下的形态都会有所不同

    在实际使用的时候,状态之间还有可能互相影响,比如订单在未支付时,点击支付,产品经理要求订单如果是从冻结状态变成待支付状态时,要把该订单放进一个风险列表进行风险监测,那你是不是还得知道该订单上一个状态是否冻结状态。而策略模式不会,策略和策略之间是单独存在的。

  • 相关阅读:
    艾泊宇产品战略:技术型老板必须警惕的2个致命错误
    面试题之HashMap与HashTable的区别
    链动2+1模式系统,是如何成为人、货、场资源流动加速器?
    C#基础入门教程-数组(Array)
    Android进程与线程
    前端工作总结214-可以不参考原型
    mmlab花朵分类结果展示(1)
    implementation of the CVideoPlay class
    Array.reduce() 详解
    Redi未授权访问的两种利用方式
  • 原文地址:https://blog.csdn.net/iromkoear/article/details/126300504