18. “状态变化”模式 之State模式(状态模式)

状态变化模式

  • 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时有维持高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
  • 典型模式
    • State
    • Memento

1. 动机

  • 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生改变,比如文档出于只读状态,其支持的行为和读写状态支持的行为就可能完全不同
  • 如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

2. 代码示例

比如我们有个网络应用,该应用会根据网络的状态进行一些行为上的调整,如下代码所示

    Network_Open,
    Network_Close,
    Network_Connect,
};

class NetworkProcessor{
    NetWorkState state;

public:
    void Operation1(){
        if (state == Network_Open){ // 如果当前是打开状态,则经历过一系列操作之后,再恢复成关闭状态

            // ....
            state = Network_Close;
        }        
        else if(state == Network_Close){ // 如果当前是关闭状态,经历过一些操作之后,设置成连接态
            // ...
            state = Network_Connect;  
        }
        else if (state == Network_Connect){ // 如果当前是连接状态,经历过一些操作之后,设置成打开状态
            // ...
            state = Network_Open;  
        }
    }

    void Operation2(){
        if (state == Network_Open){

            // ....
            state = Network_Connect;
        }        
        else if(state == Network_Close){ 
            // ...
            state = Network_Open;  
        }
        else if (state == Network_Connect){
            // ...
            state = Network_Close;  
        }
    }

    void Operation3(){
        // ...
    }
};

结合上面代码和前面的动机对比理解:

对象的状态如果改变,其行为也会随之而发生改变:Operation1中的if else就已经很清楚的表明了Operation1是根据你的状态不同,而行为不一样,但是在这个过程中它还会改变它的状态

上面代码有什么问题,有前面学习印象的同学,会感觉这个问题似曾相识,这个很像是策略模式里面解决的问题,很多if else的bad Smell,上面出现的很多If else是有关业务状态,策略模式就已经告诉我们,对于这种情况,我们要问个为什么?

上面代码中的枚举类型以后会不会有其他类型出现呢?如果添加了其他的状态,那我之前的代码应该怎么修改,必然是往里面继续添加else if,然后还要梳理新添加的状态和当前已经存在状态的关系,这明显违背了开闭原则

3. 代码改进

我们首先遵从策略模式里面的类似方式,试下是否可行,我们先提抽象基类

// 这里其实就是将前面的枚举进行类型化的
class NetworkState{
public:
    NetworkState *pNext;
    virtual void Operation1() = 0;
    virtual void Operation2() = 0;
    virtual void Operation3() = 0;

    NetworkState(){}
    virtual ~NetworkState(){}
};


// 这里的代码其实就是将判断当前时候是open态的那部分逻辑抽离成了类,即Open态的类
class OpenState : public NetworkState{
    static NetworkState *m_instance;
public:
    static NetworkState* getInstance(){
        if (m_instance != nullptr){
            m_instance = new OpenState();
        }
        return m_instance;
    }

    void Operation1(){

        // ....这段省略逻辑其实就是前面
        // void Operation1(){
        // if (state == Network_Open){
        // 中省略的逻辑的逻辑 

        pNext = CloseState::getInstance();
    }

    void Operation2(){
        // ...
        pNext = ConnectState::getInstance();
    }

    void Operation3(){
        // ...
    }
};

// 下面两个类和前面这个类似
class CloseState : public NetworkState{

};

class ConnectState : public NetworkState{

};

// 网络应用这一层,塞的就不是一个枚举了,而是一个真正的状态对象
class NetworkProcessor{
    NetworkState *pState; //  1. 其实这里,和前面的结果是一样的,只是调用的方法变了而已
public:
    NetworkProcessor(NetworkState *pState){
        this->pState = pState;
    }

    void Operation1(){
        // ..
        pState->Operation1();  //  2. 再看这句话,其实虚函数的本质就是运行时的if else即,如果pState这个指针指向的是open态,那么就会调用Open态的Operation1
        pState = pState->pNext; // 3. 执行完后,就将它的状态修改为它的下一个状态,这个下一个状态是当前状态对象里面决定的
        // ...
    }

