原文链接:https://zhuanlan.zhihu.com/p/631652350

状态模式   

    状态模式(State Pattern)是一种行为型设计模式,主要用于处理复杂系统中,对象的内部状态在不同情况下可能发生变化,并且根据这些状态改变执行不同的行为。在状态模式中,对象的行为依赖于其所处的状态。状态模式通过将各个状态封装成独立的类,并将请求委托给当前状态来实现状态转换与行为的分离。

状态模式有以下角色:

  1. 环境类(Context):也称为上下文,是系统中使用到的拥有多个状态的对象。环境类通常维护一个指向具体状态的引用,然后将与状态相关的操作委托给相应的状态对象。
  2. 抽象状态类(State):定义了一个接口,用于封装与环境类的特定状态相关的行为。
  3. 具体状态类(Concrete State):实现抽象状态类的每个子类,代表了环境类(Context)可能处于的某一具体状态。当环境类中的状态发生切换时,具体状态类会负责处理状态相关的行为逻辑。

适用场景:

  1. 当对象的行为取决于其所处的状态,而对象的状态会动态改变时。
  2. 对象的状态转换逻辑和行为逻辑复杂,导致代码难以阅读和维护。
  3. 系统中某个对象存在多个状态,它们之间的状态转换引发不同的业务行为。

其实在实际开发中,有很多地方体现出系统的状态切换,一些系统的状态切换逻辑可能会比较复杂。如果不使用状态模式,或者后文中将会提到的QStateMachine这种状态模式/状态机框架,会让代码变得很难写也难阅读。

下面给出一个状态模式的C++程序示例:

#include <iostream>
using namespace std;

// Context类
class Context {
public:
    class State* current_state;  // 当前状态
    void setState(State* state) {
        current_state = state;
    }
    void request() {
        current_state->handle(this);
    }
};

// State类
class State {
public:
    virtual void handle(Context* context) = 0;
};

// 具体状态类A
class ConcreteStateA : public State {
public:
    void handle(Context* context) {
        cout << "ConcreteStateA处理请求." << endl;
        context->setState(new ConcreteStateB());
    }
};

// 具体状态类B
class ConcreteStateB : public State {
public:
    void handle(Context* context) {
        cout << "ConcreteStateB处理请求." << endl;
        context->setState(new ConcreteStateA());
    }
};

int main() {
    Context* context = new Context();

    // 初始状态为具体状态类A
    context->setState(new ConcreteStateA());

    // 循环调用handle方法
    for (int i = 0; i < 5; i++) {
        context->request();
    }

    return 0;
}

在这个例子中,我们定义了包含两个具体状态(ConcreteStateA 和 ConcreteStateB)的简单系统。上下文类(Context)将根据其当前状态来执行相应操作并切换状态。

总结一下这个例子中的关键部分:

  1. Context 类具有一个指针 current_state,该指针表示对象所处的当前状态。setState() 方法用于更新状态,当调用 request() 函数时,会委托给当前状态的 handle() 方法。
  2. State 类是一个抽象类,它定义了一个纯虚函数 handle(),所有具体状态类都需要实现该函数以完成特定于状态的行为。
  3. ConcreteStateA 和 ConcreteStateB 分别是 State 类的具体子类,根据具体需求重写了 handle() 方法。在实现过程中,改变了 Context 的状态。
  4. 在 main() 函数中,首先设置 Context 初始状态为 ConcreteStateA,然后在循环中连续调用 request() 以处理请求和切换状态。

在此基础上,可以继续扩展更多的状态和对应业务逻辑,以满足具体需求。

Qt状态机框架

以下示例展示了如何使用Qt状态机框架实现红绿灯的切换功能:

