Я потерян и нуждаюсь в каком-то божественном руководстве.
Перво-наперво: предположим, у вас есть несколько аккуратных интерфейсов:
class IProduct
{
public:
virtual void DoThings();
}
enum ProductType
{
...
}
class IProducer
{
public:
virtual IProduct* Produce( ProductType type );
}
class IConsumer
{
public:
virtual void Consume( IProduct* product );
}
Все довольно просто: абстрактная фабрика, абстрактный потребитель, который будет вызывать интерфейс, с радостью предоставленный этими недавно порожденными продуктами IP.
Но здесь начинается сложная часть.
Предположим, что есть две (или более) параллельные конкретные группы:
class ConcreteProducerA : public IProducer { ... }
class ConcreteConsumerA : public IConsumer { ... }
class ConcreteProductA : public IProduct { ... }
class ConcreteProducerB : public IProducer { ... }
class ConcreteConsumerB : public IConsumer { ... }
class ConcreteProductB : public IProduct { ... }
И эти бетоны действительно разные вещи. Как части космического челнока (с фабрикой деталей и сборочной линией челнока) и мешки с овощами (с фермой и т. Д., Кто будет есть эти овощи?). Тем не менее, у них есть эта вещь в целом: DoThings (). Притворись, что это, например, PackAndSend (), или Serialize (), или Dispose (), как хочешь. Ничего конкретного, но законного основания иерархии.
Но они все же имеют больше различий, чем общности. Таким образом, эти Конкретные потребители склонны использовать их по-разному. Настолько по-разному, что, на самом деле, они абсолютно ДОЛЖНЫ быть уверены, что это предполагаемый конкретный тип.
Так вот в чем проблема: я заставляю пользователей этой иерархии понижать IPoduct до ConcreteProduct в их виртуальных переопределениях прямо сейчас. И это сильно беспокоит меня. Я чувствую, что что-то упускаю: большой недостаток в иерархии, отсутствие знаний о шаблонах, что-то.
Я имею в виду, я могу убедиться, что ConcreteConsumerB всегда получает ConcreteProductB, но это все-таки удручает. И вы когда-нибудь использовали бы фреймворк, который всегда обходит (void *) и заставляет вас использовать его, когда вы думаете, что он придет на вас?
Решения, которые я уже рассмотрел:
- Туннелирование всех бетонов вмешивается в IProduct. Но этот продукт превратится в неуправляемый блоб, который может Eat (), BeEaten (), Launch (), Destroy () и кто знает, что еще. Так что это решение кажется мне ничем не лучше, чем принижение.
- То, что DoThings (), вероятно, можно отделить от IProduct в другой обработчик, который сможет принимать все бетоны (подобные посетителю). Таким образом, IProduct может быть удален, и будут отдельные конкретные группы. Но что, если существует слой SemiConcrete, который реализует некоторые общие функции для этих конкретных групп? Как маркировка, морфинг, массаж, что угодно. Кроме того, когда понадобится добавить еще одну конкретную группу, я буду вынужден изменить этого посетителя (ей), что, в свою очередь, увеличивает связь.
(ab) Использовать шаблоны. Это кажется мудрым на данный момент. Что-то вроде
template < typename _IProduct >
class IConcreteProducer : public IProducer
{
public:
virtual _IProduct* Produce( _IProduct::Type type ) = 0;
virtual _IProduct::Type DeduceType( ProductType type ) = 0;
virtual IProduct* Produce( ProductType type )
{
return IConcreteProducer<typename _IProduct>::Produce( DeduceType( type ) );
}
}
template < typename _IProduct >
class IConcreteConsumer : public IConsumer
{
public:
virtual void Consume( _IProduct* product ) = 0;
virtual void Consume( IProduct* product )
{
IConcreteConsumer<typename _IProduct>::Consume( (_IProduct*)product );
}
}
Таким образом, я контролирую это удручение, но оно все еще присутствует.
В любом случае, эта проблема звучит кому-то знакомо? Кто-то видел, как это решено, а может, героически решило это сам? Решение C ++ было бы потрясающим, но я думаю, что достаточно любого статически типизированного языка.