学好设计模式防被祭天:状态模式

状态模式

为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。

一:理解

  1. 状态模式从另一个角度思考状态转移问题。
  2. 原有逻辑是实体的状态从A变成B。在状态模式中,状态转义过程被抽象成从处于状态A的实体变成处于状态B的实体。
  3. 可以抽象出多个包含实体的状态类。


二:例子

你是个富二代。

你的生活状态很简单,一种是充满能量状态ENERGETIC_STATE,还有一种就是贤者时间状态XIANZHE_STATE。

当你在贤者时间时,只要休息了,就会变得活力满满。

当你在充满能量状态时,只要papapa了,就会进入贤者时间。

当你在贤者时间,就没有兴趣接着再papapa了。

当你活力满满,就不需要再休息了。

于是,你叫来程序员小菜帮你抽象一下你的生活状态。

小菜上来就是一顿敲。

@Data
public class FuErDai {
    private static final int XIANZHE_STATE = 0;
    private static final int ENERGETIC_STATE = 1;

    private int state;

    public FuErDai(int state) {
        this.state = state;
    }

    public void rest() {
        if (state == XIANZHE_STATE) {
            System.out.println("当前状态是贤者时间,需要休息!");
            state = ENERGETIC_STATE;
        } else if (state == ENERGETIC_STATE) {
            System.out.println("当前状态是能量满满,不需要休息!");
        }
    }

    public void papapa() {
        if (state == XIANZHE_STATE) {
            System.out.println("当前状态是贤者时间,不想papapa!");
        } else if (state == ENERGETIC_STATE) {
            System.out.println("当前状态是能量满满,来一发!");
            state = XIANZHE_STATE;
        }
    }
}

这是一个很简单的程序,FuErDai类中含有状态属性state,每次想要休息rest或者papapa时,使用if else对当前状态进行判断。

测试程序:

public class ClientV1 {
    public static void main(String[] args) {
        FuErDai fuErDai = new FuErDai(0);
        fuErDai.papapa();
        fuErDai.papapa();
        fuErDai.rest();
        fuErDai.rest();
        fuErDai.papapa();
        fuErDai.rest();
    }
}

输入/输出:

当前状态是贤者时间,不想papapa!
当前状态是贤者时间,不想papapa!
当前状态是贤者时间,需要休息!
当前状态是能量满满,不需要休息!
当前状态是能量满满,来一发!
当前状态是贤者时间,需要休息!

这段程序简单易懂,你很开心。

有一天,你突然发现,自己在充满能量和贤者时间以外还有一个状态,就是一半一半状态HALF_STATE。

在一半一半状态时候:

  1. 你尝试休息,会有百分之五十的概率开始休息,休息完进入活力满满状态。
  2. 你尝试papapa,会有百分五十的概率接受约X,papapa之后进入贤者时间。

小菜觉得不就是多了一个状态而已,分分钟搞定。

@Data
public class FuErDaiV2 {
    private static final int XIANZHE_STATE = 0;
    private static final int ENERGETIC_STATE = 1;
    private static final int HALF_STATE = 2;

    private int state;

    public FuErDaiV2(int state) {
        this.state = state;
    }

    public void rest() {
        if (state == XIANZHE_STATE) {
            System.out.println("当前状态是贤者时间,需要休息!");
            state = ENERGETIC_STATE;
        } else if (state == ENERGETIC_STATE) {
            System.out.println("当前状态是能量满满,不需要休息!");
        } else if (state == HALF_STATE) {
            if (isAccept()) {
                System.out.println("当前状态是一半一半,可以休息");
                state = ENERGETIC_STATE;
            } else {
                System.out.println("当前状态是一半一半,但不休息");
            }
        }
    }

    public void papapa() {
        if (state == XIANZHE_STATE) {
            System.out.println("当前状态是贤者时间,不想papapa!");
        } else if (state == ENERGETIC_STATE) {
            System.out.println("当前状态是能量满满,来一发!");
            state = XIANZHE_STATE;
        } else if (state == HALF_STATE) {
            if (isAccept()) {
                System.out.println("当前状态是一半一半,可以papapa");
                state = XIANZHE_STATE;
            } else {
                System.out.println("当前状态是一半一半,但不papapa");
            }
        }
    }

    private boolean isAccept() {
        return new Random().nextBoolean();
    }
}

小菜在rest和papapa方法中增加了对HALF_STATE状态的判断,多了一层if else逻辑。

你觉得这个程序没问题,就向你的二代朋友们炫耀。

