03 ноября 2007

Policy-based programming

Александреску в своей книге "Современное проектирование на С++" заразил меня тем, что он называет policy-based class design - разработка класса на основе его составляющих - "политик". Примером может являться умный указатель: при разработке обобщенного умного указателя можно не догадываться как именно конечному программисту потребуется от умного указателя:
а) управлять памятью (выделять память, удалять...),
б) реализовывать "владение" объектом (разделять одно значение, копировать...),
в) проверять значение указателя на правильность инициализации (через assert или исключения...),
г) ...

Что можно сделать - написать шаблон класса, который в качестве параметров (шаблона) принимает классы, реализующие эти самые стратегии: управления памятью, владения объектом, и т.д.

Так вот эту же замечательную идею можно легко использовать не только в статике (шаблоны), а и в динамике. Идея такова: для нужных стратегий (policy) пишете интерфес и храните в своем классе указатель (умный, разумеется) на объект этого интерфейсного класса.

Пример:
template <class StoragePolicy, class OwnershipPolicy>
class StaticPolicyDemo
{
void Delete()
{
if (OwnershipPolicy::IsDeletable())
StoragePolicy::Delete(ptr);
}
...
};

class DynamicPolicyDemo
{
class IStoragePolicy
{
virtual void Delete(Object*) = 0;
...
};
class IOwnershipPolicy
{
virtual bool IsDeletable() = 0;
...
};

void Delete()
{
if (_OwnershipPolicy->IsDeletable())
_StoragePolicy->Delete(ptr);
}

DynamicPolicyDemo(PStoragePolicy StoragePolicy, POwnershipPolicy OwnershipPolicy) :
_StoragePolicy(StoragePolicy), _OwnershipPolicy(OwnershipPolicy)
{
}

void SetStoragePolicy(PStoragePolicy StoragePolicy)
{
_StoragePolicy = StoragePolicy;
}

void SetOwnershipPolicy(POwnershipPolicy OwnershipPolicy)
{
_OwnershipPolicy = OwnershipPolicy;
}

typedef std::auto_ptr<IStoragePolicy> PStoragePolicy;
typedef std::auto_ptr<IOwnershipPolicy> POwnershipPolicy;
PStoragePolicy _StoragePolicy;
POwnershipPolicy _OwnershipPolicy;
};
Такая конструкция очень полезна, когда нужно в run-time менять части поведения некоторого объекта. Например, графического контрола Grid это может быть: а) внешний вид графического элемента (skin), б) источник записей, в) фильтр записей, и др. Если часто используются типовые policy, то для их хранения логично использовать shared_ptr (вместо auto_ptr, приведенного в примере).

Комментариев нет: