设计模式-状态模式

状态模式

当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

做同样的事情,不同意的状态下,有不同的表现行为!

状态模式介绍

定义

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

优点

  • 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  • 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  • 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点

  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

状态模式结构与实现

结构

  • 环境类(Context)角色:
    也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  • 抽象状态(State)角色:
    定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  • 具体状态(Concrete State)角色:
    实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
    状态模式图

模板实现

  • 环境类Context默认为A状态
  • 每次调用状态对象的handle()方法执行不同的行为,打印不同状态下的状态信息且切换状态

运行结果用尾行注释写在Client中

/**
 * 环境类
 */
class Context {
    private State state;

    /**
     * 定义环境类的初始状态
     *  初试状态设置为 ConcreteStateA
     */
    public Context() {
        this.state = new ConcreteStateA();
    }
    
    public void showState() {
        this.state.showState();
    }

    /**
     * 设置新状态
     * @param state 状态对象
     */
    public void setState(State state) {
        this.state = state;
    }

    /**
     * 读取状态
     * @return  状态对象
     */
    public State getState() {
        return (state);
    }

    /**
     * 对请求做处理
     */
    public void handle() {
        state.handle(this);
    }
}

/**
 * 抽象状态类
 */
abstract class State {
    /**
     * 将当前状态信息打印出来
     */
    public abstract void showState();
    /**
     * 行为处理方法
     * @param context   上下文
     */
    public abstract void handle(Context context);
}

/**
 * 具体状态A类
 */
class ConcreteStateA extends State {
    @Override
    public void showState() {
        System.out.println("当前为A状态");
    }

    @Override
    public void handle(Context context) {
    	System.out.println("AAA");
        context.setState(new ConcreteStateB());
    }
}

/**
 * 具体状态B类
 */
class ConcreteStateB extends State {
    @Override
    public void showState() {
        System.out.println("当前为B状态");
    }

    @Override
    public void handle(Context context) {
    	System.out.println("BBB");
        context.setState(new ConcreteStateA());
    }
}

public class StateTemlateCilent {
    public static void main(String[] args) {
        Context context = new Context();    // 创建环境

        context.showState();// 当前为A状态

        context.handle();    // 处理请求

        context.showState(); // AAA \n 当前为B状态

        context.handle();

        context.showState(); // BBB \n 当前为A状态

        context.handle();

        context.showState(); // AAA \n 当前为B状态
    }
}


示例 元素战士不同元素下的攻击

在不同状态下,某方法可能什么也不做,也可能会做一些不同的行为,也有可能会自动的切换状态

  • 模式
    享元模式 + 状态模式
  • 环境类
    元素战士
  • 状态类
    火属性状态、水属性状态,草属性状态、无属性状态

元素战士 在不同状态下的攻击效果不一样,每次发动攻击后随机变化状态(也可能变成原状态->无变化)

package behaviour.state.attack;

/**
 * 环境类
 */
class ElementWarrior {
    private ElementState state;

    /**
     * 定义环境类的初始状态
     *  初试状态设置为 无元素状态
     */
    public ElementWarrior() {
        this.state = new NonAttributeState();
    }

    /**
     * 设置新状态
     * @param state 状态对象
     */
    public void setState(ElementState state) {
        this.state = state;
    }

    /**
     * 读取状态
     * @return  状态对象
     */
    public ElementState getState() {
        return state;
    }

    /**
     * 攻击
     */
    public void attack() {
        state.attack(this);
    }
}

/**
 * 抽象状态类
 */
abstract class ElementState {
    /**
     * 攻击
     * @param warrior   战士
     */
    public abstract void attack(ElementWarrior warrior);

    protected void changeRandomState(ElementWarrior warrior) {
        int random = (int)(Math.random() * 4);
        ElementState state = null;
        switch (random) {
            case 0:state = new NonAttributeState();break;
            case 1:state = new FireAttributeState();break;
            case 2:state = new GrassAttributeState();break;
            case 3:state = new WaterAttributeState();break;
            default:break;
        }
        if (state != null && state.getClass() != this.getClass()) {
            System.out.println("元素战士属性随机转换!");
            warrior.setState(state);
        } else {
            System.out.println("元素战士保持了它的属性");
        }
    }
}

