Обеспечение исполнения договора по конкретным случаям абстрактной иерархии
Я потерян и нуждаюсь в каком-то божественном руководстве.
Перво-наперво: предположим, у вас есть несколько аккуратных интерфейсов:
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. Но этот продукт может превратиться в неуправляемый BLOB-объект, который может 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++ было бы потрясающим, но я думаю, что достаточно любого статически типизированного языка.
2 ответа
Тем не менее, у них есть эта вещь в целом: DoThings(). Притворись, что это, например, PackAndSend(), или Serialize(), или Dispose(), как хочешь. Ничего конкретного, но законного основания иерархии.
То, что они могут быть в какой-то иерархии, не означает, что они должны. Они не связаны. Я даже не могу понять, какую ценность вы добавляете к какой-либо кодовой базе, обобщая челноки и овощи. Если это не приносит пользы пользователям, то, скорее всего, вы просто делаете вещи более замысловатыми для себя.
Я ожидаю увидеть интерфейсы, подобные приведенным ниже. Обратите внимание, что они ни от чего не наследуют. Если у вас есть общий код, напишите более простые тупые конкретные классы, которые люди могут использовать по составу.
template<typename T>
class Producer {
public:
virtual ~Producer() {}
virtual std::auto_ptr<T> produce() = 0;
};
template<typename T>
class Consumer {
public:
virtual ~Consumer() {}
virtual void consume(std::auto_ptr<T> val) = 0;
};
Тогда я ожидал увидеть конкретные функции для их создания из разных источников.
typedef Producer<Shuttle> ShuttleProducer;
typedef Consumer<Shuttle> ShuttleConsumer;
std::auto_ptr<ShuttleProducer> GetShuttleProducerFromFile(...);
std::auto_ptr<ShuttleProducer> GetShuttleProducerFromTheWeb(...);
std::auto_ptr<ShuttleProducer> GetDefaultShuttleProducer();
Вероятно, не существует шаблона для того, что вы хотите сделать, скорее всего, это два шаблона, которые вы используете вместе (технический термин). Вы не предали, почему эти вещи должны делиться кодовой базой, поэтому мы можем только догадываться.
В более сложных сценариях вы должны строго отделить использование от создания. Вполне допустимо иметь разные интерфейсы, которые выглядят примерно одинаково, но используются по-разному.
class Foo {
public:
virtual ~Foo() {}
virtual void doStuff() = 0;
virtual void metamorphose() = 0;
};
class Fu {
public:
virtual ~Fu() {}
virtual void doStuff() = 0;
virtual void transmorgrify() = 0;
};
Одна возможность состоит в том, чтобы ввести второй слой в горячую иерархию. получать IShuttle
от IProduct
и вывести эту группу из нее. Затем добавьте IShuttleProducer
что дает IShuttle*
вместо IProduct*
, Это нормально, потому что C++ допускает ковариантные возвращаемые типы для виртуальных функций... до тех пор, пока новый возвращаемый тип наследуется от оригинала, он все еще считается переопределением.
Но ваш дизайн, вероятно, нуждается в переосмыслении в любом случае.