Part 1. 命令模式代码示例
假设有如上一段第一代版本的代码,run函数用于执行对receiver模块的一系列操作/命令。
随着项目的进展,V1需要做一些改变,新增了命令得到V2的代码版本,如下所示:
随着操作和命令的继续增多,我们需要不断对代码进行修改:
更加糟糕的是,如果我们希望对命令进行更精细的控制,比如我们希望有相关的使能信号可以开关相关的命令,则代码将会演化成如下的糟糕状态:
代码语言:javascript复制class Invoker
{
public:
void run(Receiver *receiver,
bool IsCrcEnable,
bool IsYuvEnable,
bool IsNewCmd0Enable,
...,
bool IsNewCmdNEnable)
{
receiver->TurnOn;
if (IsCrcEnable)
{
receiver->CrcOn;
}
if (IsYuvEnable)
{
receiver->YuvOn;
}
if (IsNewCmd0Enable)
{
receiver->NewCmd0On;
}
if (IsNewCmd1Enable)
{
receiver->NewCmd1On;
}
if (IsNewCmd2Enable)
{
receiver->NewCmd2On;
}
.
.
.
if (IsNewCmdNEnable)
{
receiver->NewCmdNOn;
}
...
if (IsNewCmdNEnable)
{
receiver->NewCmdNOff;
}
.
.
.
if (IsNewCmd2Enable)
{
receiver->NewCmd2Off;
}
if (IsNewCmd1Enable)
{
receiver->NewCmd1Off;
}
if (IsNewCmd0Enable)
{
receiver->NewCmd0Off;
}
if (IsYuvEnable)
{
receiver->YuvOff;
}
if (IsCrcEnable)
{
receiver->CrcOff;
}
receiver->TurnOff;
}
}
代码语言:javascript复制
更不要说,当我们需要对这些操作/命令进行排队,制定优先级和取消操作了。可想而知我们的代码将会变得十分冗长,并且难以阅读和维护(阅读和维护代码往往占用了我们80%的编程时间)。
命令模式可以非常好的解决这个问题。让我们来试着发现这个模式。
上述代码最大的问题是什么呢?run函数知道的太多了(正如我们在之前几篇文章中所说的一样:))!
事实上run函数只需要进行执行操作,并不需要将具体执行的具体操作hard coding进我们的函数体内。
到这里,仔细的同学估计已经想到了,套用之前几篇文章的套路,还是使用多态来进行代码改造(所以说多态才是OOP的根本,只不过C 和SV的多态都借用了继承的方式而已)。
我们将每一个命令都封装一个个具体的对象,那么就非常容易对这些命令进行使能操作。具体可见如下代码(不要在意具体的细节,这里只是示意代码):
代码语言:javascript复制class Command
{
public:
Command(Receiver *pReceiver): m_pReceiver(pReceiver) {}
virtual void excute() = 0;
protected:
Receiver *m_pReceiver;
};
class CrcOnCmd: public Command
{
public:
CrcOnCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->CrcOn();
}
};
class CrcOffCmd: public Command
{
public:
CrcOffCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->CrcOff();
}
};
class TurnOnCmd: public Command
{
public:
TurnOnCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->TurnOn();
}
}
class TurnOffCmd: public Command
{
public:
TurnOffCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->TurnOff();
}
}
class YuvOnCmd: public Command
{
public:
YuvOnCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->YuvOn();
}
}
class YuvOffCmd: public Command
{
public:
YuvOffCmd(Receiver *pReceiver): Command(pReceiver) {}
virtual void excute()
{
m_pReceiver->YuvOff();
}
}
class Receiver
{
public:
virtual void TurnOn() {}
virtual void TurnOff() {}
virtual void CrcOn() {}
virtual void CrcOff() {}
virtual void YuvOn() {}
virtual void YuvOff() {}
}
class Invoker
{
public:
void AddCmd(Command *cmd)
{
vector.push_back(cmd);
}
void RemoveCmd(Command *cmd)
{
auto it = find(m_CommandVector.begin(), m_CommandVector.end(), cmd);
if (it != m_CommandVector.end())
m_CommandVector.erase(it);
}
void Run()
{
for (auto &it : m_CommandVector)
it->excute();
}
private:
std::vector<Command*> m_CommandVector;
}
int main()
{
Invoker *invoker = new invoker();
Receiver *receiver = new Receiver();
CrcOnCmd *crconcmd = new CrcOnCmd(receiver);
CrcOffCmd *crcoffcmd = new CrcOffCmd(receiver);
TurnOnCmd *turnoncmd = new TurnOnCmd(receiver);
TurnOffCmd *turnoffcmd = new TurnOffCmd(receiver);
YuvOnCmd *yuvoncmd = new YuvOnCmd(receiver);
YuvOffCmd *yuvoncmd = new YuvOffCmd(receiver);
invoker->AddCmd(crconcmd);
invoker->AddCmd(yuvoncmd);
invoker->AddCmd(turnoncmd);
invoker->Run();
return 0;
}
代码语言:javascript复制
通过使用AddCmd和RemoveCmd方法我们就可以非常轻松的对命令进行增加和删除操作(示例代码中是逐一调用AddCmd方法,有没有更好的方法呢?)。
并且我们可以在此基础上非常方便的加入优先级功能,确保某些命令优先执行,只要对不同的命令赋予不同的优先级值即可,Run方法可以根据不同的优先级值对命令进行排序执行操作,对应的代码也不难实现。
Part 2. UVM Sequence
本质上发给DUT的激励就可以看成是一个个具体的命令,在UVM中,这些激励被称为transaction。
transaction往往包装在sequence中进行后续的发射操作,UVM中可以对sequence及其中的transaction进行管理,其中包括设置优先级(uvm_do_pri)、同步操作等。
试想,如果不是采用对象的方式对transaction和sequence进行管理,想要实现类似的功能框架代码和具体的业务代码其耦合性得多强。
Part3. 总结
命令模式定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
授权转载于 知乎专栏《UVM方法学与设计模式》