通用池:基于策略的设计






4.64/5 (10投票s)
通用池:基于策略的设计。
引言
在我之前的文章(基于 C++ 模板的通用池)中,我解释了如何使用模板和 C++ 实现通用池。此池设计存在一些局限性,我认为通过应用基于策略的设计方法来克服这些局限性将是一次很好的学习经历。
假定读者对 C++、模板和 STL 有基本了解。我不会详细介绍基于策略的设计。互联网上有很多资源。您也可以参考一本最好的 C++ 书籍,即 Andrei Alexandrescu 的《Modern C++ Design》,或者他的文章(Policy-Based Class Design in C++)。他详细解释了基于策略的设计。
Andrei 将基于策略的类描述为
“基于策略的类设计有助于将具有复杂行为的类组装成许多小的类(称为策略),每个类只负责一个行为或结构方面。顾名思义,策略建立了与特定问题相关的接口。只要您遵守策略接口,就可以以各种方式实现策略”。
注意:
- 为简单起见,“连接”用作需要存储在池中的资源对象。它可以是任何服务器连接或任何其他类型的资源。例如,类对象、线程对象等。
- 本文仅显示部分代码以解释实现。您需要查看源文件以获取完整代码。
让我们找出实际场景中池的不同需求。
- 我们的客户分布在多个 LDAP 服务器上,我们希望从池中为给定的 LDAP 服务器访问 连接。当前设计不支持存储带有标识符的 连接。它只存储无标识的 连接。
- 我们不希望 连接 在进程的整个生命周期内被释放,或者在 连接 空闲时间超过指定时间时释放它们。我们当前的设计只支持对象过期要求。
- 当池创建 连接 时,应用程序可能希望在将其存储到池中之前执行特定初始化,或者在销毁 连接 之前释放相关资源。当前设计不为 连接 的创建和销毁提供自定义要求的灵活性。
- 我们使用 STL 列表将 连接 存储到池中。如果我们要使用 vector 或自定义容器怎么办?在当前设计中,对容器的访问与
PoolMgr
类紧密集成。如果您更改容器,您将不得不完全更改实现。 - 我们可能希望我们的池大小固定,如果池已满,调用者必须等待从池中检索 连接,或者我们可能希望允许临时 连接。我们可能希望根据负载调整池的大小。当前设计不支持这些要求。
让我们看一下每个要求,看看如何满足它。对于第 1 个要求,我们需要在池中存储两种类型的连接对象:带 ID 的连接(可识别)和不带 ID 的连接(通用)。这可以通过“HasIdPolicy
”来实现,该策略有两个类型的类。
注意:我本可以使用结构,但类对于“C”程序员来说会更清晰。
class WithId { public: static bool HasId() { return true;} }; class WithoutId { public: static bool HasId() { return false;} };
对于第 2 个要求,我们需要两种类型的类。一种是在一定空闲时间后过期,另一种是在进程的整个生命周期内都不会过期。这可以通过“ObjectExpirationPolicy
”来实现,该策略有两个类:
class ObjectWithExpiration { public: // is object expire static bool IsExpire() {return true;….} }; class ObjectWithoutExpiration { public: // is expire :no static bool IsExpire(){return false;} };
对于第 3 个要求,我们可以定义“ObjectCreationPolicy
”,其中实现连接创建和销毁功能。用户可以定义他自己的自定义创建策略。我定义了一个默认创建策略,如下所示:
template<class Object> class ObjectCreationDefault //: public ObjectCreationPolicyBase<Object> { public: // create and initialize the object static Object* Create() { return new Object(); } // Uninitialize and destroy the object static Destroy(Object* pObj) { if(pObj) { delete pObj; } pObj = NULL; } // Validate the object if not valid static bool Validate(Object *pObj) { return true; } };
对于第 4 个要求,我们需要实现“ContainerInterface
”。我实现了两个接口,如下所示。我从 ContainerInterfaceBase
派生了这两个类,它将容器定义为基类的成员变量,这两个策略类都继承了这些成员变量。
这些是基于模板的类,它们使用上面定义的策略。这些策略类依赖于“HasIdPolicy
”类和定义实际容器的两种类型的特性。我将在本文后面解释特性。这两种特性都根据选定的策略定义了特定的类型。
template<class Object, class HasIdPolicy, template<CLASS> class CreationPolicy, class ObjectExpirationPolicy, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits<SmartHolder, HasIdPolicy> > class ContainerInterfaceBase { public: ContainerInterfaceBase(){} virtual ~ContainerInterfaceBase(){} protected: ....... Container m_oFree; Container m_oReserved; }; template<class Object, class HasIdPolicy = WithId, template<class> class CreationPolicy = ObjectCreationDefault, class ObjectExpirationPolicy = ObjectWithExpiration, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits< Loki::SmartPtr<typename HolderTraits::ObjectHolder>, WithId> > class ContainerInterfaceWithId {.....};
和
template<class Object, class HasIdPolicy = WithoutId, template<class> class CreationPolicy = ObjectCreationDefault, class ObjectExpirationPolicy = ObjectWithExpiration, typename HolderTraits = ObjectHolderTraits< Object, CreationPolicy,ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits< Loki::SmartPtr<typename HolderTraits::ObjectHolder>, WithId> > class ContainerInterfaceWithoutId {.....};
对于第 5 个要求,我们将定义“PoolSizeType
”策略,它有两个类实现,如下所示:
class PoolSizeFixed { public: // Is Sizable static bool IsSizable() { return false; } ..... }; class PoolSizeDynamic { public: // is pool sizable static bool IsSizable() { return true; } ..... };
我们已经找到了满足这些要求的解决方案。现在,让我们看看如何使用这些策略。如果我们想使用“ObjectExpirationPolicy
”,那么连接创建/访问时间的戳需要与连接对象一起存储。一种方法是修改连接类,添加时间戳成员变量,并在该类上添加 get/set 方法。另一种方法是将时间戳与连接对象一起存储在另一个包装类中,作为连接持有者。
第一种方法更容易,但缺点是所有连接类都需要修改以支持连接过期,这不可行。此外,您可能需要存储一些您无法访问源代码的连接/对象……因此,我决定实现第二种方法,即创建基于“ObjectExpirationpolicy
”的连接持有者类。当您想要存储附加信息(如服务器的用户名和密码)时,持有者类将更有用……
在不进行连接过期的 경우,我们不需要存储时间戳值。我们将创建两种类型的连接持有者类:一种带有连接时间戳作为成员变量,另一种不带时间戳。
另一个问题是如何知道在选定策略的基础上,何时以及使用哪个持有者类?我们应该创建这两个类的实例,然后使用“if else
”根据选定的策略来检查使用哪一个吗?
为了解决这个问题,我们需要了解特性。什么是特性?
Nathan C. Myers 在他的文章(Traits: a new and useful template technique)中对特性给出了简短的定义:
“一个类,用于代替模板参数。作为一个类,它聚合了有用的类型和常量;作为一个模板,它提供了一种“额外的间接层”的途径,可以解决所有软件问题。”
template<class Object, template<typename> class ObjectCreationPolicy, class ObjectExpirationPolicy> class ObjectHolderTraits;
“ObjectHolderTraits
”使用创建策略来创建和销毁连接,并使用过期策略来定义时间戳变量。
我们将使用“ObjectWithExpiration
”和“ObjectWithoutExpiration
”策略对此模板进行部分特化,如下所示:
template<class Object, template<typename> class ObjectCreationPolicy> class ObjectHolderTraits<Object,ObjectCreationPolicy,ObjectWithExpiration> { class Holder { . . . . ; }; template<class Object, template<typename> class ObjectCreationPolicy> class ObjectHolderTraits<Object,ObjectCreationPolicy,ObjectWithoutExpiration> { class Holder { . . . . ; };
第 4 个要求通过“ContainerInterfacePolicy
”通过添加抽象层来访问容器,从而部分满足。但是,如果我们想使用 vector 而不是 list 或 multimap 而不是 map 怎么办?同样,特性也提供了解决此问题的方法。
我们将定义对象容器特性,并与“HasIdPolicy
”进行部分特化。
template<class Object, class HasIdPolicy> class ObjectContainerTraits;
在这里,我们使用了 STL 的“List”容器来处理无 ID 策略,使用了 STL 的“map”容器来处理无 ID 策略。
template<class Object> class ObjectContainerTraits<Object,WithoutId> { public: typedef list<Object> Container; }; template<class Object> class ObjectContainerTraits<Object,WithId> { public: typedef map<string,Object> Container; };
容器内的对象以智能指针的形式存储。有两个原因。
- 当您将连接从空闲移动到保留或从保留移动到空闲时,如果连接持有者对象存储在容器中,它将复制该对象并将其存储到容器中,这在我们的情况下是不可行的,因为连接是指针对象,它可能具有到特定服务器的套接字连接,而该连接无法复制。第二个原因是,它可能没有实现复制构造函数。
- 如果我们将其存储为指针,那么谁来删除内存?STL 容器不会删除指针,因为它假设对象存储在容器内部,其析构函数将清理与之相关的资源。
为了解决这些问题,智能指针是最佳选择,因为我们不必担心内存泄漏和引用计数。我使用了 Loki::SmartPtr
作为智能指针。更多详情请参阅:SourceForge。
现在是时候将所有内容整合在一起并实现通用池了。Pool 类应该实现为单例。我决定使用基于策略设计的 Loki::SingletonHolder<>
类,这是 Loki 库的一部分,而不是编写自己的单例类。
在 The Code Project 上有一篇非常好的文章(Singleton Pattern: A review and analysis of existing C++ implementations)。
该类定义如下:
template<class Object, class HasIdPolicy = WithoutId, class PoolSizePolicy = PoolSizeFixed, class ObjectExpirationPolicy = ObjectWithExpiration, template<class> class CreationPolicy = ObjectCreationDefault, typename HolderTraits = ObjectHolderTraits<Object,CreationPolicy, ObjectExpirationPolicy>, typename ContainerTraits = ObjectContainerTraits<Loki::SmartPtr<typename HolderTraits::ObjectHolder>, HasIdPolicy>, typename ContainterInterfaceTraits = ContainerInterfaceTraits<Object, HasIdPolicy> > class PoolMgr { public: void Init(unsigned nPoolSize, unsigned nExpirationTimeSec); Object* Checkout(string &sId) {...} //withid Object* Checkout() {...} //without id bool Checkin(string &sId) {...} //withid bool Checkin(Object *pObj) {...} // without id void ResizePool(unsigned nNewSize){...} //resize/reset private: typename ContainterInterfaceTraits::ContainerInterfacePolicy m_oContainer; };
在此类中,我提供了池的基本功能。您可以修改它以具有自定义行为。例如,当池已满且不可调整大小时,调用函数应等待多长时间才能返回(参见 1)。如果允许对象过期策略,您可以创建一个线程,该线程将在特定时间唤醒以清理过期的连接。
最后,下面是测试我们池实现的 C++ 代码。请确保使用 Loki::SingletonHolder<>
类来创建池的实例。通过更改 PoolMgr
模板参数的不同策略来尝试它。
// My connection class. class MyConnection { public: MyConnection() { LOG(LOG_INFO, "MyConnection()"); } ~MyConnection() { LOG(LOG_INFO, "~MyConnection()"); } string & Get() { return m_sString; } void Set(string &sStr) { m_sString = sStr; } private: string m_sString; }; // int main() { // create a singleton using Loki::SingletonHolder //without id { typedef PoolMgr< MyConnection, WithoutId, PoolSizeFixed, ObjectWithExpiration> Pool; Pool *pMgr = &Loki::SingletonHolder:: Instance(); pMgr->Init(1,5); MyConnection *pConn = pMgr->Checkout(); pMgr->Checkin(pConn); pMgr->ResizePool(0); } //with id { typedef PoolMgr< MyConnection, WithId, PoolSizeFixed, ObjectWithExpiration> Pool; Pool *pMgr = &Loki::SingletonHolder:: Instance(); string sId = "1"; pMgr->Init(1,5); MyConnection *pConn = pMgr->Checkout(sId); pMgr->Checkin(sId); pMgr->ResizePool(0); } }
注意:我在我的项目中包含了 Loki 库的 Windows 版本。您可以在 SourceForge 上下载最新版本。
参考文献
- 基于 C++ 模板的通用池 Rohit Joshi。
- Policy-Based Class Design in C++ Andrei Alexandrescu。
- 书籍:Modern C++ Design Andrei Alexandrescu。
- Traits: a new and useful template technique Nathan C. Myers。
请投票并评论本文,这将帮助我提高下一篇文章的质量。谢谢。