zoukankan      html  css  js  c++  java
  • 设计模式:状态模式

      状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

      策略模式和状态模式时双胞胎,策略模式时围绕可以互换的算法来创建成功业务的。状态模式时通过改变对象内部的状态来帮助对象控制自己的行为。

      下面我们看看状态模式的场景

        场景1:有一个扭糖果机器,在投入1元硬币后,扭动曲柄,然后机器掉出一颗糖果。

      先来看看场景,既然是状态模式,顾名思义,就是根据状态来划分,在这个过程中我们可以找出糖果机所有的状态:有钱,没钱,糖果售罄,出售糖果四种状态,而动作又:投入钱,退回钱,转到曲柄,发放糖果四种,现在我们根据这四种状态和四种动作来进行编码,如下:

    //糖果机器类
    public class GumballMachine {
        //定义状态的常量
        final static int SOLD_OUT = 0;
        final static int NO_QUARTER = 1;
        final static int HAS_QUARTER = 2;
        final static int SOLD = 3;
        
        //初始化糖果机器的状态为售罄状态
        int state = SOLD_OUT;
        //记录糖果机器中剩余糖果
        int count = 0;
        
        public GumballMachine(int count){
            this.count = count ;
            if(count > 0){
                state = NO_QUARTER;
            }
        }
        
        //现在我们开始实现四个动作:投入钱,退回钱,转到曲柄,发放糖果
        //投入钱
        public void insertQuarter(){
            switch(state){
                //机器状态是有钱状态的时候,不能再投钱了
                case HAS_QUARTER:System.out.println("你不能在投入第二个硬币了");break;
                //机器状态是没钱状态,投入后,机器状态变成有钱状态
                case NO_QUARTER:state = HAS_QUARTER;System.out.println("你投入了1元钱");break;
                //机器状态是售罄
                case SOLD_OUT:System.out.println("已经没有糖果了,不能投币了");break;
                //机器状态是出售糖果,为了让机器把状态给转换过来回复到NO_QUARTER
                case SOLD:System.out.println("请等待,正在准备糖果");break;
            }
        }
        
        //退回钱
        public void ejectQuarter(){
            switch(state){
                //机器状态是有钱状态的时候,退钱
                case HAS_QUARTER:System.out.println("退回钱");state = NO_QUARTER;break;
                //机器状态是没钱状态,就没的退
                case NO_QUARTER:System.out.println("你并未投入钱");break;
                //机器状态是售罄
                case SOLD_OUT:System.out.println("已经没有糖果了,不接受钱,更不可能退钱");break;
                //机器状态是出售糖果,就不能退了
                case SOLD:System.out.println("正在出售糖果,无法退钱");break;
            }
        }
        
        //转动曲柄
        public void turnCrank(){
            switch(state){
                //机器状态是有钱状态,扭动后改变状态为出售糖果,然后调用dispense方法来操作糖果的发放
                case HAS_QUARTER:System.out.println("扭动成功");state = SOLD;dispense();break;
                //机器状态是没钱状态
                case NO_QUARTER:System.out.println("你需要投入钱先");break;
                //机器状态是售罄
                case SOLD_OUT:System.out.println("已经没有糖果了");break;
                //机器状态是出售糖果
                case SOLD:System.out.println("正在出售糖果,此操作无效");break;
            }
        }
        
        //发放糖果
        public void dispense(){
            if(state == SOLD){
                System.out.println("正在准备发放糖果");
                count --;
                if(count == 0){
                    System.out.println("最后一颗糖果发放完了,糖果已售罄了");
                    state = SOLD_OUT;
                }
                else{
                    System.out.println("发放完了糖果");
                    state = NO_QUARTER;
                }
            }
        }
    }

      让我们写个测试类来测试下

    public class Test {
        public static void main(String[] args) {
            //准备5颗糖
            GumballMachine gumballMachine = new GumballMachine(5);
            
            //投入钱
            gumballMachine.insertQuarter();
            //扭动曲柄
            gumballMachine.turnCrank();
            //在投入钱
            gumballMachine.insertQuarter();
            //退钱
            gumballMachine.ejectQuarter();
            //没钱的时候,扭动曲柄
            gumballMachine.turnCrank();
            //没钱的时候,退钱
            gumballMachine.ejectQuarter();
            //投入钱
            gumballMachine.insertQuarter();
            //再投入钱
            gumballMachine.insertQuarter();
        }
    }

      运行结果如下

      

      看起来状态模式就是这么简单了,但是如果扩展程序,加上其它状态那么我们就需要在四个动作中分别加入新的判断,这显得很麻烦,而且是不是也违背了我们的封装变化这原则呢,所以我们可以从新来设计下更合理的状态模式。

      首先,我们定义一个State接口,在这个接口内,糖果机器每个动作都有一个对应的方法

    //状态接口,每个状态都实现它里面的方法
    public interface State {
        void insertQuarter();
        void ejectQuarter();
        void turnCrank();
        void dispense();
    }

      然后为机器中的每个状态实现状态类,这些类负责在对应的状态下,机器会是怎么样的行为。

    //四大状态之没有钱状态
    public class NoQuarterState implements State{
        GumballMachine gumballMachine;
        
        //通过构造器得到糖果机器的引用
        public NoQuarterState(GumballMachine gumballMachine){
            this.gumballMachine = gumballMachine;
        }
        
        @Override
        public void insertQuarter() {
           //因为确定是没有钱状态下投入钱,所以不需要理会其他状态
            System.out.println("你投入了1元钱");
            //将机器的状态变成有钱状态
            gumballMachine.setState(gumballMachine.getHasQuarterState());
        }
    
        @Override
        public void ejectQuarter() {
            System.out.println("你并未投入钱,无法退钱");
        }
    
        @Override
        public void turnCrank() {
            System.out.println("你需要先投钱,才能正常扭动");        
        }
    
        @Override
        public void dispense() {
            System.out.println("你并未投入钱,所以没用在发放糖");        
        }
    }
    
    
    //四大状态之有钱状态
    public class HasQuarterState implements State{
        GumballMachine gumballMachine;
        
        public HasQuarterState(GumballMachine gumballMachine){
            this.gumballMachine = gumballMachine;
        }
            
        @Override
        public void insertQuarter() {
            System.out.println("你不能在投入第二个硬币了");
        }
    
        @Override
        public void ejectQuarter() {
            System.out.println("退回钱了");
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        }
    
        @Override
        public void turnCrank() {
            System.out.println("扭动成功");
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    
        @Override
        public void dispense() {
            System.out.println("操作错误,机器还没到出售糖果的阶段");     
        }
    }
    
    
    //四大状态之出售状态
    public class SoldState implements State{
        GumballMachine gumballMachine;
        
        public SoldState(GumballMachine gumballMachine){
            this.gumballMachine = gumballMachine;
        }
        
        @Override
        public void insertQuarter() {
            System.out.println("请等待,正在准备出售糖果");
        }
    
        @Override
        public void ejectQuarter() {
            System.out.println("正在出售糖果,不能退钱");
        }
    
        @Override
        public void turnCrank() {
            System.out.println("正在出售糖果,再次扭动曲柄没有效果");
        }
    
        @Override
        public void dispense() {
            gumballMachine.releaseBall();
            if(gumballMachine.getCount() > 0){
                System.out.println("发放糖果完成");
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            }
            else{
                System.out.println("最后一颗糖果发放完了,糖果已售罄了");
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
    
    //四大状态之售罄状态
    public class SoldOutState implements State{
        GumballMachine gumballMachine;
        
        public SoldOutState(GumballMachine gumballMachine){
            this.gumballMachine = gumballMachine;
        }
    
        @Override
        public void insertQuarter() {
            System.out.println("投币无效,已经没有糖果了");
        }
    
        @Override
        public void ejectQuarter() {
            System.out.println("已经没有糖果了,不能投币,更不能退钱了");
        }
    
        @Override
        public void turnCrank() {
            System.out.println("已经没有糖果了,无效的扭动");
        }
    
        @Override
        public void dispense() {
            System.out.println("已经没有糖果了,此操作状态无效");
        }
    }

      最后我们将之前旧代码的条件取而代之为动作委托,委托到相应的状态类。

    //糖果机器类
    public class GumballMachine {
    //将所有状态都定义都机器中
        State soldState;
        State soldOutState;
        State noQuarterState;
        State hasQuarterState;
        
        State state = soldOutState;
        int count = 0;
        
        public GumballMachine(int count){
            soldOutState = new SoldOutState(this);
            soldState = new SoldState(this);
            noQuarterState = new NoQuarterState(this);
            hasQuarterState = new HasQuarterState(this);
            this.count = count;
            if(count > 0){
                state = noQuarterState;
            }
        }
        
        //将四个动作委托到当前的状态类就可以了,大大简化了
        public void insertQuarter(){
            state.insertQuarter();
        }
        
        public void ejectQuarter(){
            state.ejectQuarter();
        }
        
        public void turnCrank(){
            state.turnCrank();
            state.dispense();
        }
        
        
        //用于计算机器中糖果剩余量
        public void releaseBall(){
            System.out.println("正在发放糖果中...");
            if(count !=0){
                count -- ;
            }
        }
        
       //get/set方法
    }

      测试代码不变,我们可以看到结果如下:

      

      好了对比之前的,我们做了以下几点:

        1.将每个状态的行为局部化到它自己的类中

        2.将容易产生问题的判断条件删除,以方便日后的维护

        3.让每个状态对修改关闭,对扩展开发,即使加入新的状态也没关系

      最后,让我们再来区分区分状态、策略、模板方法模式。

        状态:封装基于状态的行为,并将行为委托到当前状态。

        策略:将可以互换的行为封装起来,然后使用委托的方法决定使用哪一个行为。

        模板方法:由子类决定如何实现算法中的某些步骤。   

      下一节:代理模式

    作者:哀&RT
    出处:博客园哀&RT的技术博客--http://www.cnblogs.com/Tony-Anne/
    您的支持是对博主最大的鼓励,感谢您的认真阅读。
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    超详细的 Vagrant 上手指南
    简单聊聊 Ironic
    什么是裸金属
    使用 minikube 快速上手 Kubernetes | v1.7.3
    来看看你对Python变量理解到位了没有
    python HelloWorld 的 4 种姿势,你知道几种
    Windows 系统安装 Python 3.8 详解
    myeclipse2017下载安装与破解详细教程
    eclipse中tomcat的add and Remove找不到项目
    cmd中查看MySQL数据库表数据及结构
  • 原文地址:https://www.cnblogs.com/Tony-Anne/p/6527920.html
Copyright © 2011-2022 走看看