在抽象层次结构的具体实例上强制执行合同

时间:2011-09-12 17:15:39

标签: c++ templates design-patterns interface class-hierarchy

我迷失了,需要一些神圣的指导。

首先要做的事情:假设你有一些很好的接口:

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 { ... }

那些混凝土是不同的东西。就像航天飞机零件(零件工厂和梭子装配线)和蔬菜袋(带有农场和...... idk,谁会吃这些蔬菜?)。然而他们总体上有这样的事情:DoThings()。无论你喜欢什么,假装它是PackAndSend(),Serialize()或Dispose()。没有什么具体的,但基于层次结构的合法性。 但那些仍然比一般性更多的差异。所以那些ConcreteConsumers倾向于以不同的方式使用它们。事实上,他们绝对必须确定,这应该是具体的类型。

所以这就是问题所在:我正在强迫该层次结构的用户立即将IPoduct转发到他们的虚拟覆盖中的ConcreteProduct。这让我很烦恼。我觉得我错过了一些东西:层次结构中的一个大缺陷,一些缺乏模式知识,一些东西。 我的意思是,我可以确定,ConcreteConsumerB总是收到ConcreteProductB,但它仍然是一个垂头丧气。你会不会使用一个框架,它总是绕过(void *)并迫使你把它投射到你认为会来的时候?

我已经考虑过的解决方案:

  1. 将所有conctrete interfeces隧道传输到IProduct。但是那个产品变成了无法控制的blob,谁可以Eat(),BeEaten(),Launch(),Destroy()以及谁知道还有什么。所以这个解决方案似乎没有比转向我更好了。
  2. DoThings()可能会从IProduct解耦到另一个处理程序,它将能够接受所有的混凝土(类似访客)。这样就可以删除IP产品,并且会有单独的具体组。但是,如果有一个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 );
        }
    }
    

    通过这种方式,我可以控制那个沮丧的人,但现在已经存在了。

  4. 无论如何,这个问题对某人来说听起来很熟悉吗?有人看到它解决了,或者可能是英雄式的解决了吗? C ++解决方案很棒,但我认为任何静态类型的语言都足够了。

2 个答案:

答案 0 :(得分:2)

一种可能性是将第二层引入热层次结构。从IShuttle获取IProduct,并从中派生该组。然后添加IShuttleProducer,产生IShuttle*而不是IProduct*。这没关系,因为C ++允许虚函数的协变返回类型......只要新的返回类型派生自原始类型,它仍然被认为是覆盖。

但是你的设计可能需要重新思考。

答案 1 :(得分: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;
};