类设计复杂性(C ++)

时间:2015-04-16 09:20:30

标签: c++ class

我的课程

  • 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函数,尽管代码完全相同。

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

P.S。我想不出一个好头衔。请随时修改它。

4 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

您可以在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();
}

请注意,这仅在参数为指针或引用时才有效(我希望在可能的情况下处理引用)。虚拟调度(选择适当的功能)将不起作用。

答案 2 :(得分:2)

没有详细说明两种类型的层次结构'关系是彼此之间以及它们如何相互作用,不可能说出适当的方法。我已经概述了其他答案和另一个可行的替代方案,可以扩展到评论中提到的访客模式。

如Joey Andres已经建议的那样,在virtual中实现Parent函数的子代中执行多态行为通常是针对此问题的典型面向对象解决方案。它是否合适取决于对象的责任。

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

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

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&)的实施可能会放在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&) {
        // ...
    }
};

还有更多的样板,但由于你似乎非常反对在孩子们中实施这种行为,这对你来说可能是个好方法。

答案 3 :(得分:1)

我不确定语法,但你得到了要点。

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