在抽象层次结构的具体实例上执行契约

发布于 2024-12-04 07:57:05 字数 2292 浏览 5 评论 0原文

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

首先要做的事情是:假设您有一些非常整洁的接口:

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

并且这些具体是完全不同的东西。就像一个航天飞机零件(有一个零件工厂和一条航天飞机装配线)和一袋袋蔬菜(有一个农场和..不知道,谁会吃这些蔬菜?)。然而他们一般都有那个东西:DoThings()。假装它是 PackAndSend()、Serialize() 或 Dispose(),无论您喜欢什么。没有什么具体但合法的层次结构基础。 但这些差异仍然多于共性。因此,那些具体消费者倾向于以不同的方式使用它们。如此不同,事实上,他们绝对必须确定它是假定的具体类型。

所以问题是:我现在强制该层次结构的用户在其虚拟覆盖中将 IPoduct 向下转换为 ConcreteProduct。这让我很烦恼。我觉得我错过了一些东西:层次结构中的一个大缺陷,一些缺乏模式知识的东西。 我的意思是,我可以确定,ConcreteConsumerB 总是收到 ConcreteProductB,但它仍然是一个沮丧。你会使用一个框架,它总是绕过 (void*) 并迫使你将它投射到你认为会出现的时候吗?

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

  1. 将所有具体接口隧道连接到 IProduct 中。但该产品会变成无法控制的斑点,谁可以 Eat()、BeEaten()、Launch()、Destroy() 以及谁知道还有什么。所以这个解决方案对我来说似乎没有什么比沮丧更好的了。
  2. DoThings() 可能可以从 IProduct 解耦到另一个处理程序中,该处理程序将能够接受所有具体内容(类似于访问者)。这样 IProduct 就可以被删除,并且会有单独的具体组。但是,如果有一个 SemiConcrete 层,它为这些具体组实现了一些通用功能,该怎么办?比如贴标签、变形、按摩等等。另外,当需要添加另一个具体组时,我将被迫更改该访问者,这会增加耦合。
  3. (ab)使用模板。目前看来这很明智。类似的东西

    模板<类型名称 _IProduct >
    IConcreteProducer 类:公共 IProducer
    {
    民众:
        虚拟 _IProduct* Produce( _IProduct::Type 类型 ) = 0;
        virtual _IProduct::Type DuceType( ProductType 类型 ) = 0;
        虚拟 IProduct* Produce( ProductType 类型 )
        {
            return IConcreteProducer::Produce( DeduceType( type ) );
        }
    }
    
    模板<类型名称 _IProduct >
    IConcreteConsumer 类:公共 IConsumer
    {
    民众:
        虚拟无效消耗(_IProduct *产品)= 0;
        virtual void Consume( IProduct* 产品 )
        {
            IConcreteConsumer<类型名称 _IProduct>::Consume( (_IProduct*)product );
        }
    }
    

    这样我就可以控制那种沮丧,但它仍然存在。

无论如何,这个问题听起来很熟悉吗?有人看到它被解决了,或者也许他自己英勇地解决了它? C++ 解决方案非常棒,但我认为任何静态类型语言就足够了。

I'm lost and in need of some divine guidance.

First things first: assume you have some nicely-neat interfaces:

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


enum ProductType
{
   ...
}


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


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

Its plain simple yet: abstract factory, abstract consumer who will invoke interface, gladly provided by those freshly-spawned IProducts.
But here comes the tricky part.
Assume that there are two ( or more ) parallel concrete groups:

class ConcreteProducerA : public IProducer { ... }
class ConcreteConsumerA : public IConsumer { ... }
class ConcreteProductA : public IProduct { ... }

class ConcreteProducerB : public IProducer { ... }
class ConcreteConsumerB : public IConsumer { ... }
class ConcreteProductB : public IProduct { ... }

