C ++绘图类和继承heirachy哲学

时间:2015-08-04 17:03:28

标签: c++ oop inheritance

简介

这是一个关于构造继承层次结构的逻辑推理或哲学的具体问题,在这个问题所面临的具体问题的背景下。

项目目标描述

我正在尝试编写一个C ++图形绘图库。目前,我正在尝试实施基本的2D图,包括:

  • 分散图
  • 错误栏图
  • 直方图

我这样做的原因是因为没有一个简单的C ++绘图库可以满足我的要求。*(参见我的另一个问题。(现已关闭):C++ Graph Plotting Package

*满足我认为绘图库应该满足的几个要点,包括跨平台,同时无限数量的绘图等。我不会列出所有的点,有太多。如果你评论一个可能的建议,我会告诉你为什么我选择不使用它,或者我还没有尝试过,在这种情况下,我会试一试。

基本上我对如何构建代码感到困惑。

使用继承的方法

我想到了两种可能的构造继承的方法。在我描述之前,这里有一些关于代码实现的基本信息。

  • 我有一个包含指向某些像素数据的指针的类。这个像素数据是"实际图形"将被绘制。
  • 目前 有一个带有纯虚方法Draw()的基类。所有可以"绘制的课程"继承自此类,强制它们覆盖Draw()方法。似乎足够合乎逻辑。例子包括"轴" class - 你可以画一个轴," x-y数据点" class - 您可以从成对的x-y点绘制散点图。

方法1

我原来的想法是包含像素数据的类应该包含一个指向"图形描述的指针" class,它本身包含所需的所有变量"绘制图像到像素"。这包括图表的轴,数据点以及标题和轴标签等内容。所有这些数据都捆绑在一起"图形描述" class,这个类本身是" drawable",继承自" drawable基类"。

虽然这看起来相当明智,但你可以想象这个方法创建了一个复杂的继承结构,带有一些奇怪的东西。 (请允许我详细说明。)

传统上,C ++课程的学生会学习代表" fruit" [在这里插入其他荒谬的例子],"苹果","樱桃"和"梨子"继承自" fruit" 因为它们是所有类型的水果。有些人会在这里猛烈地反对我,因为你有足够的经验知道,实际上有一些常见的例子,这些都不起作用,正如我在实施我的项目时所发现的那样。例如,我设计了自己的存储矢量, drawable。由此,我的存储矩阵继承 - 但矩阵类只是" drawable"如果它包含绘制模式的正确数量的向量(所有相同的长度)。为了澄清这一点,如果矩阵包含2组数据,则可绘制矩阵,并绘制为" x-y points"。但是,如果您有3组数据并且您使用3D绘制(尚未实现)作为" xyz points"或者如果您有3套并且在模式中绘制" xy,它也是可绘制的-with-y-errorbars" ...看到这种情况会变得复杂。因此矩阵继承自vector,但实际上也来自drawable基类。从概念上讲,这是非常奇怪的,因为矩阵和向量都是数学对象。注意这个问题可以通过"可绘制的矩阵类来解决。哪个继承自矩阵和可绘制的基础(逻辑上合理,但由于在构造函数中分配内存的性质而编程上有危险) 包含矩阵类(逻辑上可能不那么明智) [有争议],但编程方式更安全。)

好的,现在回到那个切线,显然这里有一些事情可以说是滥用继承的概念。

方法2

关于继承的第二种观点略有不同。而不是考虑苹果是否是一种水果,而是担心你真正想要对你的课程做些什么。详细说明,我现在根据以下规则将继承视为扩展类的一种方式:

  • 从父级继承的子类只能从父类的成员中读取。 (如果遵循此规则,则不会分配内存两次。)
  • 子类可能有父级没有的其他成员变量。 (明显的)。
  • 子类可能具有成员函数,这些函数对仅存在于子类中的变量进行读/写操作,并且可以对从父类继承的变量进行读操作。

现在我们考虑一下我们真正希望能够我们的数据,而不是考虑派生类是否可以放入更大的类别"其中父级和子级都是成员。 (认为​​水果是水果类的成员,苹果也是如此。水果,苹果和哺乳动物是该类别的成员"生物对象"。)

可以说,这是一种更好的思考方式,因为在一天结束时,编译器并不关心水果是否是生物对象,只是成员函数对成员数据的影响。几节课。

如果你严格遵守第一个"规则",那么你经常会多次输入很多东西,这很烦人且难以维护。您还经常最终将类作为其他类的成员数据(即使通过指针)而不是使用继承。继承功能在某种程度上受到限制。

再次,从切线返回:如前所述,我的图形类包含成员变量,轴类和数据类以及标签类。因为图表是由这些东西组成的。

但我发现实际上所有这些东西(轴,标签和数据)都可以绘制到像素数据......因此为什么不从包含像素数据的类继承呢?这样可以访问该数据(受到保护)。但这在概念上很奇怪,因为轴标签和数据与包含像素数据的类无关。例如,他们自己不应该实现clear()方法! (你可以在概念上清除[重置为默认状态]一个轴,就像清除一个像素值数组一样。)的确,我用友谊来访问来自轴等类的像素数据,而不是继承。

摘要

所以我强调了迄今为止我为构建这个项目代码而提出的两种方法。我希望我已经足够清楚,但可能不是。这是一个难以解释清楚的情况。

我想知道的是关于我应该如何实现这样的图形图库继​​承明智的建议**以及方法1或2是否正确或不正确的原因。我怀疑两者都不完全正确。

**请记住,我有一个像素值数组,并希望能够使用轴和标签绘制不同类型的图形。有很多方法可以做到这一点:每种类型的图形应该是一个继承自抽象基础通用图形类的新类,还是2D图形应该是1类,并且用户应该传入带有数据的标记来告诉图形如何绘制数据?还有一个我不知道的问题是什么课程应该"拥有"其他类的实例。 EG:在2D图形的情况下,图形是否拥有2轴类别?这应该是一个动态分配的指针,所以我们可以分别使用2轴和3轴的2D和3D图形吗?

只要你愿意,问题就可以继续下去 - 我完全糊涂了。也许有人可以给我指导我应该做什么。

1 个答案:

答案 0 :(得分:3)

我认为你最大的问题是,在实际拥有一些具体的东西之前,你会在做出很多选择和设计决定时压倒自己。在我处理你的情况时,我通常采用的方法是从一个基本的例子开始,我想创建并思考我的方式" down"从那里。例如,听起来你的程序只会绘制2D和3D图形。

为什么不从这样的结构开始:

class GDrawable {

    virtual void draw() = 0;
};

class 2DPoint : GDrawable {
    double x, y;
    Point(const double & xx, const double & yy) : x(xx), y(yy) {}
public:
    void draw {}
};

class 3DPoint : GDrawable {
    double x, y, z;
    Point(const double & xx, const double & yy, const double& zz) : 
         x(xx), y(yy), z(zz) {}
public:
    void draw {}
};
class Axis : GDrawable {
    std::vector<double> ticks;
public
    // define members
    void draw {}
};

class 2DGraph : GDrawable {
    std::vector<2DPoint> points;
    std::pair<Axis, Axis> axes;
public:
    void draw {
        std::for_each(points.begin(), points.end(), [](const 2DPoint& p){
            p.draw();
        });
        axes.first.draw(); 
        axes.second.draw();
     }
};

class 3DGraph : GDrawable {
    std::vector<3DPoint> points;
    std::vector<Axis> axes;
public:
    void draw {
        std::for_each(points.begin(), points.end(), [](const 3DPoint& p){
            p.draw();
        });
        std::for_each(axes.begin(), axes.end(), [](const Axis& a){
            a.draw();
        });
     }
};

// main or some client code
std::vector<std::unique_ptr<GDrawable>> drawables;

// fill up drawables

// DRAW EM ALL
std::for_each(drawables.begin(), drawables.end(), [](std::unique_ptr<GDrawable>&& d ) {
        d->draw();
});

您甚至可能想要创建一个AssemblyDrawer类,该类负责获取某些GDrawable的所有元素并绘制它。