定义

允许一个对象在其内部状态改变时改变它的行为,让这个对象看起来似乎修改了她的类


那你会说,这跟if-else有什么区别?

还真就没什么区别。不只是没区别,状态模式还让我们的程序变得更复杂。对啊,那为什么要用状态模式呢?




图纸

设计模式——2_7 状态(State)-LMLPHP




一个例子:如何模拟一个转笔刀

道友,你应该用过那种铅笔转笔刀吧?他的主要工作就是让我们插进去一根铅笔,然后用内部的刀片把铅笔削尖

他就是我们这次例子,准备好了吗,我们开始了:


自动转笔刀

假定我们要开发一款自动转笔刀:只要我们把铅笔戳进去,按动按钮,理论上来说这个转笔刀就会自动帮我们削铅笔并在削好后把铅笔弹出来,为了用代码来操控这个转笔刀,我们实现了如下结构:

设计模式——2_7 状态(State)-LMLPHP

Pencil
/**
 * 铅笔
 */
public class Pencil {

    /**
     * 锋利度,默认没有削的铅笔就是0
     */
    private int sharpness = 0;

    /**
     * 是锋利的吗
     * <p>
     * >=10 视为锋利的
     */
    public boolean isSharp() {
        return sharpness >= 10;
    }

    /**
     * 增加锋利度
     */
    public void addSharpness(int sharp) {
        sharpness += sharp;
    }
}
PencilSharpener
/**
 * 铅笔转笔刀
 */
public class PencilSharpener {

    /**
     * 当前正在削的铅笔
     */
    private Pencil pencil;

    /**
     * 弹出铅笔
     */
    public void popup() {
        this.pencil = null;
        System.out.println("弹出铅笔");
    }

    /**
     * 旋转一圈,增加铅笔3点的锋利值
     */
    public void rotate() {
        if (pencil != null) {
            pencil.addSharpness(3);
        }
    }

    /**
     * 削铅笔
     */
    public void sharpen() {
        while (pencil != null && !pencil.isSharp()) {
            //如果有铅笔,而且铅笔不锋利
            rotate();//转圈削
        }

        //弹出铅笔
        popup();
    }
    
    /**
     * 插入铅笔
     */
    public void setPencil(Pencil pencil) {
        this.pencil = pencil;
    }
}

转笔刀,首先得有铅笔,所以我们创建了一个 Pencil(铅笔) ,在 Pencil 里面,我们通过一个整型值 sharpness(锋利度) 来表示铅笔的锋利度,并规定当 sharpness < 10的时候,这跟铅笔就是不锋利的,应该被削

我们创建了 PencilSharpener(铅笔转笔刀) 用来表示一个转笔刀,并赋予他 popup 弹出铅笔 和 rotate 削一圈的方法,最终把他们都集成到 sharpen 削铅笔方法里


这个代码简单直接,一看就是我喜欢的风格,这玩意他耿直你晓得吧

这段代码也一直恪尽职守,直到某天有用户投诉这个自动转笔刀转起来没完没了以至于一个月偷跑了他几百块电费


投诉和改善

钝刀

经过排查,我们发现原因出在转笔刀的刀片上。因为转笔刀在削铅笔的时候他内部刀片的锋利度也在不断的被损耗,直到刀钝到无法再增加铅笔的锋利度。于是在 sharpen 方法内出现一个死循环,转笔刀不断的调用 rotate 可是铅笔的锋利度并不改变

也就是说,我们需要增加对刀片状态的监控,就像这样:

设计模式——2_7 状态(State)-LMLPHP

Blade
/**
 * 刀片
 */
public class Blade {

    /**
     * 锋利度,默认默认刀的锋利值是10
     */
    private int sharpness = 10;

    /**
     * 是锋利的吗
     * <p>
     * >=5 视为锋利的
     */
    public boolean isSharp() {
        return sharpness >= 5;
    }

    /**
     * 减少锋利度
     */
    public void lessenSharpness(int sharp) {
        sharpness -= sharp;
    }
}
PencilSharpener
/**
 * 铅笔转笔刀
 */
public class PencilSharpener {

    
    ……
    
    /**
     * 刀片
     */
    private Blade blade = new Blade();

    /**
     * 旋转一圈,增加铅笔3点的锋利值,减少刀子1点锋利值
     */
    public void rotate() {
        if (pencil != null) {
            pencil.addSharpness(3);
            blade.lessenSharpness(1);
        }
    }

    /**
     * 削铅笔
     */
    public void sharpen() {
        while (pencil != null && !pencil.isSharp()) {
            if (blade.isSharp()) {
                //如果刀子锋利
                //如果有铅笔,而且铅笔不锋利
                rotate();//转圈削
            } else {
                System.out.println("刀子不锋利了,请换刀");
                break;
            }
        }

        //弹出铅笔
        popup();
    }
}

