Android 架构师之路14 设计模式之状态模式

Android 架构师之路 目录

1、状态模式概念

1.1 介绍

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

1.2 定义

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

1.3 使用场景
  • 行为随状态改变而改变的场景
    例如权限设计,人员的状态不同即时执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。

  • 条件、分支判断语句的替代者
    在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理。

2、状态模式UML类图

状态模式UML类图

角色如下:

  • 环境类(Context): 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。
  • 抽象状态类(State): 定义一个接口以封装与Context的一个特定状态相关的行为。
  • 具体状态类(ConcreteState): 每一子类实现一个与Context的一个状态相关的行为。

3、状态模式实现

Context:
public class Context {
    //定义出所有的电梯状态
    public final static OpenningState openningState = new OpenningState();
    public final static ClosingState closeingState = new ClosingState();
    public final static RunningState runningState = new RunningState();
    public final static StoppingState stoppingState = new StoppingState();
    //定一个当前电梯状态
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}
State:
public abstract class LiftState{
    //定义一个环境角色,也就是封装状态的变换引起的功能变化
    protected Context context;

    public void setContext(Context _context) {
        this.context = _context;
    }

    //首先电梯门开启动作
    public abstract void open();

    //电梯门有开启,那当然也就有关闭了
    public abstract void close();

    //电梯要能上能下,跑起来
    public abstract void run();

    //电梯还要能停下来,停不下来那就扯淡了
    public abstract void stop();
}
ConcreteState:
public class ClosingState extends LiftState {
    //电梯门关闭,这是关闭状态要实现的动作
    @Override
    public void close() {
        System.out.println("电梯门关闭...");
    }

    //电梯门关了再打开,逗你玩呢,那这个允许呀
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState); //置为门敞状态
        super.context.getLiftState().open();
    }

    //电梯门关了就跑,这是再正常不过了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState); //设置为运行状态;
        super.context.getLiftState().run();
    }

    //电梯门关着,我就不按楼层
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //设置为停止状态;
        super.context.getLiftState().stop();
    }
}

public class OpenningState extends LiftState {
    //开启当然可以关闭了,我就想测试一下电梯门开关功能
    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.closeingState);
        //动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    //打开电梯门
    @Override
    public void open() {
        System.out.println("电梯门开启...");
    }

    //门开着电梯就想跑,这电梯,吓死你!
    @Override
    public void run() {
        //do nothing;
    }

    //开门还不停止?
    public void stop() {
        //do nothing;
    }
}
public class RunningState extends LiftState {
    //电梯门关闭?这是肯定了
    @Override
    public void close() {
        //do nothing
    }
    //运行的时候开电梯门?你疯了!电梯不会给你开的
    @Override
    public void open() {
        //do nothing
    }
    //这是在运行状态下要实现的方法
    @Override
    public void run() {
        System.out.println("电梯上下跑...");
    }
    //这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //环境设置为停止状态;
        super.context.getLiftState().stop();
    }
}
public class StoppingState extends LiftState {
    //停止状态关门?电梯门本来就是关着的!
    @Override
    public void close() {
        //do nothing;
    }

    //停止状态,开门,那是要的!
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState);
        super.context.getLiftState().open();
    }

    //停止状态再跑起来,正常的很
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState);
        super.context.getLiftState().run();
    }

    //停止状态是怎么发生的呢?当然是停止方法执行了
    @Override
    public void stop() {
        System.out.println("电梯停止了...");
    }
}
Client:
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        //设置电梯当前状态
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

结果输出:

电梯门开启...
电梯门关闭...
电梯上下跑...
电梯停止了...

4、Android中使用举例

QQ加好友受限场景实现
规定:
1、每个QQ号一天最多能够发送50个添加好友的邀请(防止恶意软件频繁添加好友),超过50个QQ号将被限制
2、同一个IP一天最多发送500好友邀请,一旦超过了500个,系统会自动对这个IP限制

public interface IQQState {
    public void onStateChange(String message);
}

public class QQStateNotRestriction implements IQQState {
    @Override
    public void onStateChange(String message) {
        System.out.println("QQ加好友未受限......,"+message);
    }
}
public class QQStateRestriction implements IQQState {
    @Override
    public void onStateChange(String message) {
        System.out.println("QQ加好友受限,不能再加好友......");
    }
}
public class QQContext {

    private IQQState state;
    // 保存好友
    private Map<String, ArrayList<String>> friendNameMap = new HashMap<>();
    // 好友数量
    private Map<String, Integer> friendCountMap = new HashMap<>();

    private List<IQQState> stateList = new ArrayList<>();

    public QQContext() {
        state = new QQStateNotRestriction();
        stateList.add(new QQStateNotRestriction());
    }

    /**
     * 添加好友
     *
     * @param userName
     * @param friendName
     */
    public void addFriend(String userName, String friendName) throws Exception {
        Integer count = friendCountMap.get(userName);
        ArrayList<String> arrayList = friendNameMap.get(userName);
        if (arrayList == null) {
            arrayList = new ArrayList<>();
        }
        arrayList.add(friendName);
        friendNameMap.put(userName, arrayList);
        friendCountMap.put(userName, count == null ? 1 : ++count);

        StateType[] values = StateType.values();

        if (count == null) count = 1;
        for (StateType stateType : values) {
            //筛选出了第一个
            if (count <= stateType.getCount()) {
                state = stateType.getClazz().newInstance();
                break;
            }
        }
    }

    /**
     * 发消息
     *
     * @param message
     */
    public void sendMessage(String message) {

        if (state != null) {
            state.onStateChange(message);
        }
    }

    enum StateType {
        QQStateNot(5, QQStateNotRestriction.class),
        QQState(6, QQStateRestriction.class);
        private int count;
        private Class<? extends IQQState> clazz;

        StateType(int count, Class<? extends IQQState> clazz) {
            this.count = count;
            this.clazz = clazz;
        }

        public int getCount() {
            return count;
        }

        public Class<? extends IQQState> getClazz() {
            return clazz;
        }
    }

}
public class Client {
    public static void main(String[] args) throws Exception {
        QQContext qqContext = new QQContext();
        for (int i = 0; i < 6; i++) {
            qqContext.addFriend("kpioneer", "Lisi" + i);
            qqContext.sendMessage("我是kpioneer");
        }

    }
}

设定超出加好友5个的时候,受限。
结果输出:

QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友受限,不能再加好友......

5、模式总结

5.1 优点
  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
5.2 缺点
  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
特别感谢:

Dream
JAVA_DIRECTION

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容