从面向对象的最佳实践的角度来看,我应该在哪里放置父类的某些子节点所需的变量或方法,而不是其他子节点?
实施例。 类Button,Knob,Lever和Switch继承自父类Device。 Button,Lever和Switch需要一个布尔值isOn,但Knob不需要。你在哪里定义isOn?
杠杆和开关需要一个切换isOn的方法Throw(); Button使用isOn但不使用Throw()来处理它。这是否会影响您对isOn的放置,以及您在何处定义Throw()方法?
以上仅仅是一个例子;让我们假设每个子类都有不同的属性来区分它,并且存在使用所讨论的继承模型合理的共性。
答案 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,因为它是一个可切换的控件。