Java设计模式之状态模式详解

摘要:
《设计模式之禅》这本书中对状态模式有着非常详尽的讲解,但总觉得自己没能够理解透彻、灵活运用。后文,我们将实现上述功能的软件模块称为“app安装模块”,本文将以这个案例为基础,围绕实现“app安装模块”展开状态机和状态模式的讲解。三状态模式1为什么要使用状态模式?因此,状态模式是一种高度封装、高度解耦的、易于拓展的架构模式。

(本文由言念小文原创,转载请注明出处)

在实际工作中经常遇到某个对象,处于不同的状态有不同行为逻辑、且状态之间可以相互迁移的业务场景,特别是在开发通信协议栈类软件中尤为多见。《设计模式之禅》这本书中对状态模式有着非常详尽的讲解(目前为止我认为讲解得最好的书),但总觉得自己没能够理解透彻、灵活运用。直到今年完成了一个通信协议软件的开发,重新研究了“状态机”,然后回过头来理解当初学习的状态模式,豁然开朗。因此,本文先从状态机开始讲解,然后结合状态机详细阐述状态模式的两种实现方式,最后给出状态模式的优缺点及其使用场景。

案例描述

按照老风格,本文先描述一个场景案例,然后围绕案例来展开后文。相信每个人都用过手机的应用商城,通常在应用商城中会将可以安装的app以列表(listview)的形式呈现,一个应用占据列表的一个子项(item),如下图1所示:

Java设计模式之状态模式详解第1张

1

我们将注意力聚焦到item的按钮上:

a当检测到可安装的app,按钮显示“安装”;

b点击按钮,软件会去下载app安装包,这时按钮更新视图,显示“正在下载”(即安装进度)

c下载完成后,软件自动安装app,按钮显示“正在安装”;

d安装完成后,按钮显示“打开”,这时点击按钮将打开对应的app

通常,一切顺利,我们安装一个app,按钮会经历“安装”“正在下载”“正在安装”“打开”四种状态。可惜的是,往往事有多磨:

当下载app安装包时,可能出现下载异常,这时按钮切换状态到“下载失败”,点击按钮,软件重新尝试下载,按钮切换状态到“正在下载”;

当安装app时,可能出现安装失败,这时按钮切换状态到“安装失败”,点击按钮,软件重新尝试安装,按钮切换状态到“正在安装”;

以上,便是我们更新一个软件时,可能遇到情况。后文,我们将实现上述功能的软件模块称为app安装模块”,本文将以这个案例为基础,围绕实现“app安装模块”展开状态机和状态模式的讲解。

状态机

1.什么是状态机

通常我们工作中接触到的状态机都是有限状态机,那么什么是有限状态机呢?偷个懒直接百度大挪移:

