设计模式:状态

设计模式–状态

通常,计算机软件基于称为状态的条件进行操作。 这些状态传统上是使用switch语句实现的。 开关的情况代表了各种状态。 以下示例可用于读取光盘文件


int state = 1;
switch (state)
{
     case 1:
         //open the file
         state = 2;
         break;
      case 2;
         //read a record from the file
         if end of file
         {
             state = 4;
             break;
         }
         state =3;
         break;
......case 3:
........ //displays the record
         state = 2;
         break;
       case 4:
         //close the file
         state = 0;
         break;
} 
在上面的开关中,用户设置初始状态以打开文件。 打开文件后,状态变量将更改为读取文件的状态。 读取状态检查文件结尾,如果未到达文件结尾,则将状态更改为显示状态。 显示状态显示记录,然后将状态设置回读取状态,以读取下一条记录的文件。 在读取状态下,如果已到达文件末尾,则状态将更改为关闭文件。 关闭文件后,状态将更改为无状态,并且退出开关。

请注意,所需要做的就是每个案例(状态)必须知道以下状态。

这是用于从光盘读取文本文件的上述开关。 假设光盘文件包含:

这是

我最容易的事情

见过

该代码将是:


int main()
{
    fstream input;
    int state = 1;
    char buffer[256];
    while (state)
    {
        switch (state)
        {
             case 1:
             //open the file
             input.open("c:\\scratch\\instructor\\input.txt", ios::in);
             state = 2;
             break;
         case 2:
             //read a record from the file
            input.getline(buffer, 256);
             if (input.eof() == true)
             {
                 state = 4;
                 break;
             }
             state = 3;\
             break;
        case 3:
            //displays the record
             cout << buffer << endl;
             state = 2;
             break;
        case 4:
             //close the file
            input.close();
            state = 0;
            break;
        }  //end of switch
    } //end of while
} 
用户设置初始状态,然后编写一个循环,只要存在状态(switch语句中的情况),该循环就会继续。 每个案例都执行其动作,然后将状态更改为后继状态。

自己尝试此代码。 编译并运行它,直到您看到案例如何协同工作。

局限性

切换方法(一种转到编程方式)的局限性在于,如果需要添加或删除任何状态,则需要更改切换。 此外,开关的用户需要了解状态。 也就是说,没有上下文。 一切都无需封装就可以完成。

对于大量已安装的软件,更改开关将导致需要重新安装用户群。 也就是说,由变化引起的涟漪具有海啸的比例。

面向对象的方法–第1部分

切换真正发生的是,处理根据状态而变化。 在面向对象的世界中,行为的改变意味着对象的类型有所不同。 在这种情况下,需要的是一个看起来会根据其内部状态更改其类型的对象。

该对象称为

上下文对象 ,它包含状态以及执行操作的请求。 用户仅创建上下文对象并传递服务请求。

为了说明这一点,下面的示例将以面向对象的方式实现该开关。


int main()
{
    FileRead context;
    context.Request("c:\\scratch\\instructor\\input.txt");
    context.Process(); 
} //end of main 
首先,创建上下文对象,然后提交服务请求。 在这种情况下,请求是要读取的光盘文件的名称。 然后,要求上下文对象处理请求。 结果将是屏幕上显示光盘记录。

这里没有关于状态的任何信息。 用户摆脱了这种负担。

上下文类是:


class FileRead
{
    private:
        State* CurrentState;
        fstream TheFile;
        string request;
    public:
        FileRead();
        ~FileRead();
        void Request(string name);
        void Process();
        void ChangeState(State* theNewState);
        fstream& GetFileStream();
}; 
上下文对象包含其内部状态为CurrentState。 当上下文对象处理请求时,正是该成员需要更改类型。 由于这是一个文件读取上下文对象,因此它还包含文件流和用于将信息传递给CurrentState的请求。 该请求的含义将根据CurrentState的类型而有所不同。 有一种接收服务请求的方法和一种处理该请求的方法。有一种通过更改CurrentState指向的对象类型来更改内部状态的方法。 最后,有一种方法可以返回对文件流的引用。

这些方法的实现可能如下:


FileRead::FileRead() : CurrentState(0)
{ 
}
void FileRead::Process()
{
    ChangeState(new OpenState);
    string token;
    while (CurrentState)
    {
        CurrentState->DoProcess(this->request, *this, token);
    }
}
void FileRead::ChangeState(State* theNewState)
{
    delete CurrentState;
    CurrentState = theNewState;
}
fstream& FileRead::GetFileStream()
{
        return this->TheFile;
}
void FileRead::Request(string name)
{
    this->request = name;
}
FileRead::~FileRead()
{
    delete CurrentState;
} 
实际状态是一个以State为基类的多态层次结构。 State类具有一个虚拟的析构函数,可以将其用作多态基类。 唯一的方法是接收请求,上下文对象和令牌的处理方法。

class State
{
   public:
      virtual ~State(); //a signal that it's OK to derive from this class. 
      //the derived object's process
      virtual void DoProcess(string& request, FileRead& context, string& token) = 0;
};
State::~State()
{
    //nothing to do
}  
这些是各种状态的派生类。 DoProcess方法是State基类DoProcess方法的重写。 覆盖在派生类中是私有的,强制调用来自状态指针或引用。 这些导出状态不能直接使用。