我们新增了 Blade 刀片类,并且用和铅笔类似的方式管理他的锋利度(理论来说无法连续削两支铅笔),现在他又正常了


没有铅笔

可是几天后,新的投诉来了,客户说为什么在没有插入铅笔的情况下,依然会在点击削铅笔按钮的时候触发弹出铅笔的动作

接着改,这次要改这里:

PencilSharpener
……
	public void sharpen() {
        if(pencil != null){
            while (!pencil.isSharp()) {
                if (blade.isSharp()) {
                    //如果刀子锋利
                    //如果有铅笔,而且铅笔不锋利
                    rotate();//转圈削
                } else {
                    System.out.println("刀子不锋利了,请换刀");
                    break;
                }
        	}
            
            //弹出铅笔
        	popup();
        }
    }

……

我们把对铅笔的判断提到最外层,让他包含popup,这样以解决用户的投诉


可是改到这里你发现一个可怕的事实,我们终于把所有的行为都包含在if-else里面了


if if if

为什么我们会举步维艰?所有的行为在执行前我们都要谨慎的判断当前外界的状态,走错一步就会报错崩溃。有没有一种方法,可以让算法抽象出来,统一某一种状态下的所有行为?


答案当然是肯定的,先分析一下一共可能出现多少种状态,就像这样:

接着我们把他们做成类簇,就像这样:

设计模式——2_7 状态(State)-LMLPHP

State
/**
 * 转笔刀状态
 */
public abstract class PencilSharpenerState {

    protected final PencilSharpener pencilSharpener;

    public PencilSharpenerState(PencilSharpener pencilSharpener) {
        this.pencilSharpener = pencilSharpener;
    }

    public abstract void handle();
}

/**
 * 无铅笔状态
 */
public class NoPencil extends PencilSharpenerState{

    public NoPencil(PencilSharpener pencilSharpener) {
        super(pencilSharpener);
    }

    @Override
    public void handle() {
        //什么都不做
        System.out.println("没有铅笔");
    }
}

/**
 * 钝刀状态
 */
public class Dull extends PencilSharpenerState{

    public Dull(PencilSharpener pencilSharpener) {
        super(pencilSharpener);
    }

    @Override
    public void handle() {
        System.out.println("刀子钝了,没法削了");
        pencilSharpener.popup();//弹出铅笔
    }
}

/**
 * 正常状态
 */
public class Normal extends PencilSharpenerState {

    public Normal(PencilSharpener pencilSharpener) {
        super(pencilSharpener);
    }

    @Override
    public void handle() {
        pencilSharpener.rotate();//转一圈
    }
}

/**
 * 完成状态
 */
public class Finish extends PencilSharpenerState {

    public Finish(PencilSharpener pencilSharpener) {
        super(pencilSharpener);
    }

    @Override
    public void handle() {
        //弹出铅笔,结束
        pencilSharpener.popup();
    }
}

我们通过 PencilSharpenerState 这个状态类簇,把原先应该被放在 rotate 方法里面的那些动作都独立了出来,那现在我们要把她们绑定到 PencilSharpener 上,就像这样:

PencilSharpener
/**
 * 铅笔转笔刀
 */
public class PencilSharpener {

    private final PencilSharpenerState noPencil = new NoPencil(this);//无铅笔状态
    private final PencilSharpenerState dull = new Dull(this);//钝刀状态
    private final PencilSharpenerState normal = new Normal(this);//正常状态
    private final PencilSharpenerState finish = new Finish(this);//完成状态
    private PencilSharpenerState current = noPencil;//当前状态 默认为无铅笔

    /**
     * 刀片
     */
    private Blade blade = new Blade();

    /**
     * 当前正在削的铅笔
     */
    private Pencil pencil;

    /**
     * 弹出铅笔
     */
    public void popup() {
        this.pencil = null;
        System.out.println("弹出铅笔");
        //弹出铅笔时状态变为无铅笔
        current = noPencil;
    }

    /**
     * 旋转一圈
     */
    public void rotate() {
        pencil.addSharpness(3);//增加铅笔锋利度
        blade.lessenSharpness(1);//减少铅笔锋利度

        updateState();//更新状态
        current.handle();//接着执行
    }

    /**
     * 削铅笔
     */
    public void sharpen() {
        current.handle();//调用状态对象里面的执行方法
    }

    /**
     * 插入铅笔
     */
    public void setPencil(Pencil pencil) {
        this.pencil = pencil;
        //插入铅笔的时候状态根据刀片的状态进行切换
        updateState();
    }

