【问题标题】:Derive & Destroy Encapsulation, or Violate DRY?派生和破坏封装,还是违反 DRY?
【发布时间】:2011-07-01 22:18:22
【问题描述】:

我有两个 C++ 类:Sequence,就像 std::vectorFile,它是代表机器上文件的字符串的 Sequence

Sequence 派生File 是不费吹灰之力的。它的行为完全相同,但增加了读取和写入文件的功能。 File 特定的功能很容易实现,无需将Sequence 的数据成员标记为受保护。相反,它们可以是私有的,File 可以使用Sequence 的公共接口。快乐时光无处不在。

我想创建一个内部管理动态分配内存的Array 类。 Array 对象无法调整大小;大小在构造函数中指定。*

这是有争议的地方。

从概念上讲,从Array 派生Sequence 是有意义的。正如 FileSequence 具有读取和写入文件的附加功能一样,@​​987654337@ 是 Array 具有按需调整大小的附加功能。

但有一个关键区别:调整大小功能需要直接访问Array 正在管理的内存。换句话说,以前的私有成员现在必须受到保护。

使用受保护成员而不是私有成员会破坏封装。 ArraySequence 之间的链接是唯一需要它的链接;作品中的其他类可以只使用它们父母的公共接口。从这个意义上说,推导是个坏主意。

您可能会争辩说,想要数组的人可以只使用Sequence 而忽略调整大小的功能。但话又说回来,您可以只使用File 并忽略读/写功能。这就像购买一台笔记本电脑,但从未将其从办公桌上移开。这根本没有意义。

最好的做法是:派生并可能破坏封装;使Array 成为一个完全独立的类,并且必须毫无意义地重新实现很多功能;还是完全忘记Array,让人们使用Sequence

*请注意,这是一个娱乐和教育项目,因此具有不可调整大小的动态分配数组的实用性无关紧要。

【问题讨论】:

  • “数组”和“序列”是什么关系?您对“Array”的描述只是说它是一个内部管理动态分配内存的类(但它的大小是固定的......?)从那个解释中不清楚为什么这两个类完全相关。
  • @Joseph 我不明白这是怎么回事。 Array 只是通过InternalArray = new T[SizeSpecifiedInConstructor] 分配空间。 Sequence 做同样的事情,但它也提供了成员函数来调整 InternalArray 的大小。

标签: c++ oop inheritance encapsulation


【解决方案1】:

您可能会考虑将问题拆分为稍微不同的方向。代替继承,也许这个问题可以用一个模板来解决——具体来说,一个管理集合缓冲区的策略模板。您将拥有(至少)两种实现方式:一种用于固定分配,另一种用于自动调整大小。

这根本不会破坏封装,我可以看到两者之间几乎唯一的重叠是初始分配无论是固定的还是可变的都可能大致相同。鉴于这是多么微不足道,我怀疑是否值得花费大量时间或精力来尝试将其排除在外。理论上可能是这样,但至少在典型情况下,我们谈论的是一行代码,而且是非常简单的代码。

暂时回到继承问题,归结为:这非常类似于标准圆形与椭圆形的情况,其中一个有足够的相似性,看起来像另一个,但最终都不满足 LSP -- 你不能安全地把任何一个当作另一个,所以(至少是公共的)继承是不合适的。私有继承不需要遵循 LSP,但通常仅在/如果您需要/想要覆盖基类的虚函数时才有用,这在此处似乎也不太可能。

【讨论】:

    【解决方案2】:

    我不会在这里使用推导。

    Sequence 并不是真正的Array。虽然实际上它们似乎有许多常用方法,但从设计的角度来看,它们的用途和保证却截然不同。

    不过,在Sequence 中使用Array 并让Sequence 将多个调用(内联)直接转发到Array 是有意义的:

    template <typename T>
    class Sequence
    {
    public:
      Sequence(): _array(10) {}
      explicit Sequence(size_t n): _array(n) {}
    
      bool empty() const { return _size == 0; }
      size_t size() const { return _size; }
      size_t capacity() const { return _array.size(); }
    
    private:
      size_t _size; // current size
      Array<T> _array;
    }; // class Sequence
    

    注意:我在这里假设 Array 是一次性使用其所有元素构建的,而序列将一次添加一个

    同样,File 派生自 Sequence 是否有意义?您是否没有实现问题,例如将Sequence 的内容与磁盘表示同步?

    【讨论】:

    • 我不会不断地将File 对象与磁盘上的表示同步。相反,我提供了Open()Save() 之类的方法。
    【解决方案3】:

    好吧,在您的情况下,使用 public 继承从 Array 派生 Sequence 绝对是个坏主意(从矩形派生正方形)。在面向对象编程方面,序列不是数组,因为Array 具有Sequence 没有的属性,它是:An Array object cannot be resized。如果您进行推导,它将破坏Liskov substitution principle

    在您的情况下,由于您想要实现一些已经存在于另一个类中的功能,我建议您使用 private 继承(这意味着实现的继承)或 composition ,例如将Array 的实例存储在Sequence 的私有区域中,并将其用于内部实现。

    UPD: 但是,在我看来,使用 Array 来实现 Sequence 也很成问题。也许最好创建一些抽象基类Container 来实现SequenceArray 的通用功能,然后从它派生这两个类。

    【讨论】:

    • 如何违反 LSP?请记住,在Array&amp; 上运行的任何函数都可以安全地使用Sequence&amp; 执行相同的操作。
    • 这与方形/矩形示例不同,因为您对Sequence 所做的任何事情都不会改变这样一个事实,即当它被视为一个Array 时,它仍然表现为Array,反之亦然。
    • 你可以改变序列的大小,不是吗?一旦 Sequence 是一个数组(通过公共继承的含义),这意味着您正在更改数组的大小 - 与数组的不变量相矛盾
    • @Grigor 我认为这让 LSP 太过分了。提供Array 之类的check_validity() 方法是没有意义的,因为它根本不可能无效。虽然我确实理解它是如何打破Array 的不变量的,但这并不是Array 保证大小不会改变,而是它没有提供这样做的机制。此外,即使Array 确实有check_validity()Sequence 也肯定会覆盖它。
    • 作为记录,我同意公共继承是不合适的。如果有的话,我会考虑私下从 Sequence 派生 Array。只是不是因为违反了 LSP。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-28
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多