/**
 * 具体状态:无属性状态
 */
class NonAttributeState extends ElementState {
    @Override
    public void attack(ElementWarrior warrior) {
        System.out.println("无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]");
        changeRandomState(warrior);
    }
}

/**
 * 具体状态:火属性状态
 */
class FireAttributeState extends ElementState {
    @Override
    public void attack(ElementWarrior warrior) {
        System.out.println("火属性攻击:[向前方扔出一个小火球,对20米以内的单个敌人造成15点伤害,且后续三个回合,每回合结束时分别造成10、8、5伤害]");
        changeRandomState(warrior);
    }
}

/**
 * 具体状态:火属性状态
 */
class WaterAttributeState extends ElementState {
    @Override
    public void attack(ElementWarrior warrior) {
        System.out.println("水属性攻击:[向前方喷出高压水,对15米内直线上的所有敌人造成40~5点伤害(越远伤害越低击退效果越差),击退或击倒对方]");
        changeRandomState(warrior);
    }
}

/**
 * 具体状态:草属性状态
 */
class GrassAttributeState extends ElementState {
    @Override
    public void attack(ElementWarrior warrior) {
        System.out.println("草属性攻击:[丢一颗种子向10米内的单个敌人,造成5点伤害,自身回复5血量,持续3回合]");
        changeRandomState(warrior);
    }
}

public class Client {
    public static void main(String[] args) {
        final ElementWarrior w = new ElementWarrior();
        w.attack();
        w.attack();
        w.attack();
        w.attack();
        w.attack();
    }
}

运行结果

无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士属性随机转换!
草属性攻击:[丢一颗种子向10米内的单个敌人,造成5点伤害,自身回复5血量,持续3回合]
元素战士属性随机转换!
无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士保持了它的属性
无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士属性随机转换!
水属性攻击:[向前方喷出高压水,对15米内直线上的所有敌人造成40~5点伤害(越远伤害越低击退效果越差),击退或击倒对方]
元素战士保持了它的属性

状态模式 与 策略模式 异同

与策略模式的主要差异在 状态模式状态会自动的切换,而策略模式的策略只能主动的切换

不同点

  1. 状态模式重点在各状态之间的切换,从而做不同的事情;而策略模式更侧重于根据具体情况选择策略,并不涉及切换。

  2. 状态模式不同状态下做的事情不同,而策略模式做的都是同一件事。例如,聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付,也就是说他们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。

  3. 状态模式封装了对象的状态,而策略模式封装算法或策略。因为状态是跟对象密切相关的,它不能被重用;而策略模式通过从Context中分离出策略或算法,我们可以重用它们。

  4. 在状态模式中,每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。

  5. 状态模式将各个状态所对应的操作分离开来,即对于不同的状态,由不同的子类实现具体操作,不同状态的切换由子类实现,当发现传入参数不是自己这个状态所对应的参数,则自己给Context类切换状态;这种转换是"自动","无意识"的。状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。而策略模式是直接依赖注入到Context类的参数进行策略选择,不存在切换状态的操作。

  6. 策略模式的客户端必须对所有的策略类相当了解,明确当前场景下各种策略的利弊,权衡在当前场景下应该使用哪种策略,也就是是说策略类对客户端是暴露的,策略是外界给的,策略怎么变,是调用者考虑的事情,系统只是根据所给的策略做事情。

    状态模式依赖于其状态的变化时其内部的行为发生变化,将动作委托到代表当前状态的对象,对外表现为类发生了变化。状态是系统自身的固有的,由系统本身控制,调用者不能直接指定或改变系统的状态转移。

相同点

状态模式和策略模式都是为具有多种可能情形设计的模式,把不同的处理情形抽象为一个相同的接口,符合对扩展开放,对修改封闭的原则。

扩展

策略模式更具有一般性一些,在实践中,可以用策略模式来封装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理,在这点上策略模式是包含状态模式的功能的,策略模式是一个重要的设计模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值