添加新方法的子类

Sub-classes that add new methods

我正在写一个 class Tracker ,它将向客户端公开方法以获取用户训练的当前状态,如距离、配速、卡路里等。将此值想象为 getters.

class Tracker{
  float getDistance();
  float getTime();
  float getCalories();
}

现在,提前思考,我可能会发现一种方法来获取海拔高度,然后可能(不是同时)获取步数,所以我的问题是如何更好地解决这个设计。

第一个想法,classic 继承

我的第一个想法是只子class这个接口,所以我最终会得到

class ElevationTracker extends BaseTracker{

   float getElevation();

}

但是,我可能想添加一个 StepTracker,扩展 ElevationTracker,这样我就有两个统计数据。

class StepTracker extends ElevationTracker{

   float getStepsCount();

}

这对我来说看起来有点奇怪,因为 StepTracker 现在 隐含地 提供海拔统计数据,它可能是相反的, ElevationTracker扩展 StepTracker,在这种情况下,这只是我首先发现哪个功能的问题。

此外,我不确定这种继承是否符合专业化理念

一个 class 包罗万象

另一个想法,也许是最简单的,就是只有一个 class Tracker,任何时候我想添加一个新功能,通过添加一个新方法来改变这个 class检索此功能的信息;然后客户端可以更新其代码以使用此新功能。 例如,下个月我将 Tracker class 更改为

class Tracker{
  float getDistance();
  float getTime();
  float getCalories();

  //This is new
  float getElevation();
}

我认为这个解决方案就像我之前想到这个 Elevation 功能一样(我第一次创建 Tracker class),我会从头开始添加最后一个方法。

我现在这样做只是因为现在“要求已经改变”

使每个功能成为 class

另一个想法是不要将每个功能都视为 class 的方法,而是将其视为 class 本身,这意味着我会有一个像 Tracker

interface Tracker {

  float getValue();

  /** Maybe some other methods */

}

然后每个特征都有一个 class

那么我就添加一个新的ElevationTracker和一个StepTracker,它们彼此独立。

这里的问题是有些功能依赖于其他功能,例如 PaceTrackerCaloriesTracker 可能依赖于 DistanceTracker,因此它们可能需要接收一个实例共 DistanceTracker.

此外,客户端代码可能会变得有点混乱,必须为每个功能保留一个实例。 我看到的最重要的陷阱是我通常会一起使用所有这些跟踪器,我可能不会只使用 DistanceTracker 或只使用 ElevationTracker,所以单独使用每个功能可能没有任何好处

结论

我想知道这个选项中哪个最好,或者是否还有其他更好的选项。也许我可以通过一些调整或添加设计模式来改进它来重新考虑其中之一。

在我看来,Single class 选项提供了更快的开发,考虑到,虽然其他人利用了 OOP 功能,但他们只是转移了更新的问题class 已经从 Tracker class 写入另一个客户端或中间 class.

正如您正确指出的那样,继承在这里不适用,因为跟踪器之间不存在 IS-A 关系。如果一个跟踪器需要其他跟踪器来计算其值,则可以使用组合。根据 SOLID 设计原则的单一职责原则,一个 class 或方法应该做一件特定的事情。在这种情况下应用这个原则,为每种跟踪器创建不同的 classes 或接口是有意义的。为每种跟踪器和相应的实现提供接口可能会很好 classes。因此,例如,可以创建 IDistanceTracker、IElevationTracker、IStepTracker 等接口及其实现,如 DistanceTrackerImpl、ElevationTrackerImpl 等。

通过针对接口进行编码,我们保持了代码的灵活性,以便将来提供同一跟踪器的不同实现。例如,在客户端代码中,我们可以使用接口,而在服务代码中,我们可以潜在地使用同一接口的多个实现,并做一些事情,比如对特定的客户端集使用一种实现,而对其余的使用另一种实现。在这种情况下,客户端代码不必更改。有时,假设我们想要升级某些客户端以使用更新版本的实现 - 假设地说 - 可穿戴设备 1.0 硬件只能跟踪步数,假设我们根据步数显示消耗的卡路里,然后是卡路里追踪器可以根据步数计算卡路里。假设在较新的可穿戴硬件 2.0 中,硬件也能够跟踪海拔高度,因为需要修改较新硬件的卡路里追踪器以使用海拔数据来计算卡路里。但旧设备仍应使用以前的卡路里跟踪器,它只使用步数而不使用海拔。这导致需要具有相同跟踪器的多个实现,因此最好通过具有单个接口 ICalorieTracker 和相同的多个实现来针对接口进行编码。

因此,恕我直言,在没有任何继承关系的情况下为每个跟踪器提供单独的接口和实现可能是个好主意。如果存在满足 IS-A 关系的跟踪器,则一个跟踪器接口可以扩展另一个。例如,IAdvancedCalorieTracker 扩展了 ICalorieTracker。这里的子类型满足IS-A关系,所以用继承就好了。