У меня есть куча алгоритмов и коллекций, и я использую дизайн на основе политики (см. Книгу Современный дизайн C ++) иметь дело с произвольной комбинаторной сложностью. Это здорово, но для того, чтобы предотвратить разрушение класса Host с помощью указателя на политику, 1 предлагает сделать деструкторов охраняемых. Однако, если я сделаю защищенными деструкторы Algorithm и Collection, я не смогу использовать их самостоятельно, но только в качестве политик. Кроме того, я не вижу преимуществ дизайна на основе политик по сравнению с общим шаблоном фабрики …
Вот модель кода:
#include <iostream>
template<class Collection>
class AlgorithmOne
{
public:
void doSomethingWithData(Collection& data)
{
// Use Collection iterators to build up an algorithm.
}
};
template<class Collection>
class AlgorithmTwo
{
public:
void doSomethingWithData(Collection& data)
{
// Use Collection iterators to build up an algorithm.
}
};
template<class Collection>
class AlgorithmThree
{
public:
void doSomethingWithData(Collection& data)
{
// Use Collection iterators to build up an algorithm.
}
};template<class Element>
class CollectionOne
{
public:
typedef Element ElementType;
};
template<class Element>
class CollectionTwo
{
public:
typedef Element ElementType;
};
template<class Element>
class CollectionThree
{
public:
typedef Element ElementType;
};
template<typename HostTraits>
class HostInheritsData
:
public HostTraits::Collection,
public HostTraits::Algorithm
{
public:
typedef HostTraits Traits;
using Traits::Algorithm::doSomethingWithData;
void doSomethingWithData()
{
doSomethingWithData(*this);
}
};
template<typename HostTraits>
class HostCompositsData
:
public HostTraits::Algorithm
{
typename HostTraits::Collection data_;
public:
typedef HostTraits Traits;
using Traits::Algorithm::doSomethingWithData;
void doSomethingWithData()
{
doSomethingWithData(data_);
}
// Clumsy and breaking encapsulation
typename HostTraits::Collection& data()
{
return data_;
}
};
template<typename HostTraits>
class GenericStrategy
{
typename HostTraits::Collection data_;
typename HostTraits::Algorithm algorithm_;
public:
void doSomethingWithData()
{
algorithm_.doSomethingWithData(data_);
}
};
class ElementOne {};
class ElementTwo {};
class ElementThree {};
struct MyConfig
{
typedef ElementOne Element;
typedef CollectionThree<Element> Collection;
typedef AlgorithmOne<Collection> Algorithm;
};
int main(int argc, const char *argv[])
{
HostInheritsData<MyConfig> hostInherits;
hostInherits.doSomethingWithData();
// This must be a mistake, are policies meant to be used this way?
hostInherits.doSomethingWithData(hostInherits);
HostCompositsData<MyConfig> hostComposits;
hostComposits.doSomethingWithData();
// Clumsy to use, not intuitive and breaking encapsulation.
hostComposits.doSomethingWithData(hostComposits.data());
// Combinatorics are there, I can combine whatever I want in MyConfig as for
// policies, but I can also have global Algorithm and Collection objects
// (no protected destructors).
GenericStrategy<MyConfig> strategy;
strategy.doSomethingWithData();
return 0;
}
Вот мои вопросы:
Я настраиваю структуру класса Host с помощью политик. Как я могу полностью обогатить интерфейс класса Host, когда каждый реалистичный Алгоритм требует для работы Collection, а Collection инкапсулируется в хост?
Когда я сравниваю основанный на политике дизайн вместе с универсальной фабрикой, разве универсальная фабрика не приносит мне ту же комбинаторную сложность? Кажется, что использование Generic Factory лучше, так как я могу поменять Element, Container и Algorithm во всех возможных комбинациях, и у меня все еще могут быть открытые деструкторы для всех политик, что позволяет мне комбинировать их любым способом, например, как я хочу. глобальная комбинация Элемент, Коллекция и Алгоритм.
Мне кажется, что Расширенные Политики становятся проблемой, как только структура настроена. Даже если позже я добавлю функцию-член к алгоритму, он, вероятно, будет иметь параметры, относящиеся к Collection (например, итераторы Collection): если Хост инкапсулирует Collection с использованием композиции, мне нужно запросить его для параметра его собственной функции-члена:
// Clumsy to use, not intuitive and breaking encapsulation.
hostComposits.doSomethingWithData(hostComposits.data());
и если Хост инкапсулирует Коллекцию с использованием наследования, он становится (по крайней мере для меня) еще более странным:
// This must be a mistake, are policies meant to be used this way?
hostInherits.doSomethingWithData(hostInherits);
Полностью ли я неправильно понял дизайн, основанный на политике (еще раз), правильно ли я использую черты?
Является ли шаблон общей стратегии лучшим выбором в этом случае?
Вы можете подумать о количестве связь в вашем дизайне. Например. убедитесь, что вы действительно хотите, чтобы ваши алгоритмы Collection
в качестве параметров шаблона. Это вводит связь между алгоритмом и контейнером, с которым он работает. Взгляните на Стандартную библиотеку: ее алгоритмы шаблоны функций принимая итераторы в качестве параметров шаблона. Итераторы ничего не знают о контейнере (коллекция в вашем словаре), на который они указывают.
Для выполнения других задач, кроме итерации и доступа, алгоритмы принимают в качестве параметров несколько более обогащенные типы, например back_inserters
для доступа к push_back()
член контейнера. Но в целом — без предшествующих знаний — абсолютно нет необходимости передавать весь интерфейс контейнера всем алгоритмам. Чтобы сделать действительно конкретные вещи контейнера, встраивая алгоритм как функция-член контейнера (например, sort()
функция-член std::list
) более уместно.
Для выполнения немного разных вещей есть несколько перегрузок (например, std::transform
) с тем же именем функции. Только если ваш алгоритм должен поддерживать состояние действительно ли необходимо сделать его шаблоном класса, и предпочтительнее сделать его функциональный объект, т.е. содержащий перегруженный оператор () вместо DoSomethingWithData()
функция-член.
Ваши данные параметризуются так же, как и стандартная библиотека: шаблон класса с Element
в качестве параметра шаблона. Способ подачи таких данных в алгоритмы заключается в предоставлении доступ к итератору к вашим данным в виде begin()
а также end()
функции-члены. Стандартные контейнеры (vector
, map
, unorderd_map
и т. д.) также использовать политики в качестве параметров шаблона, например, Allocator
, Compare
или же Hash
класс, с помощью которого вы можете настроить поведение ваших данных. Стандартные умные указатели Deleter
параметр политики для настройки их поведения.
Короче: внимательно проверьте свой дизайн. Что вы хотите сделать? Что каждый компонент (алгоритм, структура данных) должен знать о другом? И можете ли вы добраться туда при правильном использовании Стандартной библиотеки? Можно с уверенностью сказать, что большинство вещей, которые вы, возможно, захотите, уже закодированы, и вы можете сосредоточиться на написании логики вашего приложения, а не на алгоритмических или структурных деталях данных.
Других решений пока нет …