【问题标题】:Class design complication (C++)类设计复杂性 (C++)
【发布时间】:2015-04-16 09:20:29
【问题描述】:

我的课是

  • Base
    • Derived_A
    • Derived_B
  • Parent
    • Child_One
    • Child_Two

Base 有两个签名函数:

virtual void foo( const Parent& ) = 0;
virtual void bar( const Base& ) = 0;

,程序的其他部分所期望的。

问题是:

Derived_A 对待 Child_OneChild_Two 相同。但是Derived_B 对待它们的方式不同。

我应该如何实现这个?

一种方法是找出传递给Derived_B.foo 的对象类型。这显然是“设计缺陷”。 我尝试的另一种方法是将签名函数更改为:

class Base
{
  class Derived_A;
  class Derived_B;

//  virtual void bar( const Base& ) = 0;
  virtual void bar( const Derived_A& ) = 0;
  virtual void bar( const Derived_B& ) = 0;
}

class Derived_A: public virtual Base
{ 

  virtual void foo( const Parent& ) = 0;
}

class Derived_B: public virtual Base
{ 
  virtual void foo( const Child_A& ) = 0;
  virtual void foo( const Child_B& ) = 0;
}

但是现在bar 函数不能使用Base.foo。所以我要写两次bar函数,虽然代码完全一样。

还有其他方法可以解决这个问题吗?你推荐哪一个?

附:我想不出一个好的标题。请随意修改。

【问题讨论】:

  • 您不能将Parent 的子代传递给foo(Parent)。您只能传递 Parent 实例。见stackoverflow.com/questions/274626/what-is-object-slicing你需要多态的引用或指针。
  • 查看访问者设计模式。
  • 我实际上在我的程序中使用了引用。我试图简化它,但做得过火了。

标签: c++ class


【解决方案1】:

您描述的问题称为Double Dispatch。该链接描述了问题和一些可能的解决方案(包括多态函数签名和访问者模式)。