class TrafficLight : public QWidget
{
    Q_OBJECT

public:
    TrafficLight(QWidget *parent = nullptr) : QWidget(parent)
    {
        QVBoxLayout *layout = new QVBoxLayout(this);

        QLabel *lightLabel = new QLabel();
        lightLabel->setFixedSize(100, 300);
        lightLabel->setStyleSheet("background-color: red;");
        layout->addWidget(lightLabel);
		//创建一个QStateMachine对象来管理状态机
        QStateMachine *stateMachine = new QStateMachine(this);
		//定义两个QState对象:redState和greenState,分别代表交通灯中的红色和绿色状态
        QState *redState = new QState(stateMachine);
		//给每个状态分配属性:使用assignProperty()方法为lightLabel设置不同的背景颜色(红或绿)
        redState->assignProperty(lightLabel, "styleSheet", "background-color: red;");
        QState *greenState = new QState(stateMachine);
        greenState->assignProperty(lightLabel, "styleSheet", "background-color: green;");

        // 控制红绿灯颜色切换的时间
        int timeForRed = 5000;
        int timeForGreen = 4000;
		//创建定时器以控制红绿色持续时间
        QTimer *timerForRed = new QTimer(this);
        timerForRed->setInterval(timeForRed);//用于计时红色状态的持续时间
		//并将其设为single-shot模式,确保每次只触发一次超时事件
        timerForRed->setSingleShot(true);

        QTimer *timerForGreen = new QTimer(this);
        timerForGreen->setInterval(timeForGreen);//用于计时绿色状态的持续时间
		//并将其设为single-shot模式,确保每次只触发一次超时事件
        timerForGreen->setSingleShot(true);

        // 将定时器与状态转换关联起来
		//将redState的entered信号与timerForRed的start方法连接,确保在 进入红色状态时 启动定时器
        QObject::connect(redState, &QState::entered, timerForRed, static_cast<void (QTimer::*)()>(&QTimer::start));
		//将greenState的entered信号与timerForGreen的start方法连接,确保在 进入绿色状态时 启动定时器
        QObject::connect(greenState, &QState::entered, timerForGreen, static_cast<void (QTimer::*)()>(&QTimer::start));

		//添加状态转换:将redState与greenState之间的状态转换与timerForRed的timeout信号关联,
		//当timerForRed的timeout信号触发时,状态机将从redState切换到greenState。
        redState->addTransition(timerForRed, &QTimer::timeout, greenState);//绑定了触发当前状态转移目标状态的信号
        greenState->addTransition(timerForGreen, &QTimer::timeout, redState);//添加状态转换
		
		//指定状态机的初始状态为红色
        stateMachine->setInitialState(redState);
		//然后调用start()开始运行状态机
        stateMachine->start();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TrafficLight w;
    w.show();
    return a.exec();
}

这是一个基于Qt状态机框架实现的交通灯信号程序。它创建了一个窗口,包含一个表示交通信号(红/绿)的标签(QLabel)。

程序通过QStateMachine和QState这两个Qt类构建了一个简单的状态机来控制红绿灯信号的切换。在此过程中,使用assignProperty()方法绑定状态与标签背景色,利用entered()信号连接状态与定时器,以及使用addTransition()方法设定基于timeout信号的状态转换。

我们重点关注状态转移逻辑在Qt源码中的实现。也就是说:上面的程序示例中调用了QState::addTransition方法,来绑定了触发当前状态转移目标状态的信号(在上面的示例中,红灯会根据timerForRed定时器的timeout信号,来转移到绿灯状态)。当相应信号发射时,就会发生状态转移。这个过程,在Qt源码内部是如何体现的?根据笔者的调试和源码阅读,可以总结出以下关键节点:

1. 使用 QState::addTransition(const QObject sender, const char signal, QAbstractState *target) 添加一个信号转换。这个方法将创建一个QSignalTransition对象,并根据传入的sender、signal和target参数进行初始化。

/*!
  \fn template <typename PointerToMemberFunction> QState::addTransition(const QObject *sender, PointerToMemberFunction signal, QAbstractState *target);
  \since 5.5
  \overload

  Adds a transition associated with the given \a signal of the given \a sender
  object, and returns the new QSignalTransition object. The transition has
  this state as the source, and the given \a target as the target state.
*/

/*!
  Adds a transition associated with the given \a signal of the given \a sender
  object, and returns the new QSignalTransition object. The transition has
  this state as the source, and the given \a target as the target state.
*/
QSignalTransition *QState::addTransition(const QObject *sender, const char *signal,
                                         QAbstractState *target)
{
    if (!sender) {
        qWarning("QState::addTransition: sender cannot be null");
        return 0;
    }
    if (!signal) {
        qWarning("QState::addTransition: signal cannot be null");
        return 0;
    }
    if (!target) {
        qWarning("QState::addTransition: cannot add transition to null state");
        return 0;
    }
    int offset = (*signal == '0'+QSIGNAL_CODE) ? 1 : 0;
    const QMetaObject *meta = sender->metaObject();
    if (meta->indexOfSignal(signal+offset) == -1) {
        if (meta->indexOfSignal(QMetaObject::normalizedSignature(signal+offset)) == -1) {
            qWarning("QState::addTransition: no such signal %s::%s",
                     meta->className(), signal+offset);
            return 0;
        }
    }
    QSignalTransition *trans = new QSignalTransition(sender, signal);
    trans->setTargetState(target);
    addTransition(trans);
    return trans;
}

2. 调用QState::addTransition(QAbstractTransition transition)将新创建的QSignalTransition对象添加到源状态。此函数首先检查target state是否为空或属于另一个状态机。如果验证成功,该函数会调用QStateMachine::maybeRegisterTransition(QAbstractTransition transition)尝试注册传递的转换。

/*!
  Adds the given \a transition. The transition has this state as the source.
  This state takes ownership of the transition.
*/
void QState::addTransition(QAbstractTransition *transition)
{
    Q_D(QState);
    if (!transition) {
        qWarning("QState::addTransition: cannot add null transition");
        return ;
    }

    transition->setParent(this);
    const QVector<QPointer<QAbstractState> > &targets = QAbstractTransitionPrivate::get(transition)->targetStates;
    for (int i = 0; i < targets.size(); ++i) {
        QAbstractState *t = targets.at(i).data();
        if (!t) {
            qWarning("QState::addTransition: cannot add transition to null state");
            return ;
        }
        if ((QAbstractStatePrivate::get(t)->machine() != d->machine())
            && QAbstractStatePrivate::get(t)->machine() && d->machine()) {
            qWarning("QState::addTransition: cannot add transition "
                     "to a state in a different state machine");
            return ;
        }
    }
    if (QStateMachine *mach = machine())
        QStateMachinePrivate::get(mach)->maybeRegisterTransition(transition);
}

3. QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition transition)决定了具体要采取的操作。此处有两种可能的转换类型:QSignalTransition和QEventTransition。我们关心前者,所以在检查到QSignalTransition实例时,它将调用maybeRegisterSignalTransition(QSignalTransition transition)进行处理。

void QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition *transition)
{
    if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
        maybeRegisterSignalTransition(st);
    }
#if QT_CONFIG(qeventtransition)
    else if (QEventTransition *et = qobject_cast<QEventTransition*>(transition)) {
        maybeRegisterEventTransition(et);
    }
#endif
}

4. 接着,QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition transition)判断状态机是否处于运行状态。如果为true,其将调用registerSignalTransition(QSignalTransition transition)确保信号与QSignalEventGenerator对象连接起来。

void QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition *transition)
{
    Q_Q(QStateMachine);
    if ((state == Running) && (configuration.contains(transition->sourceState())
            || (transition->senderObject() && (transition->senderObject()->thread() != q->thread())))) {
        registerSignalTransition(transition);
    }
}

5. QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition)通过QMetaObject类获取信号的元数据,并确定信号索引值并确保其已连接。然后,使用QMetaObject::connect将给定信号与QSignalEventGenerator对象连接起来。最后,此方法保存新的信号索引信息以备后续使用。

void QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition)
{
    Q_Q(QStateMachine);
    if (QSignalTransitionPrivate::get(transition)->signalIndex != -1)
        return; // already registered
    const QObject *sender = QSignalTransitionPrivate::get(transition)->sender;
    if (!sender)
        return;
    QByteArray signal = QSignalTransitionPrivate::get(transition)->signal;
    if (signal.isEmpty())
        return;
    if (signal.startsWith('0'+QSIGNAL_CODE))
        signal.remove(0, 1);
    const QMetaObject *meta = sender->metaObject();
    int signalIndex = meta->indexOfSignal(signal);
    int originalSignalIndex = signalIndex;
    if (signalIndex == -1) {
        signalIndex = meta->indexOfSignal(QMetaObject::normalizedSignature(signal));
        if (signalIndex == -1) {
            qWarning("QSignalTransition: no such signal: %s::%s",
                     meta->className(), signal.constData());
            return;
        }
        originalSignalIndex = signalIndex;
    }
    // The signal index we actually want to connect to is the one
    // that is going to be sent, i.e. the non-cloned original index.
    while (meta->method(signalIndex).attributes() & QMetaMethod::Cloned)
        --signalIndex;

    connectionsMutex.lock();
    QVector<int> &connectedSignalIndexes = connections[sender];
    if (connectedSignalIndexes.size() <= signalIndex)
        connectedSignalIndexes.resize(signalIndex+1);
    if (connectedSignalIndexes.at(signalIndex) == 0) {
        if (!signalEventGenerator)
            signalEventGenerator = new QSignalEventGenerator(q);
        static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
        bool ok = QMetaObject::connect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);
        if (!ok) {
#ifdef QSTATEMACHINE_DEBUG
            qDebug() << q << ": FAILED to add signal transition from" << transition->sourceState()
                     << ": ( sender =" << sender << ", signal =" << signal
                     << ", targets =" << transition->targetStates() << ')';
#endif
            return;
        }
    }
    ++connectedSignalIndexes[signalIndex];
    connectionsMutex.unlock();

    QSignalTransitionPrivate::get(transition)->signalIndex = signalIndex;
    QSignalTransitionPrivate::get(transition)->originalSignalIndex = originalSignalIndex;
