OOP中,Template Method模式的一个典型实现如下:
class AbstractTestCase
{
public:
AbstractTestCase(std::string const& testName)
: testName_(testName)
{}
void RunTest()
{
Logger::Instance().Log("Start Test: " + testName_);
try
{
RunTestImpl();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName_);
}
private:
std::string testName_;
virtual void RunTestImpl() = 0;
};
{
public:
AbstractTestCase(std::string const& testName)
: testName_(testName)
{}
void RunTest()
{
Logger::Instance().Log("Start Test: " + testName_);
try
{
RunTestImpl();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName_);
}
private:
std::string testName_;
virtual void RunTestImpl() = 0;
};
class ConcreteTestCase1 : public AbstractTestCase
{
public:
ConcreteTestCase1()
: AbstractTestCase("ConcreteTestCase1")
{}
private:
virtual void RunTestImpl()
{
...
}
}
{
public:
ConcreteTestCase1()
: AbstractTestCase("ConcreteTestCase1")
{}
private:
virtual void RunTestImpl()
{
...
}
}
... // other ConcreteTestCase that derives from AbstractTestCase
int main()
{
std::vector<AbstractTestCase*> testCases;
testCases.push_back(new ConcreteTestCase1);
testCases.push_back(new ConcreteTestCase2);
...
for(int i=0; i<testCases.size(); ++i)
{
testCases[i]->RunTest();
}
return 0;
}
{
std::vector<AbstractTestCase*> testCases;
testCases.push_back(new ConcreteTestCase1);
testCases.push_back(new ConcreteTestCase2);
...
for(int i=0; i<testCases.size(); ++i)
{
testCases[i]->RunTest();
}
return 0;
}
以上是一个简单的测试框架。其中Logger是一个Singleton对象,它把测试过程中收集的信息写入日志,以便日后分析。
RunTest()和RunTestImpl()是Template Method的实作,所有的ConcreteTestCase复用了RunTest()的流程。
C++的Template Function也可以实现流程的复用,于是上述代码可以改写为:
template <typename Function>
void RunTest(std::string const& testName, Function test)
{
Logger::Instance().Log("Start Test: " + testName);
try
{
test();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName);
}
{
Logger::Instance().Log("Start Test: " + testName);
try
{
test();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName);
}
void test1()
{
...
}
}
void test2()
{
...
}
int main()
{
RunTest("test1", test1);
{
RunTest("test1", test1);
RunTest("test2", test2);
return 0;
}
return 0;
}
这个例子只是想说明,如果不需要OOP的动态接口复用,只需要静态代码复用,那么Template Function是一种很方便的选择。此外,配合使用Boost.Bind (它将成为C++09标准库的一部分),我们可以获得额外的弹性。例如,您的TestCase希望从数据文件中读取测试数据,这将有利于您逐步完善测试数据。此外,您也不愿意把测试数据文件的路径写死在测试代码中,这样您可以在不同的时刻使用不同的测试数据集。这时,您可以这样构建您的测试程序。
#include "boost/bind.hpp"
...
void testWithFile(char const* filePath)
{
...
}
int main(int argc, char* argv[])
{
// Smoking tests that should be run every time
{
// Smoking tests that should be run every time
RunTest("test1", test1);
RunTest("test2", test2);
if(2 == argc)
{
// Load test file whose path is in argv[1]
RunTest("testWithFile", boost::bind(testWithFile, argv[1]));
}
return 0;
}
return 0;
}
实际上,利用Boost.Function (它也将成为C++09标准库的一部分),我们甚至可以将RunTest()重构为普通函数 (Refactor Template Function to Trivial Function with Generic Delegate)。
#include "boost/bind.hpp"
#include "boost/function.hpp"
void RunTest(std::string const& testName,
boost::function0<void> const& test)
{
Logger::Instance().Log("Start Test: " + testName);
try
{
test();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName);
}
{
Logger::Instance().Log("Start Test: " + testName);
try
{
test();
}
catch(std::exception& e)
{
Logger::Instance().Log("Exception: " + e.what());
}
catch(...)
{
Logger::Instance().Log("Unknown Exception");
}
Logger::Instance().Log("End Test: " + testName);
}
...
int main(int argc, char* argv[])
{
// Smoking tests that should be run every time
{
// Smoking tests that should be run every time
RunTest("test1", test1);
RunTest("test2", test2);
if(2 == argc)
{
// Load test file whose path is in argv[1]
RunTest("testWithFile", boost::bind(testWithFile, argv[1]));
}
return 0;
}
return 0;
}
这个简单的例子表明,C++ Template Metaprogramming将通过程序库来影响我们的编程方式。