    void Operation2(){
        // ..
        pState->Operation2();
        pState = pState->pNext;
        // ...
    }

    void Operation3(){
        // ..
        pState->Operation3();
        pState = pState->pNext;
        // ...
    }

这样做了之后好处是什么,其实和策略模式好处异曲同工,当我们状态增加的时候,其实我们只要增加一个类就可以了,比如增加了一个wait状态,只需要增加一个类似下面的类,并且要在这个类里面维护自己的下一个状态,和OpenState一样,像NetworkProcessor这个类完全不需要修改

class WaitState : public NetworkState{
};

4. 模式定义

允许一个对象在其内部状态改变的时候改变它的行为(其实里里面使用的是多态,即前面代码中NetworkProcessor中维护的NetworkState *pState;)。从而使对象看起来似乎修改了其行为。

                                                                                                             ------《设计模式》GOF

5. 结构

 

 

 State里面一般是多个行为而不是一个Handle(),当然也可以是一个行为,看起来一个行为的时候和策略模式没有什么两样

6. 要点总结

  • State模式将所有与一个特定状态相关的行为(Operation1、2、3)都放入到一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作于状态转换之间的解耦。
  • 为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的---即要么彻底装换过来,要么不转换

        (第一版代码各个操作中夹杂着各种状态的切换,很乱很杂,但是我们最后的版本,一个状态只需要关系各个操作之后的下一个状态是什么就好了,比如:operation1之后的状态是什么,2之后的状态是什么,3之后的状态是什么,就很清晰)

  • 如果State对象没有实例变量(即没有后继态之后的实例),那么上下文可以共享一个State对象(代码中的单例),从而节省对象开销。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象接口 9 1.6.4 描述对象的实现 10 1.6.5 运用复用机制 13 1.6.6 关联运行时刻和编译时刻的 结构 15 1.6.7 设计应支持变化 16 1.7 怎样选择设计模式 19 1.8 怎样使用设计模式 20 第2章 实例研究:设计一个文档编 辑器 22 2.1 设计问题 23 2.2 文档结构 23 2.2.1 递归组合 24 2.2.2 图元 25 2.2.3 组合模式 27 2.3 格式化 27 2.3.1 封装格式化算法 27 2.3.2 Compositor和Composition 27 2.3.3 策略模式 29 2.4 修饰用户界面 29 2.4.1 透明围栏 29 2.4.2 Monoglyph 30 2.4.3 Decorator 模式 32 2.5 支持多种视感标准 32 2.5.1 对象创建的抽象 32 2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 2.7 用户操作 40 2.7.1 封装一个请求 41 2.7.2 Command 类及其子类 41 2.7.3 撤消和重做 42 2.7.4 命令历史记录 42 2.7.5 Command 模式 44 2.8 拼写检查和断字处理 44 2.8.1 访问分散的信息 44 2.8.2 封装访问和遍历 45 2.8.3 Iterator类及其子类 46 2.8.4 Iterator模式 48 2.8.5 遍历和遍历过程中的动作 48 2.8.6 封装分析 48 2.8.7 Visitor 类及其子类 51 2.8.8 Visitor 模式 52 2.9 小结 53 第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 模式 87 3.5 Singleton(单件)—对象创建型 模式 84 3.6 创建型模式的讨论 89 第4章 结构型模式 91 4.1 Adapter(适配器)—类对象结构型 模式 92 4.2 Bridge(桥接)—对象结构型 模式 100 4.3 Composite(组成)—对象结构型 模式 107 4.4 Decorator(装饰)—对象结构型 模式 115 4.5 FACADE(外观)—对象结构型 模式 121 4.6 Flyweight(享元)—对象结构型 模式 128 4.7 Proxy(代理)—对象结构型 模式 137 4.8 结构型模式的讨论 144 4.8.1 Adapter与Bridge 144 4.8.2 Composite、Decorator与Proxy 145 第5章 行为模式 147 5.1 CHAIN OF RESPONSIBIL ITY(职责链) —对象行为型模式 147 5.2 COMMAND(命令)—对象行为型 模式 154 5.3 INTERPRETER(解释器)—类行为型 模式 162 5.4 ITERATOR(迭代器)—对象行为型 模式 171 5.5 MEDIATOR(中介者)—对象行为型 模式 181 5.6 MEMENTO(备忘录)—对象行为型 模式 188 5.7 OBSERVER(观察者)—对象行为型 模式 194 5.8 STATE状态)—对象行为型模式 201 5.9 STRATEGY(策略)—对象行为型 模式 208 5.10 TEMPLATE METHOD(模板方法) —类行为型模式 214 5.11 VISITOR(访问者)—对象行为型 模式 218 5.12 行为模式的讨论 228 5.12 1 封装变化 228 5.12.2 对象作为参数 228 5.12.3 通信应该被封装还是被分布 229 5.12.4 对发送者和接收者解耦 229 5.12.5 总结 231 第6章 结论 232 6.1 设计模式将带来什么 232 6.2 一套通用的设计词汇 232 6.3 书写文档和学习的辅助手段 232 6.4 现有方法的一种补充 233 6.5 重构的目标 233 6.6 本书简史 234 6.7 模式界 235 6.8 Alexander 的模式语言 235 6.9 软件中的模式 236 6.10 邀请参与 237 6.11 临别感想 237 附录A 词汇表 238 附录B 图示符号指南 241 附录C 基本类 244 参考文献 249