在处理请求时,可以根据需要使用令牌参数来在状态之间传递信息。


class OpenState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
};
class ReadState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
};
class DisplayState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
};
class ExitState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
}; 
这些是各种状态的DoProcess实现。 您可以查看请求根据状态如何变化。 OpenState将请求视为要打开的文件名。 ReadState将其视为返回已读记录的一种方式。
 
void OpenState::DoProcess(string& request, FileRead& context, string& token)
{
    context.GetFileStream().open(request.c_str(), ios::in);
    context.ChangeState(new ReadState);
}
void ReadState::DoProcess(string& request, FileRead& context, string& token)
{
    char buffer[256];
    context.GetFileStream().getline(buffer, 256);
    if (context.GetFileStream().eof() == true)
    {
         context.ChangeState(new ExitState);
         return;
    }
    request = buffer;
    token = buffer;
    context.ChangeState(new DisplayState); 
}
void DisplayState::DoProcess(string& request, FileRead& context, string& token)
{
    if (token.size())
    {
        cout << token << endl;
    }
    context.ChangeState(new ReadState);
}
void ExitState::DoProcess(string& request, FileRead& context, string& token)
{
    context.GetFileStream().close();
    request.erase();
    token.erase();
    context.ChangeState(0);
} 
您应该能够汇编这些代码示例,并能够读取和显示具有256个字节或更少记录的任何文本文件。

稍加改进,便可以删除256个字节的限制。 然后可以添加状态以写入文件。 显示屏可能会被移除。 最终产品将是一个可以读写任何文件的对象。

面向对象的方法–第2部分

需求经常发生变化。 上面的示例显示从文件读取的记录,每行一条记录。 新要求要求每个记录的每个单词都显示在单独的行上。

为了满足此要求,需要添加一些新状态。 此外,现有状态可能具有新的后续状态。

为了将记录处理成单词,添加了两个状态。 一种是WhiteSpace状态,删除前导空白字符。 第二个单词是WordState,它将单词从记录中分解出来并存储在令牌中。

这些新类如下所示:


class WhiteSpaceState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
};
class WordState :public State
{
    private:
        virtual void DoProcess(string& request, FileRead& context, string& token); 
}; 
DoProcess方法如下所示:
 
void WhiteSpaceState::DoProcess(string& request, FileRead& context, string& token)
{
                while (isspace(request[0]) )
    {
        request.erase(0,1);
    } 
    if (request.size())
    {
        context.ChangeState(new WordState);
    }
    else
    {
        context.ChangeState(new ReadState);
        return;
    }  
} 
void WordState::DoProcess(string& request, FileRead& context, string& token)
{
    token.erase();
    while (request.size() && !isspace(request[0]))
    {
        token +=request[0];
        request.erase(0, 1);
    } 
    //This is the successor state
    context.ChangeState (new DisplayState);
} 
WhiteSpaceState :: DoProcess确定是否存在请求。 如果不是,则状态更改为ReadState。 否则,将从请求中删除前导空格字符,并将状态更改为WordState。

WordState :: DoProcess会将单词放入令牌中。 它只是逐个字符地移动,直到遇到空白字符为止。 将单词放入令牌中后,状态将更改为DisplayState,以便可以在屏幕上显示单词。

为了支持这些新状态,一些现有状态现在具有新的后续状态。 下面显示的是需要更改的DoProcess方法。 ReadState :: DoProcess现在具有WhiteSpaceState后继程序,因此该记录(请求)将被处理为单词(令牌)。 DisplayState :: DoProcess也具有WhiteSpaceState的后继状态以获取下一个标记。

下面显示的是第一个示例中的方法,其中旧状态被注释掉,新状态被添加。


void ReadState::DoProcess(string& request, FileRead& context, string& token)
{
    char buffer[256];
    context.GetFileStream().getline(buffer, 256);
    if (context.GetFileStream().eof() == true)
    {
         context.ChangeState(new ExitState);
         return;
    }
    request = buffer;
    token = buffer;
    context.ChangeState(new WhiteSpaceState);
    //context.ChangeState(new DisplayState); 
}
void DisplayState::DoProcess(string& request, FileRead& context, string& token)
{
    if (token.size())
    {
        cout << token << endl;
    }
    context.ChangeState(new WhiteSpaceState);
    //context.ChangeState(new ReadState); 
} 
请注意,添加这些状态后,FileRead类完全没有变化。 这意味着main()也没有变化。 除此之外,现有状态的唯一变化是确定新的后续状态。 新的分词功能是通过在原始应用程序中仅更改两行代码来实现的。 单词突破要求的其余部分是用新代码实现的。

通过将各种状态的详细信息隐藏在派生类中,可以实现高度的封装。 这减少了由变化引起的波纹。

使用手柄

由于指针语法是众所周知的,因此本文中的示例使用指针。 但是,建议在实际使用的应用程序中使用。 您应该参考“ C / C ++文章”部分中有关“句柄”的文章。

更多的信息

请参阅Erich Fromm等人的《设计模式》一书,Addison-Wesley,1994年。

本文仅显示国家模式的概念基础,而没有显示使用此模式的动机和后果。

版权所有2007 Buchmiller Technical Associates美国北华盛顿本德

From: https://bytes.com/topic/c/insights/660060-design-patterns-state

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值