面向对象:如何从多个实现中进行选择

时间:2014-03-09 16:42:14

标签: c++ oop casting polymorphism

我是一个不错的程序程序员,但我是面向对象的新手(我曾经在好老Pascal和C上接受过工程师培训)。我觉得特别棘手的是选择其中一种方法来实现同样的目的。对于C ++来说尤其如此,因为它的功能可以让你做任何你喜欢的事情,甚至可怕的事情(我想这里的权力/责任格言是合适的)。

我认为这可能有助于我运行一个我正在与社区斗争的特定案例,以了解人们如何做出这些选择。我正在寻找的是与我的具体案例相关的建议,以及更一般的指针(没有双关语意)。这是:

作为练习,我正在开发一个简单的模拟器,其中“几何表示”可以有两种类型:“圆”或“多边形”。然后,模拟器的其他部分需要接受这些表示,并可能以不同方式处理它们。我已经提出了至少四种不同的方法来做到这一点。每个的优点/缺点/权衡取舍是什么?

A:功能重载

CirclePolygon声明为不相关的类,然后重载需要几何表示的每个外部方法。

B:施法

宣布enum GeometricRepresentationType {Circle, Polygon}。声明一个抽象的GeometricRepresentation类,并从中继承CirclePolygonGeometricRepresentation具有由GetType()Circle实施的虚拟Polygon方法。然后,方法使用GetType()和switch语句将GeometricRepresentation强制转换为适当的类型。

C:不确定名称

声明枚举类型和抽象类,如 B 。在此课程中,还会创建函数Circle* ToCircle() {return NULL;}Polygon* ToPolygon() {return NULL;}。然后,每个派生类重载相应的函数,返回this。这仅仅是动态铸造的重新发明吗?

D:将他们聚集在一起