然而,这些朋友纷纷表示不屑。

虽然说不出为什么,但总觉得这么多if else不是很优雅。

炫耀不成反丢脸,你拿起你那把40米的大刀准备杀了这位程序员祭天。

40米的大刀

吓得小菜急忙开始对于重构的思考:

  1. 这个程序主要抽象的是富二代状态的转移,可以尝试使用状态模式
  2. 太多的if else的确不利于代码的维护,而且说不准哪天富二代又需要增加状态。

于是,小菜决定使用状态模式进行尝试。

他先抽象出一个接口,接口描述的是某状态下的富二代,无论处于什么状态的富二代,都有rest和papapa方法,通过State接口进行约束。

public interface State {
    void rest();

    void papapa();
}

接着,小菜新建了三个状态类。

状态类中包含FuErDaiV3属性,因为状态类其实表示的是当前状态下的富二代,rest和papapa方法,还是需要富二代来执行。

// 贤者时间状态
public class XianZheState implements State {
    FuErDaiV3 fuErDaiV3;

    public XianZheState(FuErDaiV3 fuErDaiV3) {
        this.fuErDaiV3 = fuErDaiV3;
    }

    @Override
    public void rest() {
        System.out.println("当前状态是贤者时间,需要休息!");
        fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
    }

    @Override
    public void papapa() {
        System.out.println("当前状态是贤者时间,不想papapa!");
    }
}

// 活力满满状态
public class EnergeticState implements State {
    private FuErDaiV3 fuErDaiV3;

    public EnergeticState(FuErDaiV3 fuErDaiV3) {
        this.fuErDaiV3 = fuErDaiV3;
    }

    @Override
    public void rest() {
        System.out.println("当前状态是能量满满,不需要休息!");
    }

    @Override
    public void papapa() {
        System.out.println("当前状态是能量满满,来一发!");
        fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
    }
}

// 一半一半状态
public class HalfState implements State {
    private FuErDaiV3 fuErDaiV3;

    public HalfState(FuErDaiV3 fuErDaiV3) {
        this.fuErDaiV3 = fuErDaiV3;
    }

    @Override
    public void rest() {
        if (isAccept()) {
            System.out.println("当前状态是一半一半,可以休息");
            fuErDaiV3.setState(fuErDaiV3.getEnergeticState());
        } else {
            System.out.println("当前状态是一半一半,但不休息");
        }
    }

    @Override
    public void papapa() {
        if (isAccept()) {
            System.out.println("当前状态是一半一半,可以papapa");
            fuErDaiV3.setState(fuErDaiV3.getXianZheSate());
        } else {
            System.out.println("当前状态是一半一半,但不papapa");
        }
    }

    private boolean isAccept() {
        return new Random().nextBoolean();
    }
}

因为状态各不同,所以不同状态类对于rest和papapa方法的处理也不一样。

接下来是重构之后的富二代类FuErDaiV3。

@Data
public class FuErDaiV3 {
    private State xianZheSate;
    private State energeticState;
    private State halfState;
    private State state;

    public FuErDaiV3() {
        xianZheSate = new XianZheState(this);
        energeticState = new EnergeticState(this);
        halfState = new HalfState(this);
        state = xianZheSate;
    }

    public void rest() {
        state.rest();
    }

    public void papapa() {
        state.papapa();
    }
}

富二代类包含三个已知可能会进入的状态,和一个当前状态属性state。

在构造器新建富二代对象时,需要将本身传入状态类中,分别新建出处于贤者时间的高富帅,处于活力满满状态的高富帅和处于一半一半状态的高富帅。

并且将当前状态/初始状态设置成处于贤者时间的高富帅。

当你需要执行rest和papapa方法时,只需直接调用state的对应方法即可。

在使用了状态模式之后,除了判断百分之五十概率处用了if else,其余的条件判断语句都已被去掉。

当富二代需要增加状态时候,只需新建状态类,以及略微修改富二代类即可。

你很满意,于是唱起了hip-hop。

hip-hop


三:再理解

  1. 状态模式中,富二代类包含状态属性,状态类中又包含富二代属性。相互引用,不知道该先写哪个类。
  2. 状态模式可以去掉对于当前状态的判断语句,方便日后的维护。
  3. 使得富二代的代码做到最清晰。
  4. 不能做到无修改扩展,当增加新状态时候,需为富二代类增加状态属性,并修改构造器。此外,还需要确认其他状态类在执行方法时,是否会进入新状态。不过总体而言,比使用状态模式之前还是优雅太多了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容