Обеспечение исполнения договора по конкретным случаям абстрактной иерархии - PullRequest
1 голос
/ 12 сентября 2011

Я потерян и нуждаюсь в каком-то божественном руководстве.

Перво-наперво: предположим, у вас есть несколько аккуратных интерфейсов:

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 *) и заставляет вас использовать его, когда вы думаете, что он придет на вас?

Решения, которые я уже рассмотрел:

  1. Туннелирование всех бетонов вмешивается в IProduct. Но этот продукт превратится в неуправляемый блоб, который может Eat (), BeEaten (), Launch (), Destroy () и кто знает, что еще. Так что это решение кажется мне ничем не лучше, чем принижение.
  2. То, что DoThings (), вероятно, можно отделить от IProduct в другой обработчик, который сможет принимать все бетоны (подобные посетителю). Таким образом, IProduct может быть удален, и будут отдельные конкретные группы. Но что, если существует слой SemiConcrete, который реализует некоторые общие функции для этих конкретных групп? Как маркировка, морфинг, массаж, что угодно. Кроме того, когда понадобится добавить еще одну конкретную группу, я буду вынужден изменить этого посетителя (ей), что, в свою очередь, увеличивает связь.
  3. (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 ]

2 голосов
/ 13 сентября 2011

Тем не менее, у них есть эта вещь в целом: 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;
};
2 голосов
/ 12 сентября 2011

Одна из возможностей - ввести второй слой в горячую иерархию. Выведите IShuttle из IProduct и извлеките эту группу из него. Затем добавьте IShuttleProducer, который дает IShuttle* вместо IProduct*. Это нормально, потому что C ++ допускает ковариантные возвращаемые типы для виртуальных функций ... до тех пор, пока новый возвращаемый тип наследуется от оригинала, он все еще считается переопределением.

Но ваш дизайн, вероятно, нуждается в переосмыслении в любом случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...