And those concretes are reeealy different things. Like a space-shuttle parts ( with a parts factory and a shuttle assembly line ) and bags of vegetables ( with a farm and .. idk, who whould consume those vegetables? ). Yet they have that thing in general: DoThings(). Pretend that it is, like, PackAndSend(), or Serialize(), or Dispose(), whatever you like. Nothing concrete, yet legit to base a hierarchy on.
But those still have more differences, than generalities. So those ConcreteConsumers tend to use them differently. So differently, that, in fact, they absolutely MUST be sure, that it is supposed concrete type.

So here is the problem: I'm forcing the users of that hierarchy to downcast IPoduct to ConcreteProduct in their virtual overrides right now. And thats bugging me hard. I feel I'm missing something: a big flaw in hierarchy, some lack of pattern knowledge, something.
I mean, I can make sure, that ConcreteConsumerB always recieves ConcreteProductB, but it's still a downcast. And would you ever use a framework, that always passes around (void*)'s and forces you to cast it to whenewer you think is gonna come at ya?

Solutions I've already considered:

  1. Tunnel all conctrete interfeces into IProduct. But that product gona turn into uncontrollable blob, who can Eat(), BeEaten(), Launch(), Destroy() and whoever knows what else. So this solution seems nothing better than downcasting to me.
  2. That DoThings() can probably be decoupled from IProduct into another handler, which will be able to accept all of the concretes (Visitor-like). That way IProduct can be removed and there will be separate concrete groups. But what if there is a SemiConcrete layer, which imlements some common functionality for those concrete groups? Like labeling, morphing, massaging, whatever. Plus when there will be need to add another concrete group I'll be forced to change that visitor(s), which kinda increases coupling.
  3. (ab)Use templates. That seems wise at the moment. Something along the lines of

    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 );
        }
    }
    

    This way I'm in control of that downcast, but it is stil present.

Anyways, does this problem sound familiar to someone? Somebody seen it solved, or maybe heroicaly solved it himself? C++ solution would be awesome, but I think any staticaly-typed language will suffice.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

秋意浓 2024-12-11 07:57:05

但是他们一般都有那个东西: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;
};

Yet they have that thing in general: DoThings(). Pretend that it is,
like, PackAndSend(), or Serialize(), or Dispose(), whatever you like.
Nothing concrete, yet legit to base a hierarchy on.

Just because they can be in some hierarchy, doesn't mean they should. They are unrelated. I can't even fathom what value you are adding to whatever code base by generalizing shuttles and vegetables. If it doesn't add benefit to the users, then you are likely just making things more convoluted on yourself.

I would expect to see interfaces like the below. Notice they don't inherit from anything. If you have shared code, write simpler dumb concrete classes that people can reuse by composition.

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;
  };

Then I'd expect to see concrete functions to create these from various sources.

typedef Producer<Shuttle> ShuttleProducer;
typedef Consumer<Shuttle> ShuttleConsumer;

std::auto_ptr<ShuttleProducer> GetShuttleProducerFromFile(...);
std::auto_ptr<ShuttleProducer> GetShuttleProducerFromTheWeb(...);
std::auto_ptr<ShuttleProducer> GetDefaultShuttleProducer();

There probably isn't a pattern for what you want to do, it is likely two patterns that you are smooshing (technical term) together. You didn't betray why these things should be sharing a code base, so we can only guess.

In the more complicated scenarios, you'll want to strictly separate use from creation though. It is perfectly valid to have different interfaces that look sort of similar, but are used differently.

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;
};
此刻的回忆 2024-12-11 07:57:05

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

但无论如何,你的设计可能都需要重新思考。

One possibility is to introduce a second layer to hot hierarchy. Derive IShuttle from IProduct, and derive that group from it. Then add an IShuttleProducer that yields an IShuttle* instead of IProduct*. This is okay, because C++ allows covariant return types for virtual functions... so long as the the new return type derives from the original, it is still considered an override.

But your design probably needs some rethinking either way.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文