    /**
     * 更新状态
     */
    private void updateState() {
        //优先判断铅笔的状态
        if (pencil == null) {
            current = noPencil;//无铅笔
        } else if (pencil.isSharp()) {
            //铅笔足够锋利了
            current = finish;//完成状态
        } else if (blade.isSharp()) {
            //刀片足够锋利
            current = normal;//正常状态
        } else {
            current = dull;//钝刀状态
        }
    }
}

PencilSharpener 的开头,我们就定义出所有可能出现的状态所对应的算法对象,并和this建立了绑定。这让 PencilSharpener 的方法显得相当清爽,因为每种状态里需要做的事情都被单独分割出来了

但是我们总要去判断当前的状态并对其做出反应的,于是我们定义了 updateState 方法,这个方法在每个切换状态后需要判断时被调用,以确定当前正确的状态对象


而这正是一个标准的状态模式实现


最后,再给他补个main方法看看效果:

public static void main(String[] args) {
	PencilSharpener pencilSharpener = new PencilSharpener();

	for (int i = 1; i <= 3; i++) {
		System.out.printf("第%s根铅笔:\n", i);

		Pencil pencil = new Pencil();
		System.out.printf("铅笔锋利度为:%s\n", pencil.getSharpness());
		pencilSharpener.setPencil(pencil);
		pencilSharpener.sharpen();
		System.out.printf("铅笔锋利度为:%s\n", pencil.getSharpness());
		System.out.println("*****************");
    }

    pencilSharpener.sharpen();
}

我们特地建了三根铅笔让转笔刀去削并在最后不插入铅笔再让他执行一次,理论上来说四次调用 pencilSharpener.sharpen(); 的结果分别会是:

  1. 正常削完
  2. 削到一半弹出
  3. 完全没削弹出
  4. 不执行

就像这样:

设计模式——2_7 状态(State)-LMLPHP




碎碎念

状态和if

请回顾一下本文最开始的问题,为什么我们要用状态模式呢?

答:状态模式让整个程序的结构变得更清晰,而且打断了算法和算法间的耦合。在使用状态模式之前,你的算法之间的关联都是写死的,当出现新的状态时,你只能增加新的if-else块,而且他们未必都是同级的,你需要去协调他们。

状态模式提供了另一种解题思路,他让你把if-else块里面做的事情抽象成一个对象。那么算法的调用对象,和被你抽象出来的算法对象之间就可以用组合了,我可以根据不同的状态,把不同的算法对象组合到算法调用对象内部。这样一来我不需要再去整理if-else,而是要确认对象此刻的状态。如果将来有新的状态出现,那也是新增算法对象的事情,至此实现解耦


那我们是不是可以说。所以程序里要减少if-else,无所不用其极的去减少这种结构出现的次数。就像上例,似乎都是在强调如何减少程序里面出现的各种条件判断。我们的敌人就是if-else,他是导致程序变得复杂的根本原因


上面那段话可以翻译成这样:

因为张三被枪打死了,这把枪导致了张三的死亡,所以我们判这把枪死刑

if-else就相当于这把枪,虽然在上例中的确是大量if-else直接导致结构的混乱,可他绝不是根本原因,别把他当敌人。该被审判的是那个扣动扳机的人


那是谁开的枪?

答:从第一篇设计模式开始,开枪的人从来没有变过,就是那些散落各处的“变化”。

在上例中,我们把所有“变化”集中到一处,使整个结构变得清晰。通过解读 updateState 任何人都能对转笔刀接下来会做的行为一目了然,而这正是我们在上例使用状态模式之前的实现里无法做到的,也这正是状态模式的魅力所在

这也符合我们的设计原则:找出应用中可能需要变化的地方,把他们独立出来,不要和那些不需要变化的代码混合在一起


回到上例

请注意上例中的 updateState 方法,我们在根据 PencilSharpener 当前的状态并切换对应的状态对象的,所以不可避免的出现了大量的if-else,而这恰巧是整个转笔刀正常运转的关键。

也就是说哪怕是在上例中 if-else 也不是被消灭了,而是被整合了



状态和可复用性

状态类的复用

状态类是可以被复用的,这一点在上例中没有体现出来

简单来说,比如我有多种转笔刀,可能他们削铅笔的方式不一样,有些用刀片,有些用滚轴,哪怕使用激光去削铅笔,在 没有插入铅笔的情况 下他们要做的事情都是一样的,也就是说上例中的 NoPencil 状态类显然是可以复用的

说到底,这是因为状态类是对算法的抽象,所以只有有算法相同的部分,都可以复用


状态对象

不只是状态类,状态对象也是可以复用的。虽然状态类叫状态类,但是状态类大都是没有自身状态的,他有价值的只是里面的方法,这样一来状态对象就是可复用的。所以状态对象常常的单例的




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

04-09 12:20