有限状态机,(英语Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型

注意这个定义里面两个关键字“有限状态”“自动机”。“有限状态”指明状态机中的状态是有限且明确的,在案例中button的状态有:待安装、正在下载、下载失败、正在安装、安装失败、待打开。“自动机”说明状态机中:状态及状态下对应动作是在状态机内部自动转换和执行的,调用状态机的客户端,无需关心状态机内部的状态迁移和动作执行。

2.1状态机的构成要素

状态机由以下四大要素构成:

现态(Qn) -- 当前状态机所处的状态。

次态(Qn+1) -- 状态机要迁移到的新状态。

事件(EVENT)(又称为条件) -- 状态机的触发信号;事件到来,能够触发状态机执行特定动作,或进行状态迁移,或二者皆执行;事件一般来自于状态机外部。

动作(ACTION) -- 事件到来后,状态机执行的动作,动作执行完后,状态机可迁移到新状态也可维持原状态,故而对于状态机中的某一状态,动作并非必须。

2.2状态机的描述方式

状态机的描述方式有两种:状态迁移图和状态机表。

状态迁移图:

状态迁移图通过图形的方式来描述对象的全部状态逻辑,这种方式比较直观、清晰。状态迁移图由状态、状态迁移、事件和动作构成。其中,事件和动作写在状态迁移的带箭头线条上,如图2所示,图2为“app安装模块”状态迁移图,圆圈和双圆圈表示起始和结束状态。

Java设计模式之状态模式详解第2张

2 app安装”状态迁移图

状态机表:

状态迁移表通过矩阵的方式,描述状态机的状态迁移与行为逻辑。状态机表有两种写法:

第一种,横竖表头都为状态,横表头为现态,竖表头为次态,现态和次态相交的单元格为事件触发后要执行的动作,如表1所示:

现态

次态

待安装

下载中

下载失败

安装中

安装失败

待打开

待安装

-

-

-

-

-

-

下载中

download()

-

download()

-

-

-

下载失败

-

undo()

-

-

-

-

安装中

install()

install()

-

-

install()

-

安装失败

-

-

-

undo()

-

-

待打开

-

-

-

undo()

-

open()

1

注意1:途中undo()为表示只做状态转移,实际不执行其他动作。

注意2:由于不论什么状态,只要状态迁移了,都会有UI上变化,因此更新UI的动作updateView()不重复的提现啊状态机图和状态机表中。

第二种,横表头为现态,竖表头为事件触发后的动作,现态和动作相交的单元格,为次态。

现态

动作

待安装

下载中

下载失败

安装中

安装失败

待打开

download()

下载中

-

下载中

-

-

-

install()

安装中

安装中

-

-

安装中

-

open()

-

-

-

待打开

-

待打开

2

两种状态机表各有特点,第一种比较适合状态较多的情况,第二种适合动作比较多的情况(根据小文的个人工程经验,比较推荐第二种)。从状态表中可以看到,状态表不能很好的描述出单个状态和动作的触发事件,因此通常状态表还是需要和状态迁移图结合使用的。

2.1状态机的运行过程

状态机的运行实际是状态的迁移和对应动作的执行,这里我总结如下的运行分支:

EVENT-->ACTION // 事件触发,只执行动作,不转移状态

EVENT-->TRANS STATE // 事件触发,只转移状态,不执行动作

EVENT-->ACTION-->TRANS STATE // 事件触发,先做动作,后转移状态

需要说明的是:这里的ACTION通常都是触发状态转移必须要做的动作,如果不做,状态将无法成功迁移。比如,案例“app安装模块”从“安装”到“正在下载”状态的迁移,状态迁移前必须要执行download()动作,如果没有执行这个动作,状态是无法成功迁移的。

有人可能会有疑问,可以按照EVENT-->TRANS STATE-->ACTION运行吗,其实在实际的编码过程中,是可以的:案例中“app安装模块”先从“安装”迁移到“正在下载”状态,紧接着在“正在下载”状态下执行download()动作,这样在功能实现上与前一种运行顺序没有差异。不过,我个人更喜欢EVENT-->ACTION-->TRANS STATE这种顺序,因为这种顺序更加符合我们的自然逻辑。

状态模式

1 为什么要使用状态模式?

1.1什么是状态模式?

在此,我不想套用GOF的定义,因为定义往往是总结和概括后高度提炼的概念,不太利于理解。当我们在项目开发过程中,分析某些业务对象或模块,发现他们的运行规律表现为状态机特征的时候,状态模式可能就要提上我们架构方案了。那么到底什么是状态模式呢?别急,看完后面的文章,相信你自己能总结出来。

1.2为什么要使用状态模式

我们使用状态模式就是为了用软件实现具有状态机特征的业务对象,为什么要这样做呢?在状态机的定义一节,我们讲到“状态及状态下对应动作是在状态机内部自动转换和执行的,调用状态机的客户端,无需关心状态机内部的状态迁移和动作执行”。因此,状态模式是一种高度封装、高度解耦的、易于拓展的架构模式。这么好的模式,当然要啦。

2 使用状态模式完成案例

我们先来分解一下状态模式要达到的目标:a.状态及状态下对应动作是在状态机内部自动转换和执行的;b.调用状态机的客户端,无需关心状态机内部的状态迁移和动作执行。

要达到目标a,那么我们每种具体状态必须要进行封装:状态内部的动作和转换,是要封装在这个状态内部的,每个状态都必须至少要将以下两个要素封装其中:动作、状态转移方法。

要实现目标b,具体的各种状态就不能直接暴露给调用的客户端(Client),从Client到各具体状态(ConcreteState),中间必须要有一个对象,对各状态进行统一管理和无差别的暴露给ClientClient只需要与这个对象交互,就能触发软件模块自动正确运行。

2.1静态类图

通过目标分解,然后反向推理分析,便可以直接给出静态类图方案:

Java设计模式之状态模式详解第3张

Client:调用“app安装模块”的客户端。

StateContext:状态上下文,即状态的环境类,对各个具体状态进行封装和管理,让各个具体状态无差别的曝露给客户端,StateContext曝露给客户端的永远是当前的状态。

State:抽象状态。

ConcreteState:具体状态。

2.2状态模式实现代码

2.2.1方式1

方式1按照我们常规的自然逻辑,在各个状态中按照EVENT-->ACTION-->TRANS STATE顺序运行。

第一步定义抽象State

public abstract classState {
    
    publicStateContext stateContext;
    
    public voidsetStateContext(StateContext context){
        stateContext =context;
    }
    
    protected voidupdateView(String s) {
        System.out.println("update button view = " +s);
    };
    
    protected abstract voiddoAction(Event e);
    
    protected abstract voidtransState(Event e);
    
    public abstract voideventChange(Event e);

}

对于状态,必须要有事件触发、执行动作、状态转移几种方法,结合本案例,还要有更新UI的方法,此外一个状态必须要持有状态环境对象stateContext,才能在状态迁移的时候,更新stateContext中的当前状态。

第二步定义StateContext

public classStateContext {
    
    //当前状态
    publicState currState; 
    
    //定义出所有状态
    public static final StateToInstall stateToInstall = newStateToInstall();
    public static final StateDownloading stateDownloading = newStateDownloading();
    public static final StateDownloadFailed stateDownloadFailed = newStateDownloadFailed();
    public static final StateInstalling stateInstalling = newStateInstalling();
    public static final StateInstallFailed stateInstallFailed = newStateInstallFailed();
    public static final StateToOpen stateToOpen = newStateToOpen();
    
    publicStateContext(State state) {
        currState =state;
        //context对象传递给当前状态对象
        this.currState.setStateContext(this);
    }

    /*** 获取当前状态
     * @return当前状态
     */
    publicState getCurrState() {
        returncurrState;
    }

    /*** 设置当前状态
     * @paramcurrState
     */
    public voidsetCurrState(State currState) {
        this.currState =currState;
        //context对象传递给当前状态对象
        this.currState.setStateContext(this);
    }
    
    /*** 触发条件改变
     * @parame
     */
    public voideventChange(Event e) {
        currState.eventChange(e);
    }

}

注意:StateContextState之间是聚合关系,故而在StateContext中定义出所有具体状态。

第三步定义事件类型

这里用一个枚举类来定义触发app安装模块”动作执行和状态迁移的事件信号

public enumEvent {
    
    EVENT_CLICK, //按钮点击
    EVENT_DOWNLOAD_FAILED, //下载失败
    EVENT_DOWNLOAD_SUCCESS, //现在成功
    EVENT_INSTALL_FAILED, //安装失败
    EVENT_INSTALL_SUCCESS, //安装成功
}

第四步定义具体状态类

/*** “待安装”状态类
 * @author言念小文
 *
 */
public class StateToInstall extendsState{

    @Override
    protected voiddoAction(Event e) {
        if(Event.EVENT_CLICK.equals(e) && !checkDownloaded()) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "do action download()");
            updateView("下载中");
            return;
        }
        if(Event.EVENT_CLICK.equals(e) &&checkDownloaded()) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "do action install()");
            updateView("安装中");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_CLICK.equals(e) && !checkDownloaded()) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "transfer state to StateDownloading");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateDownloading);
            return;
        }
        
        if(Event.EVENT_CLICK.equals(e) &&checkDownloaded()) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "transfer state to StateInstalling");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateInstalling);
            return;
        }
        
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_CLICK.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }
    
    private booleancheckDownloaded() {
        return false;
    }

}