10.1 何为Qt样式表 由于Qt样式表的引入,定制Qt部件的外观样式变得非常简单。 Qt样式表的思想很大程度上是来自于HTML的层叠式样式表(CSS), 通过调用QWidget::setStyleSheet()或QApplication::setStyleSheet(), 你可以为一个独立的子部件、整个窗口,甚至是整个个应用程序指定一个样式表。 样式表是通过QStyle的一个叫做QStyleSheetStyle的特殊子类来实现的。 这个特殊的子类实际上是其他的系统特定风格类的包裹类,它会把通过样式表指定的自定义外观风格应用在底层的系统特定风格之上。 10.2 样式表语法基础 Qt样式表与CSS的语法规则几乎完全相同,如果你已经了解了CSS,完全可以跳过本节。 一个样式表由一系列的样式规则构成。每个样式规则都有着下面的形式: selector { attribute: value } 选择器(selector)部分通常是一个类名(例如QComboBox),当然也还有其他的语法形式。 属性(attribute)部分是一个样式表属性的名字, 值(value)部分是赋给该属性的值。 为了使用方便,我们还可以使用一种简化形式: selector1, selector2, ..., selectorM { attribute1: value1; attribute2: value2; ... attributeN: valueN; } 这种简化形式可以同时为与M个选择器相匹配的部件设置N种属性。 例如: QCheckBox, QComboBox, QSpinBox { color: red; //字体颜色:红 font: bold; } 这个规则设置了所有的QCheckBox、QComboBox和QSpinBox的前景色、背景色和字体。 10.3 方箱模型 在样式表中,每个部件都被看作是一个由四个同心相似的矩形组成的箱体: 空白(margin)、边框(border)、填充(padding)和内容(content)。 对于一个平面部件——例如一个空白、边框和填充都是0像素的部件——而言,这四个矩形是完全重合的。 空白区域位于边框外,并且总是透明的。 边框为部件提供了四周的框架,其border-style属性可以设置为一些内置的框架风格,如inset、outset、solid和ridge。 填充在边框和内容区域之间提供了空白间隔。 10.4 前景与背景 部件的前景色用于绘制上面的文本,可以通过color属性指定。 背景色用于绘制部件的填充矩形,可以通过background-color属性指定。 背景图片使用background-image属性定义,它用于绘制由background-origin指定的矩形区域(空白、边框、填充或内容)。背景图片在矩形区域内的对齐和平铺方式可以通过background-position和background-repeat属性指定。 QFrame { margin: 10px; border: 2px solid green; padding: 20px; background-image: url(qt.png); background-position: top right; background-origin: content; background-repeat: none; } 在这个例子中,QFrame四周的空白、边框和填充值都是一样的。 实际上margin属性可以在上下左右四个方向分别指定我们需要的不同值,例如: QFrame { margin: 14px 18px 20px 18px; } 同时,我们也可以分别指定margin-top、margin-right、margin-bottom、margin-left四个属性。 QFrame { margin-top: 14px; margin-right: 18px; margin-bottom: 20px; margin-left: 18px; } 虽然目前我们仅仅使用了QFrame作为例子,但是我们也可以同样的将这些属性应用于任何一个支持方箱模型的Qt部件,例如:QCheckBox、 QLabel、QLineEdit、QListView、QMenu、QPushButton、QTextEdit、和QToolTip。 10.5 可缩放样式 在默认情况下,通过background-image指定的背景图片会自动重复平铺,以覆盖部件的整个填充矩形(即边框里面的那个区域)。 ///注意区别: 如果我们想创建能够随着部件大小自动缩放而不是平铺的背景,我们需要设置一种称之为“边框图片”的东东。 注意 “边框图片”可以通过border-image属性指定,它同时提供了部件的背景和边框。 一个“边框图片”被分为九个部分(九宫格),有点向tic-tac-toe游戏的棋盘。 当一个部件的边框被填充时,四角的格子通常不会发生变化,而其余的五个格子则可能被拉伸或平铺以填充可用空间。 当指定一个“边框图片”时,除了图片本身,我们还必须指定用来分割九宫格的四条分割线。同时我们还必须指定非边角的格子是应该平铺还是拉伸,以及边框的宽度(用来确定边角格子的大小,防止边角被缩放变形)。 例如,下面的样式表定义了上图中的button: QPushButton { border-width: 4px; border-image: url(button.png) 4 4 4 4 stretch stretch; } 另外,“边框图片”还应该含有alpha通道,以使背景能够在边角处露出来。 10.6控制大小 min-width和min-height两个属性可以用来指定一个部件的内容区域的最小大小。 这两个值将影响部件的minimumSizeHint(),并在布局时被考虑。 例如: QPushButton { min-width: 68px; min-height: 28px; } 如果该属性没有被指定,最小大小将从部件的内容区域和当前样式中继承。 10.7 伪状态 部件的外观可以按照用户界面元素状态的不同来分别定义,这在样式表中被称为“伪状态”。例如,如果我们想在一个push button在被按下的时候具有sunken的外观,我们可以指定一个叫做 :pressed 的伪状态。 QPushButton { border: 2px outset green; } QPushButton:pressed { background: gray; }
状态模式State Pattern)是一种行为型设计模式,它允许对象在其内部状态发生改变改变它的行为。该模式的核心思想是封装对象的状态,使得它对外部的影响最小化。状态模式常常被应用于需要按照状态决定行为的场合,比如状态机、游戏状态等。 在状态模式中,我们通常会定义一个抽象状态类(Abstract State)和若干个具体状态类(Concrete State),每个具体状态类表示对象在不同状态下的行为。同时,我们还需要定义一个环境类(Context),它包含了一个状态对象,负责状态的切换和行为的调用。 状态模式的优点包括: 1. 状态模式状态的处理分散在不同的状态类中,使得每个状态类只需处理自己状态下的行为,降低了代码的复杂度和耦合度。 2. 状态模式状态和行为分离,使得对象的状态可以在运行时动态改变,而不需要改变对象自身的结构。 3. 状态模式符合“开闭原则”,容易扩展和增加新的状态和行为。 但是,状态模式也存在一些缺点,包括: 1. 状态模式会导致类的数量增加,其中每个状态都需要一个具体状态类,这可能会导致类的爆炸。 2. 状态模式会增加代码的复杂度,尤其是当状态之间的转换比较复杂时。 总之,状态模式是一种非常有用的设计模式,它可以让对象状态变化更加灵活和可控。但是,在实际应用过程中,我们需要权衡其优缺点,选择合适的使用场景和实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值