1

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

首先要做的事情:假设您有一些非常整洁的界面:

class IProduct
{
public:
    virtual void DoThings();
}


enum ProductType
{
   ...
}


class IProducer
{
public:
    virtual IProduct* Produce( ProductType type );
}


class IConsumer
{
public:
    virtual void Consume( IProduct* product );
}

它很简单:抽象工厂,将调用接口的抽象消费者,这些新生成的 IProducts 很乐意提供。但棘手的部分来了。假设有两个(或更多)平行的具体组:

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*)'s 并迫使你将它投射到你认为会来的时候吗?

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

  1. 将所有具体的接口都隧道化到 IProduct 中。但是那个产品会变成无法控制的 blob,谁可以吃()、吃()、启动()、销毁(),谁知道还有什么。所以这个解决方案对我来说似乎没有什么比沮丧更好的了。
  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++ 解决方案会很棒,但我认为任何静态类型的语言都足够了。

4

2 回答 2

2

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

但无论哪种方式,您的设计都可能需要重新思考。

于 2011-09-12T17:42:41.313 回答
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;
};
于 2011-09-12T20:43:14.463 回答