在具体类中实现doAction(Event e)transState(Event e)eventChange(Event e)方法,具体类持有环境对象StateContext的实例。当外部事件信号通过StateContext传入某个具体类中,StateContext调用具体类中的eventChange(Event e)方法,eventChange(Event e)方法通过调用doAction()transState()来实现动作的执行和状态的转移,这样具体类就将本状态执行的动作和状态迁移全部封装在具体状态类中,Client只需要调用StateContext实例,而无需关心具体的状态类。

/*** “下载中”状态类
 * @author言念小文
 *
 */
public class StateDownloading extendsState{

    @Override
    protected voiddoAction(Event e) {
        //无论是下载成功或失败,无需执行其他动作,紧更新view
        if(Event.EVENT_DOWNLOAD_FAILED.equals(e)) {
            System.out.println("current state = StateDownloading, "
                    + "event change signal = download failed, "
                    + "do action nothing");
            updateView("下载失败");
            return;
        }
        if(Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) {
            System.out.println("current state = StateDownloading, "
                    + "event change signal = download success, "
                    + "do action install()");
            updateView("安装中");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_DOWNLOAD_FAILED.equals(e)) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "transfer state to StateDownloadFailed");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateDownloadFailed);
            return;
        }
        if(Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) {
            System.out.println("current state = StateToInstall, "
                    + "event change signal = click button, "
                    + "transfer state to StateInstalling");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateInstalling);
            return;
        }
        
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_DOWNLOAD_FAILED.equals(e)
                && !Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }

}


