关于封装和继承实践的一个问题

A question about encapsulation and inheritence practices

我听说有人说 protected 成员打破了封装点,这不是最佳实践,应该设计程序,使派生的 classes 不需要可以访问 private 基础 class 成员。


示例情况

现在,想象以下场景,一个简单的 8 位游戏,我们有一堆不同的对象,例如,普通方块 作为障碍物,尖刺移动平台等。列表可以继续。

它们都有 x 和 y 坐标指定对象大小的矩形、碰撞框质地。他们还可以共享设置位置渲染加载纹理等功能检查碰撞

但是其中一些还需要修改基础成员,例如盒子可以被推来推去,所以它们可能需要一个移动功能,一些物体可能会自己移动,或者一些块可能会在游戏中改变纹理。

因此,像 object 这样的基础 class 确实可以派上用场,但这要么需要大量的 getters - setters 或让 private 成员成为 protected。无论哪种方式,都会损害封装。


鉴于轶事背景,这将是更好的做法:

1. 有一个共同的基础 class 具有共享的功能和成员,声明为受保护的。能够使用常用函数,将基class的引用传递给非成员函数,只需要访问共享属性。但是妥协封装。

2.每个都有一个单独的class,将成员变量声明为私有的,不要破坏封装。

3。一个我无法想到的更好的方法。


我不认为封装是非常重要的,可能的方法就是让成员受保护,但我对这个问题的目标是编写一个实践良好的标准代码,而不是解决那个特定的问题.

提前致谢。

封装不是安全的事情,它是整洁的事情(因此是可支持性、可读性..)。您必须假设得出 类 的人基本上是明智的。他们毕竟是在使用您的基础 类(所以谁在乎)自己编写程序,或者他们正在与您一起编写程序

面向对象编程中 "encapsulation" 的主要目的是限制对数据的直接访问以最小化依赖关系,并且在必须存在依赖关系的情况下,以 函数的形式表达这些依赖关系 不是 数据 .

这与 Design by Contract 相关,您允许 "public" 访问某些功能并保留随时出于任何原因任意修改其他功能的权利,甚至删除的权利他们,通过将它们表示为 "protected".

也就是说,您可以拥有如下游戏对象:

class Enemy {
public:
  int getHealth() const;
}

其中 getHealth() 函数 returns 一个 int 值表示健康。它是如何得出这个值的?呼叫者不需要知道或关心。也许它是您刚收到的二进制数据包的第 9 个字节。也许它是来自 JSON 对象的字符串。没关系。

最重要的是,因为您可以自由更改 getHealth() 的内部工作方式而不 破坏 任何 相关的代码就可以了。

但是,如果您要公开一个 public int health 属性,那将打开一个充满问题的世界。如果操作不当怎么办?如果它设置为无效值怎么办?您如何设置对被操纵的 属性 的访问权限?

当您拥有 setHealth(const int health) 时,您可以轻松地执行以下操作:

  • 将其限制在特定范围内
  • 超过特定界限时触发事件
  • 更新保存的游戏状态
  • 通过网络传输更新
  • 挂钩其他 "observers" 可能需要知道何时操纵该值

None 这些东西无需封装即可轻松实现。

protected 不仅仅是一件 "get off my lawn" 的事情,它还是一个重要的工具,可以确保您的实施得到 正确使用 作为打算.

首先,我要说的是,设计没有一个千篇一律的答案。不同的问题需要不同的解决方案;然而,随着时间的推移,有些设计模式通常比其他设计模式更易于维护。

的确,许多设计建议使他们在 团队 环境中变得更好——但良好的实践对于个人项目也很有用,因此可以更容易地了解并改变未来。

有时一年后需要理解您的代码的人就是您——所以请记住这一点

I've heard people saying that having protected members kind of breaks the point of encapsulation

像任何工具一样,它也可能被滥用;但是 protected 访问本身并没有破坏封装。

定义对象封装的是预期的投影 API 表面积。有时,protected 成员在逻辑上是表面积的一部分——这是完全正确的。

如果 滥用 protected 成员可以让客户访问可变成员,这可能会破坏 class 的预期不变量——这会很糟糕.例如,如果您能够派生 class 公开 rectangle,并且能够将 width/height 设置为负值。基 class 中的函数,例如 compute_area 可能会突然产生错误的值——并导致级联故障,否则应该通过更好的封装来防止这些故障。

至于你的例子的设计问题:

Base classes 不一定是坏事,但很容易被过度使用,并可能导致 "god" classes 无意中暴露太多功能以供共享逻辑。随着时间的推移,这可能会成为维护负担,并且会造成整体混乱。

你的例子听起来更适合组合,有一些更小的接口:

  • pointvector 类型的东西将是产生像 rectangle 这样的高阶组合的基本类型。
  • 然后可以将它们组合在一起以创建一个 model 来处理二维 space 中存在碰撞的一般(逻辑)对象。
  • intersection/collision 逻辑可以从外部实用程序处理 class
  • 可以从 renderable 接口处理渲染,其中任何需要渲染的 class 都从该接口扩展。
  • 交点处理逻辑可以由 intersectable 接口处理,它确定交点处对象的行为(这有效地将每个游戏对象抽象为原始行为)
  • 等等