我应该在哪里放置几个(但不是全部)子类所需的变量或方法?

时间:2014-07-09 17:43:19

标签: oop scope object-oriented-analysis

从面向对象的最佳实践的角度来看,我应该在哪里放置父类的某些子节点所需的变量或方法,而不是其他子节点?

实施例。 类Button,Knob,Lever和Switch继承自父类Device。 Button,Lever和Switch需要一个布尔值isOn,但Knob不需要。你在哪里定义isOn?

杠杆和开关需要一个切换isOn的方法Throw(); Button使用isOn但不使用Throw()来处理它。这是否会影响您对isOn的放置,以及您在何处定义Throw()方法?

以上仅仅是一个例子;让我们假设每个子类都有不同的属性来区分它,并且存在使用所讨论的继承模型合理的共性。

3 个答案:

答案 0 :(得分:3)

当只有一个子类子集共享功能时,这可以用包含相关方法的接口表示,该接口仅由需要它们的子类实现。

public interface OnOffable  {
   boolean isOn();
   void toggleOnOff();
   void turnOn(boolean is_on);
   void turnOn();
   void turnOff();
}

class Switch extends Device implements OnOffable...

如果一个或多个函数中等复杂,您可以创建一个静态实用程序函数,以帮助防止冗余代码。然而,在这个例子中,"复杂性"需要保持开关状态。

在这种情况下,您可以创建OnOffableComposer(我的偏好)未实现OnOffable

实际上,由于这个特殊的界面可以完全实现(意味着它不需要保护或抽象的功能),它实际上可以是一个简单的"实施:

public class SimpleOnOffable implements OnOffable  {
     private boolean isOn;
   public class OnOffableComposer(boolean is_on)  {
      turnOn(is_on);
   }
   public boolean isOn()  {
      return  isOn;
   }
   public void turnOn(boolean is_on)  {
      isOn = is_on;
   }
   public void toggleOnOff()  {
      turnOn(!isOn());
   }
   public void turnOn()  {
      turnOn(true);
   }
   public void turnOff()  {
      turnOn(false);
   }
}

以下是如何使用的:

public class Switch extends Device implements OnOffable  {
   private final SimpleOnOffable smplOnOff;
   public Switch(boolean is_on)  {
      smplOnOff = new SimpleOnOffable(is_on);
   }
   public boolean isOn()  {
      return  smplOnOff.isOn();
   }
   public void turnOn(boolean is_on)  {
      smplOnOff.turnOn(is_on);
   }
   public void toggleOnOff()  {
      smplOnOff.toggleOnOff();
   }
   public void turnOn()  {
      smplOnOff.turnOn();
   }
   public void turnOff()  {
      smplOnOff.turnOff();
   }
}

虽然作曲家很简单,但这都体现了choosing composition over inheritance的概念。它允许比单继承允许更复杂的设计。

答案 1 :(得分:0)

这听起来像是错误的抽象。至少,Knob并不属于其他人。我可以在Device和三个密切相关的设备之间注入一个课程。也许像BinaryDevice

abstract class Device {}
abstract class BinaryDevice : Device {
    abstract void Activate();
    abstract void Deactivate();
}
class Switch : BinaryDevice {
    void Activate() { // activate the switch }
    void Deactivate() { // deactivate the switch }
}
// same for Lever, which honestly is really just a Switch with a different styling and may not even need to be a separate object
class Button : BinaryDevice {
    void Activate() { // activate the button, then immediately call Deactivate() }
    void Deactivate() { // deactivate the button }
}

Knob也可以从Device继承,但此时Device没有共同的功能,所以不清楚为什么普遍的公共基类是偶数必要。随着向各种设备添加进一步的功能,可能确实存在推送到基类的共同功能。实际上,dealing with generalization就像这样有完善的重构模式。

使用上面的类,我想在尝试调用错误状态的操作时会出现错误处理。例如,很难想象Button需要任何人在其上调用Deactivate()的场景,因为它会自行取消激活。 (虽然就像现实生活中的按钮可能会卡住一样,但如果它调用的动作由于某种原因而挂起,那么它也是如此。)但无论如何,即使其他对象上的Activate()Deactivate()也是如此。仍然需要检查状态。例如,您无法激活已经处于活动状态的开关。

关键是,当更仔细地考虑术语和现实世界建模时,对象模型的清晰度开始变得更加明显。很多时候,开发人员试图将术语扼杀成少数常用术语,以便最大限度地利用继承等内容,不幸的是,这通常会导致错误的抽象和混乱的域模型。

构建自然存在的对象,然后查找可以抽象为常用功能的模式。不要先尝试定义常用功能,然后强制对象适合该模具。

答案 2 :(得分:0)

一般来说,我会说如果某些但不是所有的孩子都需要父类的元素,那么应该引入一个中间父级。

在定义继承层次结构时,它是一个逻辑假设,即父项的子项应共享该共同祖先的所有属性。这类似于biological taxonomy的工作方式,它有助于保持代码清洁。

因此,让我们看一下系统中的对象(我们将使用"是一种"关系来帮助我们找出继承层次结构):

按钮,旋钮,控制杆和开关

其中每一个都可能被称为"设备",但是当我们说"设备"大多数人可能会想到手机或平板电脑等数字设备。描述这些对象的更好的词可能是"控制"因为这些物体可以帮助你控制事物。

所有对象都是控件吗?实际上它们是,所以每个对象都将 Control 作为其继承层次结构中的父对象。

我们需要进一步分类吗?那么你的要求就是有一个开/关状态,但是每个控件都有开/关状态没有意义,但只有部分开启状态。因此,让我们进一步将它们分为Control和ToggleControl。

每个 Control ToggleControl 吗?不,所以这些是单独的对象类。 每个 ToggleControl Control 吗?是的,因此 ToggleControl 可以从 Control 继承。 是否对所有对象进行了正确分类,并且所有子对象都共享父属性?是的,所以我们已经完成了构建我们的继承层次结构。

我们的继承层次结构如下所示:

                 Control (Code shared by all controls)
                   /  \
                  /    \
              Knob    ToggleControl (Code shared by all controls that can also be toggled - Adds isOn)
                         \
                          \
                        Button, Lever, Switch

现在,问题的另一部分:

  

杠杆和开关需要一个切换isOn的方法Throw(); Button使用isOn但不使用Throw()来处理它。这是否会影响您对isOn的放置,以及您在何处定义Throw()方法?

首先,"扔"是一个保留字(至少在Java中),因此使用类似于保留字的方法名称可能会引起混淆。该方法可能更好地命名为" toggle()"。

按钮应该(实际上它必须)使用toggle()来切换它的isOn,因为它是一个可切换的控件。