/*** “下载失败”状态类
 * @author言念小文
 *
 */
public class StateDownloadFailed extendsState{

    @Override
    protected voiddoAction(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            System.out.println("current state = StateDownloadFailed, "
                    + "event change signal = click button, "
                    + "do action download()");
            updateView("下载中");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            System.out.println("current state = StateDownloadFailed, "
                    + "event change signal = click button, "
                    + "transfer state to StateDownloading");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateDownloading);
            return;
        }
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_CLICK.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }

}

/*** “安装中”状态类
 * @author言念小文
 *
 */
public class StateInstalling extendsState{

    @Override
    protected voiddoAction(Event e) {
        if(Event.EVENT_INSTALL_FAILED.equals(e)) {
            System.out.println("current state = StateInstalling, "
                    + "event change signal = install failed, "
                    + "do action nothing");
            updateView("安装失败");
            return;
        }
        if(Event.EVENT_INSTALL_SUCCESS.equals(e)) {
            System.out.println("current state = StateInstalling, "
                    + "event change signal = install success, "
                    + "do action nothing");
            updateView("打开");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_INSTALL_FAILED.equals(e)) {
            System.out.println("current state = StateInstalling, "
                    + "event change signal = install failed, "
                    + "transfer state to StateInstallFailed");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateInstallFailed);
            return;
        }
        if(Event.EVENT_INSTALL_SUCCESS.equals(e)) {
            System.out.println("current state = StateInstalling, "
                    + "event change signal = install success, "
                    + "transfer state to StateToOpen");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateToOpen);
            return;
        }
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_INSTALL_FAILED.equals(e) && 
                !Event.EVENT_INSTALL_SUCCESS.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }

}

/*** “安装失败”状态类
 * @author言念小文
 *
 */
public class StateInstallFailed extendsState{

    @Override
    protected voiddoAction(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            System.out.println("current state = StateInstallFailed, "
                    + "event change signal = click button, "
                    + "do action install()");
            updateView("安装中");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            System.out.println("current state = StateInstallFailed, "
                    + "event change signal = click button, "
                    + "transfer state to StateInstalling");
            //状态转移后,设置状态环境类当前状态
stateContext.setCurrState(StateContext.stateInstalling);
            return;
        }
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_CLICK.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }

}

/*** “待打开”状态类
 * @author言念小文
 *
 */
public class StateToOpen extendsState{

    @Override
    protected voiddoAction(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            System.out.println("current state = StateToOpen, "
                    + "event change signal = click button, "
                    + "do action open()");
            //点击打开,button view没有变化
            updateView("打开");
            return;
        }
    }

    @Override
    protected voidtransState(Event e) {
        if(Event.EVENT_CLICK.equals(e)) {
            //状态不发生转移
stateContext.setCurrState(StateContext.stateToOpen);
            return;
        }
    }

    @Override
    public voideventChange(Event e) {
        if(!Event.EVENT_CLICK.equals(e)) {
            return;
        }
        //执行动作
doAction(e);
        //转移状态
transState(e);
    }

}

第五步定义Client,并运行程序

