Part 1. 策略模式代码示例
在我们的代码中,最常使用的控制语句恐怕非if...else...莫属。对if/else我们是又爱又恨,特别是当代码中出现大量的if/else/else if的时候,对于代码的维护者简直就是噩梦。
让我们直接来看一个示例:
class Invoker
{
public:
Run(std::string moduleName, std::string state, bool bIsAll, bool bIsFull ... )
{
if (moduleName == "module0")
{
if (state == "state0")
{
module0State0();
}
if (state == "state1")
{
module0State0();
}
if (state == "state2")
{
module0State0();
}
if (state == "state3")
{
module0State0();
}
}
else if (moduleName == "module1")
{
if (state == "state0")
{
module1State0();
}
if (state == "state1")
{
module1State0();
}
if (state == "state2")
{
module1State0();
}
if (state == "state3")
{
module1State0();
}
}
else if (moduleName == "module2")
{
if (state == "state0")
{
module2State0();
}
if (state == "state1")
{
module2State0();
}
if (state == "state2")
{
module2State0();
}
if (state == "state3")
{
module2State0();
}
}
else if (moduleName == "module3")
{
if (state == "state0")
{
module3State0();
}
if (state == "state1")
{
module3State0();
}
if (state == "state2")
{
module3State0();
}
if (state == "state3")
{
module3State0();
}
}
...
else if (moduleName == "moduleN")
{
if (state == "state0")
{
moduleNState0();
}
if (state == "state1")
{
moduleNState0();
}
if (state == "state2")
{
moduleNState0();
}
if (state == "state3")
{
moduleNState0();
}
}
else if (bIsAll && state == "state0")
{
module0State0();
module1State0();
module2State0();
module3State0();
...
moduleNState0();
}
else if (bIsAll && state == "state1")
{
module0State1();
module1State1();
module2State1();
module3State1();
...
moduleNState1();
}
else if (bIsAll && state == "state2")
{
module0State2();
module1State2();
module2State2();
module3State2();
...
moduleNState2();
}
else if (bIsAll && state == "state3")
{
module0State3();
module1State3();
module2State3();
module3State3();
...
moduleNState3();
}
else if (bIsFull && moduleName == "module0")
{
module0State0();
module0State1();
module0State2();
module0State3();
}
else if (bIsFull && moduleName == "module1")
{
module1State0();
module1State1();
module1State2();
module1State3();
}
else if (bIsFull && moduleName == "module2")
{
module2State0();
module2State1();
module2State2();
module2State3();
}
else if (bIsFull && moduleName == "module3")
{
module3State0();
module3State1();
module3State2();
module3State3();
}
...
else if (bIsFull && moduleName == "moduleN")
{
moduleNState0();
moduleNState1();
moduleNState2();
moduleNState3();
}
...
}
};
示例代码中的 ... 代码此处省略“1万字”。(事实上,这是某生产环节中的实际代码的美化版本)
我们先简单梳理下这段代码在做什么。假如我们需要处理N个模块,分别为module0~moduleN,每个模块都有State0~State3四种状态,可以认为是四种power state。
这段代码所描述的是这样3件事:
1.可以单独对某个module设置为某种状态;
2.bIsAll为true时可以将所有module设置为某一状态;
3.bIsFull为true时可以将某个module依序设置为State0,1,2,3。
咋看之下,这段代码倒也不错,只不过是代码长度比较感人。然而事实上是,这里的示例经过了美化。
实际的代码中,每个if或者else if下未必是一个个包装好的函数,也许就是一段段裸露的底层代码;
module名字也必然不会是module0,1,2,...如此规则;
状态名字也不会是state0,1,2,...如此规则。
如此冗长、复杂的代码却常常出现在我们的生产代码中,因为if/else/else if是“最简单”不过的添加功能的方式了。
更糟糕的是,如果有一天需要增加/删除一些module,增加/删除一些state,更改处理函数的内部逻辑,就不得不对这一长段代码进行从头到尾的修改。在复杂逻辑的掩盖下,修改的过程更容易引入新的bug,导致更多的修改工作量,更多的regression和debug过程。
然而事实上,你的修改跟大部分原来的逻辑功能并不冲突,原本你唯一需要debug的是你新增加的功能,但是这样看似简单的代码结构,其实严重违背了开闭原则,导致了潜在的更多的工作量。
策略模式很好的解决了这个问题。让我们试着发现这个模式。
一连串的if/else/else if其实所隐含的意义是:我有很多种解决方案,但是每次我会根据你的要求给出一种/某几种解决方案。
原来的代码之所以有这么大的缺陷,其根本问题是:它知道的太多了。其实我们所需要的代码无非是根据不同的输入给出不同的解决方案,至于解决方案具体长什么样并不用关心。如果我们能够抽象出所有解决方案,每次根据不同的输入要求,给出不同的解决方案(或者算法),那么就可以隔开方案具体实现和选取分派过程,达到解耦的目的。
一种抽象,可替换的具体实现,这不就是我们之前一直在使用的技巧,多态,吗。
具体的我们只需要将不同的解决方案从原来的一段段裸露的代码块,包装成一个个具有同一基类的类中的具体处理方法。那么通过基类指针/引用,即可以调用每一种具体的/不同的解决方案/算法。
一种具体的实现方案如下:
class Module
{
public:
Module(std::string moduleName) : m_moduleName(moduleName) {}
virtual void ConfigIntoState(std::string state) = 0;
void AddState(std::string state) {...}
void RemoveState(std::string state) {...}
std::vector<std::string>& GetStates() {...}
std::string GetName()
{
return m_moduleName;
}
protected:
std::string m_moduleName;
std::vector<std::string> m_states;
};
class Module0 : public Module
{
public:
Module0(std::string moduleName) : Module(moduleName) {}
virtual void ConfigIntoState(std::string state)
{
if (state == "state0")
...
else if (state == "state1")
...
else if (state == "state2")
...
else if (state == "state3")
...
}
};
class Module1 : public Module
{
public:
Module1(std::string moduleName) : Module(moduleName) {}
virtual void ConfigIntoState(std::string state)
{
if (state == "state0")
...
else if (state == "state1")
...
else if (state == "state2")
...
else if (state == "state3")
...
}
};
class Module2 : public Module
{
public:
Module2(std::string moduleName) : Module(moduleName) {}
virtual void ConfigIntoState(std::string state)
{
if (state == "state0")
...
else if (state == "state1")
...
else if (state == "state2")
...
else if (state == "state3")
...
}
};
class Invoker
{
public:
void AddModule(Module *module)
{
m_ModuleMap[module.GetName()] = module;
}
void Run(std::string moduleName, std::string state, bool bIsAll, bool bIsFull)
{
if (bIsAll)
{
for (auto &kv : m_ModuleMap)
{
kv.second->ConfigIntoState(state);
}
}
else if (bIsFull)
{
Module *module = m_ModuleMap[moduleName];
for (auto &state : module->GetStates())
{
module->ConfigIntoState(state);
}
}
else
{
m_ModuleMap[moduleName]->ConfigIntoState(state);
}
}
private:
std::unordered_map<std::string, Module*> m_ModuleMap;
};
这种解决方法的好处就在于Run方法不再需要因为增加/删除/修改模块或状态而变动。
整个Run方法在一屏内就能显示完毕,其意图也一目了然。与此同时,增加和删除模块并不会对其他模块造成任何影响:因为他们完全在不同的类中。
同样的,对于state的处理,如果有需要对if/else/else if完全可以再进行一次类似的重构。这里不再赘述。
Part 2. UVM run_test
UVM 起不同test的run_test机制也采用了策略模式的思想。
当我们调用run_test时其实调用的是一个全局task,该task会去调用top的run_test成员方法。
其中最重要的是这个cast过程:
$cast(uvm_test_top, factory.create_component_by_name(test_name,"","uvm_test_top",null));
通过factory创建了一个具体的解决方案,并将其自动挂接到top下:
uvm_test_top自动挂接到uvm_root下
其自动挂接的过程是:
create_component的构造函数new中会判断该component的parent是否为null,如果为null则直接设置其parent为top,即uvm_root。而在前述创建具体解决方案的cast过程中,已经设置其parent为null了。
通过每次根据输入参数名,create不同的component作为uvm_test_top,并自动挂接到uvm tree下,从而实现了具体test(解决方案/算法)和uvm框架运行机制的充分解耦。
Part 3. 总结
策略模式定义:定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。
授权转载于 知乎专栏《UVM方法学与设计模式》