将它们实现为具有枚举成员的单个类,该成员指示对象的类型。该类具有可以存储两种表示的成员。然后由外部方法决定不调用愚蠢的函数(例如,多边形上的GetRadius()或圆上的GetOrder()

4 个答案:

答案 0 :(得分:1)

很可能您会在PolygonCircle之间使用常用方法。我将它们组合在一个名为Shape的接口下,例如(在java中编写,因为它在语法方面更新鲜。但是如果我编写c ++示例,那就是我会使用的。这只是一段时间以来我写了c ++):

public interface Shape {
   public double getArea();

   public double getCentroid();

   public double getPerimiter(); 
}

PolygonCircle都实现此接口:

public class Circle implements Shape {
   // Implement the methods
}

public class Polygon implements Shape {
   // Implement the methods
}

你得到了什么:

  1. 您始终可以将Shape视为具有特定属性的generelized对象。您将来可以添加不同的Shape实现,而无需更改使用Shape执行某些操作的代码(除非您具有针对新Shape的特定内容)

  2. 如果你有完全相同的方法,你可以用抽象类替换接口并实现它们(在C ++接口中只是一个没有实现的抽象类)

  3. 最重要的是(我正在强化子弹#1) - 你将享受多态的力量。如果你使用枚举声明你的类型,如果你想添加新的形状,你有一天必须改变代码中的很多地方。然而,您不必为实现形状的新类别更改任何内容。

答案 1 :(得分:1)

阅读基础知识的C ++教程,阅读Stroustrup的“The C ++编程语言”,了解如何习惯使用该语言。

不要相信人们告诉你,你必须独立于语言学习OOP。肮脏的秘密是,每种语言都理解为OOP在某些情况下甚至不是模糊地相似,因此具有坚实的基础,例如Java,对C ++来说并不是一个很大的帮助;到目前为止,语言go根本就没有类。此外,C ++显然是一种多范式语言,包括程序,面向对象和通用编程。您需要学习如何有效地组合它。它被设计用于最大性能,这意味着一些较低位的东西显示出来,在程序员手中留下许多与性能相关的决定,其他语言只是不提供选项。 C ++有一个非常广泛的泛型算法库,学习使用它们是课程的必要部分。

从小处开始,所以在几年的时间里,你可以在第一次尝试的天真地笑一笑,而不是把头发拉出来。

不要担心“效率”,在任何地方使用虚拟成员函数,除非有令人信服的理由不这样做。掌握参考资料和const。正确设计对象是非常难,不要指望第一次(或第五次)尝试成为最后一次。

答案 2 :(得分:1)

以下是我教我的OO学生的一些设计规则(

1)每当你想要创建一个枚举以跟踪对象/类中的某些模式时,你可以(可能更好)为每个枚举值创建一个派生类。

2)任何时候你写一个关于一个对象的if语句(或者它当前的状态/模式/无论什么),你可以(可能更好)进行虚函数调用来执行一些(更抽象的)操作,其中原始then-或else-sub-statement是派生对象的虚函数的主体。

例如,而不是这样做:

if (obj->type() == CIRCLE) {
    // do something circle-ish
    double circum = M_PI * 2 * obj->getRadius();
    cout << circum;
}
else if (obj->type() == POLY) {
    // do something polygon-ish
    double perim = 0;
    for (int i=0; i<obj->segments(); i++)
        perm += obj->getSegLength(i);
    cout << perim;
}

这样做:

cout << obj->getPerimeter();

...

double Circle::getPerimeter() {
    return M_PI * 2 * obj->getRadius();
}

double Poly::getPerimeter() {
    double perim = 0;
    for (int i=0; i<segments(); i++)
        perm += getSegLength(i);
    return perim;
}

在上面的案例中,非常明显的是“更抽象”的想法是什么,外围。情况并非总是如此。有时它甚至没有一个好名字,这是很难“看到”的原因之一。但是,您可以将任何if语句转换为虚函数调用,其中“if”部分被函数的虚拟替换。

在你的情况下,我绝对同意Avi的答案,你需要一个基类/接口类以及Circle和Polygon的派生子类。

答案 3 :(得分:0)

首先,关于OOP的一些背景知识以及C ++和Java等其他语言的区别。


人们倾向于使用面向对象的编程来实现几个目的:

  1. 通用编程:编写通用代码;即适用于提供指定接口的任何对象或数据,而无需关心实现细节。

  2. 模块化和封装:通过向用户隐藏不相关的实现细节,防止不同的代码片段彼此过于紧密耦合(称为“模块化”)。 这是考虑关注点分离的另一种方式

  3. 静态多态:为特定类对象定制某些行为的“默认”实现,同时保持代码模块化,其中可能的自定义集已经已知< / em>当你编写程序时。
    (注意:如果您不需要保持代码模块化,那么选择行为就像ifswitch一样简单,但原始代码需要考虑所有可能性。)

  4. 动态多态:类似于静态多态,但可能的自定义集是已知 - 可能是因为您希望库的用户实现特定行为以后,例如为你的程序制作一个插件。

  5. 在Java中,使用相同的工具(继承和覆盖)来解决这些问题的基本所有
    好处是,只有一种方法可以解决所有问题,因此更容易学习 缺点是有时但并不总是微不足道的效率损失:解决问题#4的解决方案比仅需要解决#3的解决方案更昂贵。

    现在,输入C ++。

    C ++有不同的工具来处理所有这些,即使他们对同一个问题使用相同的工具(例如继承),它们也会以不同的方式使用,它们实际上是完全不同的解决方案< / em>比你在Java中看到的经典“继承+覆盖”:

    1. 通用编程:为此制作了C ++ template。它们类似于Java的泛型,但实际上Java的泛型通常需要继承才有用,而C ++模板通常与继承无关。

    2. 模块化和封装:C ++类具有publicprivate访问修饰符,就像在Java中一样。在这方面,这两种语言非常相似。

    3. 静态多态:Java无法解决此特定的问题,而是强迫您使用#4的解决方案,支付给您的罚款不一定需要付钱。另一方面,C ++使用template class es和称为CRTP的继承的组合来解决此问题。 这种类型的继承与#4的继承非常不同。

    4. 动态多态:C ++和Java都允许继承和函数覆盖,并且在这方面类似。


    5. 现在,回到你的问题。我该如何解决这个问题呢? 从上面的讨论可以得出,遗传不是针对所有指甲的单锤。

      可能最好的方法(尽管可能是最复杂的方法)是使用#3来完成这项任务。

      如果需要,你可以在它上面为需要它的类实现#4,而不会影响其他类。

      您声明了一个名为Shape的类并定义了基本功能:

      class Graphics;  // Assume already declared
      
      template<class Derived = void>
      class Shape;   // Declare the shape class
      
      template<>
      class Shape<>  // Specialize Shape<void> as base functionality
      {
          Color _color;
      public:
          // Data and functionality for all shapes goes here
          // if it does NOT depend on the particular shape
          Color color() const { return this->_color; }
          void color(Color value) { this->_color = value; }
      };
      

      然后定义通用功能:

      template<class Derived>
      class Shape : public Shape<>   // Inherit base functionality
      {
      public:
          // You're not required to actually declare these,
          // but do it for the sake of documentation.
          // The subclasses are expected to define these.
          size_t vertices() const;
          Point vertex(size_t vertex_index) const;
      
          void draw_center(Graphics &g) const { g.draw_pixel(shape.center()); }
      
          void draw_outline()
          {
              Derived &me = static_cast<Derived &>(*this);  // My subclass type
              Point p1 = me.vertex(0);
              for (size_t i = 1; i < me.vertices(); ++i)
              {
                  Point p2 = me.vertex(1);
                  g.draw_line(p1, p2);
                  p1 = p2;
              }
          }
      
          Point center() const  // Uses the methods above from the subclass
          {
              Derived &me = static_cast<Derived &>(*this);  // My subclass type
              Point center = Point();
              for (size_t i = 0; i < me.vertices(); ++i)
              { center += (center * i + me.vertex(i)) / (i + 1); }
              return center;
          }
      };
      

      一旦这样做,您就可以定义新的形状:

      template<>
      class Square : public Shape<Square>
      {
          Point _top_left, _bottom_right;
      public:
          size_t vertices() const { return 4; }
      
          Point vertex(size_t vertex_index) const
          {
              switch (vertex_index)
              {
              case 0: return this->_top_left;
              case 1: return Point(this->_bottom_right.x, this->_top_left.y);
              case 2: return this->_bottom_right;
              case 3: return Point(this->_top_left.x, this->_bottom_right.y);
              default: throw std::out_of_range("invalid vertex");
              }
          }
      
          // No need to define center() -- it is already available!
      };
      

      这可能是最好的方法,因为您很可能在编译时已经知道所有可能的形状(即您不希望用户编写插件来定义自己的形状) ,因此不需要与virtual完全交易。然而,它保持代码模块化并分离不同形状的关注点,有效地为您提供与动态多态方法相同的好处。
      (它在运行时也是最有效的选项,代价是在编译时更复杂。)

      希望这有帮助。