public classClient {
    
    public static voidmain(String[] args) {
        //创建状态环境类对象,并初始化状态
        StateContext context = newStateContext(StateContext.stateToInstall);
        
        //下载
context.eventChange(Event.EVENT_CLICK);
        System.out.println("-----------------------------------------------
");
        
        //下载失败
context.eventChange(Event.EVENT_DOWNLOAD_FAILED);
        System.out.println("-----------------------------------------------
");
        
        //重新下载
context.eventChange(Event.EVENT_CLICK);
        System.out.println("-----------------------------------------------
");
        
        //下载成功
context.eventChange(Event.EVENT_DOWNLOAD_SUCCESS);
        System.out.println("-----------------------------------------------
");
        
        //安装失败
context.eventChange(Event.EVENT_INSTALL_FAILED);
        System.out.println("-----------------------------------------------
");
        
        //重新安装
context.eventChange(Event.EVENT_CLICK);
        System.out.println("-----------------------------------------------
");
        
        //安装成功
context.eventChange(Event.EVENT_INSTALL_SUCCESS);
        System.out.println("-----------------------------------------------
");
    }

}

Client类中,只需要持有StateContext,然后输入不同的事件件号,就可以出发“app安装模块”的动作执行和状态迁移。

执行结果如下:

current state = StateToInstall, event change signal = click button, do action download()

update button view = 下载中

current state = StateToInstall, event change signal = click button, transfer state to StateDownloading

-----------------------------------------------

current state = StateDownloading, event change signal = download failed, do action nothing

update button view = 下载失败

current state = StateToInstall, event change signal = click button, transfer state to StateDownloadFailed

-----------------------------------------------

current state = StateDownloadFailed, event change signal = click button, do action download()

update button view = 下载中

current state = StateDownloadFailed, event change signal = click button, transfer state to StateDownloading

-----------------------------------------------

current state = StateDownloading, event change signal = download success, do action install()

update button view = 安装中

current state = StateToInstall, event change signal = click button, transfer state to StateInstalling

-----------------------------------------------

current state = StateInstalling, event change signal = install failed, do action nothing

update button view = 安装失败

current state = StateInstalling, event change signal = install failed, transfer state to StateInstallFailed

-----------------------------------------------

current state = StateInstallFailed, event change signal = click button, do action install()

update button view = 安装中

current state = StateInstallFailed, event change signal = click button, transfer state to StateInstalling

-----------------------------------------------

current state = StateInstalling, event change signal = install success, do action nothing

update button view = 打开

current state = StateInstalling, event change signal = install success, transfer state to StateToOpen

-----------------------------------------------

2.2.2方式2

前文我们说了,在实际编码的过程中,EVENT-->TRANS STATE-->ACTION执行顺序也是可以的的,只不过需要将Action定义在次态中,然后次态中的Action要委托到现态中执行。具体的编码方式请参照《设计模式之禅道》关于状态模式的章节。

状态模式优缺点及使用场景

1 状态模式优缺点

优点:从前文我们已经可以看出,Client只需要持有StateContext实例,仅仅通过事件信号就可以驱动“app安装模块”的运行,无需关注软件模块内部实现,故具有很好的封装性,能很好解耦。如果要添加一种新状态,只需要添加一个新的状态子类,无需影响其他类,故而扩展性良好。

缺点:随着状态增加,状态子类会增多,导致类膨胀。

2 应用场景

个人认为,对于某个模块或者对象,其行为出现状态机特征的均可以使用该模式,以达到解耦、高扩展性、避免过多条件分支语句的目的。

免责声明:文章转载自《Java设计模式之状态模式详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关于VirtualBox创建新的虚拟机后ip地址自动修改的问题你真的会玩SQL吗?删除重复数据且只保留一条下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Web Worker javascript多线程编程(一)

什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验。 一般来说Javascript和UI页面会共用一个线程,在HTML页面中执行js脚本时,页面的状态是不可响应的,直到脚本已完成。而这段代码可以交给Web Worker在后台运行,那么页面在...

Node.js躬行记(4)——自建前端监控系统

这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2。将性能和错误量化。因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统。其实在很早以前就有这个想法,当时已经实现了前端的参数搜集,只是后台迟迟没有动手,也就拖着。 目前完成的还只是个雏形,仅仅是搜集了错误和相关的性能参数。 后台样式采用了封装过的matrix。 分析功能还很薄弱...

IE、火狐(Firefox)和谷歌(Google Chrome)浏览器差异【收集】

项目的页面要求javascript在IE、火狐(Firefox)和谷歌(Google Chrome)三个浏览器中都能运行,期间遇到一些问题,现收集并总结一些: 1.获取鼠标的坐标时,使用event.clientX,不要使用event.x,因为火狐不支持event.x,最好使用event.screenX。 2.火狐中不能在js中直接使用event对象,必须将...

Qt中如何禁掉所有UI操作以及注意事项(转)

 刚做完的一个项目,在测试时出现了一个问题:由于多线程的存在,当进行语音识别时:如果用户点击程序界面上的button或者其他接受点击事件后会发出信号的widget时,程序会crash ! 后来尝试着从多线程上去解决,但是比较困难;后来只能从另外一条路来解决,那就是:当语音识别进行时:禁掉一切用户操作!       所谓的禁掉一切UI操作,在手机等手持设备上...

vue指令(7)v-on

理论知识 双向数据绑定中,数据有多个来源,包括后台业务数据,用户网页操作数据等。对于用户网页操作,vue提供了事件机制,对用户操作做出反应。 使用方式 v-on:标准事件='事件处理逻辑'。 标准实践包括点击(click)、焦点(focus)等。在标签中使用时有四种方式,以点击事件为例 < button v-on:click='msg++'>...

Python学习之路:红绿灯例子

importtime,threading event =threading.Event() deflighter(): count =0 event.set() whileTrue: if count > 5 and count < 10:#改成红灯 event.clear()...