问题
- 资源管理软件往往需要对某个表格中的所有记录做操作,先执行一次只读动作走一遍表格,放进cache,然后再进行修改操作。程序员不希望重复撰写这种常见逻辑,于是在以下抽象类中提供了一个泛型可复用框架。想法是抽象类将重复工作封装起来,收集需要操作的各个表格行,其次再对每一行执行必要的操作,派生类负责实现特定行为
class GenericTableAlgorithm {
public:
GenericTableAlgorithm(const string& table);
virtual ~GenericTableAlgorithm();
// process()成功时返回true,该函数负责所有工作
// 读取表格中的记录,为每笔记录调用Filter决定是否置于待处理的rows中
// 当待处理的rows组成的list架构完成,为每个row调用ProcessRow()
bool process();
private:
virtual bool Filter(const Record&);
virtual bool ProcessRow(const PrimaryKey&) = 0;
class GenericTableAlgorithmImpl* pimpl_;
};
- 比如客户端生成一个具体的类,在main函数中这样使用
class MyAlgorithm : public GenericTableAlgorithm {
// 重写Filter和ProcessRow做出特定行为
};
int main()
{
MyAlgorithm a("Customer");
a.Process();
}
- 这是什么设计模式,为什么用在此处,pimpl_的作用是什么,这个设计如何改善
解答
- Template Method pattern,用在这里是因为只需要遵循相同步骤,就可以将某个常见解法一般化,只有细节部分可能不同,此部分由派生类实现,甚至一个派生类可以再次使用Template Method,将虚函数重写为另一个虚函数的wrapper,这样不同的步骤就可以填进class的不同层级中
- pimpl_巧妙地将实现细节隐藏起来,它指向的结构内含private成员函数和变量,它们的改变不会造成到客户端需要重新编译的问题
- GenericTableAlgorithm承担两个不同且不相干的任务,可以被有效隔离,因为这两个任务的客户不同,这两种客户是
- client端,使用泛型演算法
- GenericTableAlgorithm,使用特殊化后的concrete “detail” class实现某种特定行为
- 下面是改进后的代码
// File gta.h
// 提供一个公开接口封装共用功能使之成为一个template method
// 可巧妙地被隔离,使本身成为一个集中注意力的class
// 客户目标锁定GenericTableAlgorithm的外部使用者
class GTAClient;
class GenericTableAlgorithm {
public:
// 构造函数现在接受一个具象的implementation对象
GenericTableAlgorithm(const string& table, GTAClient& worker)
// 现在从继承关系抽离出来了,不需要用虚析构函数
~GenericTableAlgorithm();
bool Process();
private:
class GenericTableAlgorithmImpl* pimpl_;
};
// File gtaclient.h
// 提供一个抽象接口,目的是提供扩展性
// 这是GenericTableAlgorithm的一个实作细节,与外部client无关
// 可被巧妙抽离为一个注意力集中的抽象protocal类
// 客户目标锁定concrete "implementation detail" classes的撰写者
// 他们使用并扩展GenericTableAlgorithm
class GTAClient {
public:
virtual ~GTAClient() = 0;
virtual bool Fliter(const Record&);
virtual bool ProcessRow(const PrimaryKey&) = 0;
};
// File gtaclient.cpp
bool GTAClient::Fliter(const Record&)
{
return true;
}
class MyWorker : public GTAClient {
// 重写Filter和ProcessRow做出特定行为
};
int main()
{
GenericTableAlgorithm a("Customer", MyWorker());
a.Process();
}
- 虽然看起来近似,但有三个重要影响
- 如果GenericTableAlgorithm的公开接口改变了,原来所有具象的worker class都要重新编译,因为它们派生自GenericTableAlgorithm,但现在GenericTableAlgorithm的任何改变都被隔离了,不会影响到worker class
- 如果GenericTableAlgorithm的可扩充协定改变了,比如Filter和ProcessRow增加了一些额外参数,原来GenericTableAlgorithm的所有外部client都要重新编译,但现在GenericTableAlgorithm的扩充协定被隔离了,其变化不会影响外部使用者
- 任何具象的work class现在可以在其他任何演算法中被使用,只要该算法能使用Filter和ProcessRow来进行运算
网友评论