#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q << ": added signal transition from" << transition->sourceState()
             << ": ( sender =" << sender << ", signal =" << signal
             << ", targets =" << transition->targetStates() << ')';
#endif
}

我们尤其关注这两行代码。后面会说到的QSignalEventGenerator::execute这个槽函数之所以会被调用,原因是registerSignalTransition中的这两行代码。这是因为QMetaObject::connect将信号连接到了QSignalEventGeneratorexecute 方法。在这里,生成器方法偏移量 (generatorMethodOffset) 起到关键作用,这个偏移量是QSignalEventGenerator的第一个方法,而这个方法恰好是execute

static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
bool ok = QMetaObject::connect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);

6. 当信号发出时,槽函数QSignalEventGenerator::execute(void **_a)作为中介被调用。execute()方法首先检查状态机是否处于运行阶段,然后将信号信息传递给QStateMachinePrivate::handleTransitionSignal静态方法以触发状态转换。

void QSignalEventGenerator::execute(void **_a)
{
    auto machinePrivate = QStateMachinePrivate::get(qobject_cast<QStateMachine*>(parent()));
    if (machinePrivate->state != QStateMachinePrivate::Running)
        return;
    int signalIndex = senderSignalIndex();
    Q_ASSERT(signalIndex != -1);
    machinePrivate->handleTransitionSignal(sender(), signalIndex, _a);
}

7. QStateMachinePrivate::handleTransitionSignal根据参数创建一个新的信号事件,并将其作为内部事件加入状态机事件队列。

void QStateMachinePrivate::handleTransitionSignal(QObject *sender, int signalIndex,
                                                  void **argv)
{
#ifndef QT_NO_DEBUG
    connectionsMutex.lock();
    Q_ASSERT(connections[sender].at(signalIndex) != 0);
    connectionsMutex.unlock();
#endif
    const QMetaObject *meta = sender->metaObject();
    QMetaMethod method = meta->method(signalIndex);
    int argc = method.parameterCount();
    QList<QVariant> vargs;
    vargs.reserve(argc);
    for (int i = 0; i < argc; ++i) {
        int type = method.parameterType(i);
        vargs.append(QVariant(type, argv[i+1]));
    }

#ifdef QSTATEMACHINE_DEBUG
    qDebug() << q_func() << ": sending signal event ( sender =" << sender
             << ", signal =" << method.methodSignature().constData() << ')';
#endif
    postInternalEvent(new QStateMachine::SignalEvent(sender, signalIndex, vargs));
    processEvents(DirectProcessing);
}

8. 最后,Qt应用在处理事件循环时,调用到bool QStateMachine::event(QEvent *e),此方法负责根据事件类型调用相关处理函数。在状态机信号事件的情况下,未完成的操作会在这里进行处理。

bool QStateMachine::event(QEvent *e)
{
    Q_D(QStateMachine);
    if (e->type() == QEvent::Timer) {
        QTimerEvent *te = static_cast<QTimerEvent*>(e);
        int tid = te->timerId();
        if (d->state != QStateMachinePrivate::Running) {
            // This event has been cancelled already
            QMutexLocker locker(&d->delayedEventsMutex);
            Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid));
            return true;
        }
        d->delayedEventsMutex.lock();
        int id = d->timerIdToDelayedEventId.take(tid);
        QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id);
        if (ee.event != 0) {
            Q_ASSERT(ee.timerId == tid);
            killTimer(tid);
            d->delayedEventIdFreeList.release(id);
            d->delayedEventsMutex.unlock();
            d->postExternalEvent(ee.event);
            d->processEvents(QStateMachinePrivate::DirectProcessing);
            return true;
        } else {
            d->delayedEventsMutex.unlock();
        }
    }
    return QState::event(e);
}

通过上述过程,信号发射、注册、处理和状态转换之间的关系得以完整呈现。在QStateMachine中的逐个步骤,最终使信号影响状态变化,确保仅在正确条件下(例如状态机处于运行状态)才触发状态转换。

总结

Qt状态机框架采用不同的类(如QAbstractState、QState、QStateMachine等)来表示状态机中的相关组件。在此框架中:

  • QAbstractState和其子类代表了抽象状态类和具体状态类。
  • QStateMachine本身充当上下文(Context)类,负责底层事件循环和状态切换管理。

虽然Qt状态机框架引入了信号槽机制和事件循环等特性,不过其核心设计仍然保留了状态模式的原则。状态模式的核心思想在于:将状态抽象成类(抽象状态类和具体状态类),定义一个Context类去拥有多个状态对象,维护多个状态之间的切换;通过将“状态”与“行为”的关键概念直接映射到对应的类中,使得状态迁移和操作逻辑变得更加易于理解和维护。所以,在这点上,Qt状态机框架依旧可以视为状态模式的一种实现。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