【讨论】:

    【解决方案2】:

    如果没有详细说明这两种类型层次结构之间的关系以及它们之间的交互方式,就不可能说哪种方法是合适的。我已经对其他答案和另一个可行的替代方案进行了概述,该替代方案可以扩展到评论中提到的访问者模式。

    在实现virtual 中的virtual 函数的孩子中执行多态行为,正如乔伊安德烈斯已经建议的那样,一般来说,这是一个非常典型的面向对象的解决方案。是否合适,取决于对象的职责。

    Olayinka 建议并在您的问题中已经提到的类型检测肯定闻起来很笨拙,但根据细节,可能是 N 邪恶中的最小值。它可以通过返回 enum 的成员函数(我猜这就是 Olayinka 的答案试图表示的)或一系列 dynamic_casts 来实现,如question you linked 中的一个答案所示。

    一个简单的解决方案可能是在Base 中重载foo

    struct Base {
        virtual void foo(const Parent&) = 0;
        virtual void foo(const Child_Two&) = 0;
    };
    struct Derived_A: Base { 
        void foo(const Parent& p) {
            // treat same
        }
        void foo(const Child_Two& p) {
            foo(static_cast<Parent&>(p));
        }
    };
    struct Derived_A: Base { 
        void foo(const Parent& p) {
            // treat Child_One (and other)
        }
        void foo(const Child_Two& p) {
            // treat Child_Two
        }
    };
    

    如果Base的其他子类型对Child_OneChild_Two处理相同,那么foo(const Child_Two&amp;)的实现可以放在Base中以避免重复。

    这种方法的问题是foo 必须使用正确的静态类型的引用来调用。调用不会根据动态类型解析。这对您的设计可能更好或更糟。如果您需要多态行为,您可以使用访问者模式,该模式本质上是在上述解决方案之上添加虚拟调度:

    struct Base {
        foo(Parent& p) {
            p.accept(*this);
        }
        virtual void visit(Child_A&) = 0;
        virtual void visit(Child_B&) = 0;
    };
    
    struct Parent {
        virtual void accept(Base&) = 0;
    };
    
    struct Child_A: Parent {
        void accept(Base& v) {
            v.visit(*this);
        }
    };
    // Child_B similarly
    
    struct Derived_A: Base { 
        void treat_same(Parent&) {
            // ...
        }
        void visit(Child_A& a) {
            treat_same(a);
        }
        void visit(Child_B& b) {
            treat_same(b);
        }
    };
    struct Derived_B: Base { 
        void visit(Child_A&) {
            // ...
        }
        void visit(Child_B&) {
            // ...
        }
    };
    

    还有一些样板,但由于您似乎非常不愿意在孩子身上实施这种行为,所以这对您来说可能是个好方法。

    【讨论】:

    • Derived_ADerived_BBase 的子类。您需要编辑您的答案。这没有意义。
    • @Furihr 我添加了一个似乎适合您的访问者模式示例。
    • 我正在实现访问者模式。但它似乎是 解决方案。
    【解决方案3】:

    您可以轻松地在 Parent 中创建 virtual foo 方法。既然您希望 Derive_A 对所有 Parent 的子类一视同仁,为什么不在 Parent 中实现一个类。这是最合乎逻辑的事情,因为很有可能,如果你想对他们两个都做同样的事情,那么他们两个必须有相似的数据,这些数据都存在于 Parent 中。

    class Parent{
       virtual void treatSame(){
           // Some operations that treat both Child_A, and Child_B
           // the same thing to both Child_A and Child_B.
       }
       virtual void foo() = 0;
    }
    

    由于您希望 Derived_B 在 Child_A 和 Child_B 中执行不同的操作,因此请利用多态性。考虑下面的其余类:

    class Child_A : public Parent{
        virtual void foo(){
            // Foo that is designed for special Child_A.
        }
    }
    
    class Child_B : public Parent{
        virtual void foo(){
            // Foo that is designed for special Child_B.
        }
    }
    
    
    class Base{
         virtual void foo(Parent) = 0;
         virtual void bar(Base) = 0;
    }
    
    class Derived_A: public Base
    { 
      virtual void foo( Parent& p){
         p.treatSame();
      }
    }
    
    class Derived_B: public Base
    { 
      virtual void foo( Parent& p){
          p.foo();  // Calls appropriate function, thanks to polymorphism.
      }
    }
    

    可能的用法如下:

    int main(){
        Child_A a;
        Child_B b;
    
        Derived_A da;
        da.foo(a);  // Calls a.treatSame();
        da.foo(b);  // Calls a.treatSame();
    
        Derived_B db;
        db.foo(a);  // Calls a.foo();
        db.foo(b);  // Calls b.foo();
    }
    

    请注意,这仅在参数是指针或引用时才有效(我更喜欢尽可能处理引用)。否则虚拟调度(选择适当的功能)将无法正常工作。

    【讨论】:

    • Parent 中的 virtual 函数正是我会做的。 treatSame 可以被删除,它的实现只是放在 Derived_A::foo 中,但很难说在这样一个抽象的例子中哪个更有意义。
    • 您不能将 foo 放入父级 Base 调用 foo 并在父级上运行。 Base 在 foo 内部对 Parent 执行的操作可能需要访问 Parent.foo 无法访问的东西。
    • @user2079303 但是在 Derived_A::foo 中直接处理 Parent 的方法是一种代码味道,特别是耦合太紧(过于依赖其他类)。特定于 Parent 的方法必须保留在 Parent 中,否则,您必须重新考虑您的设计。
    • @JoeyAndres,我知道你在说什么,但是当主要操作在 Derived_B 而不是在 Child_B 中完成时,调用 Child_B::foo() 无济于事。 Derived_BChild_B 没有的信息。同样,在您的示例中,Child_AChild_B 的行为(未处理)不同。
    • 为了更清楚。在你的实现中,我打电话给 Child_A,她说“你好”;我打电话给 Child_B,她说“你好吗?!”。他们的行为不同。但我想带 Child_A 上学,带 Child_B 上幼儿园。他们被区别对待。
    【解决方案4】:

    我不确定语法,但你明白了要点。

    class Base{
      virtual void bar( Base ) = 0;
      virtual void foo( Parent ) = 0;
    }
    
    class Derived_A: public virtual Base{ 
      virtual void foo( Parent ) = 0;
    }
    
    class Derived_B: public virtual Base{ 
      virtual void foo( Parent ){
          //switch case also works
          return parent.get_type() == Parent::TYPE_A ? foo_A((Child_A)parent) : foo_B((Child_B)parent);
      }
      virtual void foo_A( Child_A ) = 0;
      virtual void foo_B( Child_B ) = 0;
    }
    
    class Parent{
      virtual int get_type() = 0;
    }
    
    class Child_A: public virtual Parent{ 
         return Parent::TYPE_A;
    }
    
    class Child_B: public virtual Parent{ 
         return Parent::TYPE_B;
    }
    

    【讨论】:

    • 这是第一种方法。我可以使用它,但显然这是一个设计缺陷。
    • @Furihr 这是一个设计缺陷吗?
    • 我找不到我现在在哪里(在 stackoverflow 上)阅读它。但是,它说类似“如果您的程序需要找出对象的类型并采取相应的行动,这是一个设计缺陷。”因为它是具有 28.6k 或 26.8(?) 声誉的人。我以为她/他知道他/她在说什么。
    • @Furihr 我完全同意你的观点,但在这种情况下,我认为设计存在缺陷只是因为问题存在。根据您在另一个答案中的评论。在决定带谁去学校或幼儿园之前,您必须知道他们分别是谁。另一种解决方案是向 Parent 添加虚拟方法,以便在 Derived_B 调用其 foo 时专门调用这些方法。但这也是有缺陷的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-24
    • 2012-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    相关资源
    最近更新 更多