当我的程序员介意让我以面向对象的方式思考这个概念时,我正在阅读一些关于汽车collision avoidance systems的文章,这让我想知道这些系统是否尊重面向对象的编程模型。
我主要是一个Java开发人员,我在Java环境中转换了这个问题,并提出了一个特殊问题:在同一个类(在非静态上下文中)调用公共方法是否尊重并遵循面向对象的方式?
我的意思是,采取这个简短的假设汽车课:
public class Car {
// Class attributes.
// Constructors.
public void accelerate(final double amplitude) {
// Accelerate according to the amplitude.
}
public void brake(final double amplitude) {
// Brake according to the amplitude.
}
// Other useful methods.
private void collisionPreventionActions() {
// Some actions.
brake(100.0);
// Some other actions.
}
}
假设某些线程负责检测到冲突并在检测到冲突时采取措施,其中一个动作就是制动。显然brake(...)
方法成为一个有趣的选择,但这不会打破面向对象的做事方式吗?这不只是刹车。如果这个类中的防撞系统使用方向盘来避免事故怎么办?我觉得很奇怪汽车会从内部角度使用自己的输入......
在更一般的范围内,假设您有一个通用对象,我希望将其视为黑盒子。公共方法将相当于控制其行为的黑匣子上的杠杆。在此对象中调用公共方法意味着黑盒子将从其内部机制激活其自己的杠杆。
我问,因为我知道这样做是合法的,并且我已经看到公共方法在我的生活中被多次调用在同一个类中,但它是合法的并不一定意味着它是正确的OO这样做的方式。
在非静态上下文中使用同一类中的公共方法是否遵循面向对象编程和封装的规则?如果没有,那么正确的做法是什么,或者解决方法是什么?
答案 0 :(得分:6)
从OOP的角度来看,这个选择没有任何问题:对于一个需要组合其他方法的方法来说,这是完全正确的。
但实际上,一种常见的方法是将功能分为公共部分和私有部分,如下所示:
public void brake(final double amplitude) {
// check preconditions
if (speed == 0) throw new IllegalStateException("cannot brake when standing");
if (amplitude <= 0) throw new IllegalArgumentException("amplitude must be positive");
// ... do other important checks
doBrake(amplitude);
}
private void doBrake(final double amplitude) {
// The real code goes here
}
现在,collisionPreventionActions
可以拨打doBrake
而不是brake
,假设您在拨打电话前已经检查了所有必要的前提条件。
注意:doBrake
也应检查其前提条件。但是,不要在不满足前提条件时抛出异常,而是使用断言。不同之处在于异常表示其他人滥用您的公共方法,而断言表明您或其他人维护您的代码时滥用您的封装方法。
答案 1 :(得分:3)
当对象使用自己的API时,不会违反任何规则。相反,如果一个类具有可以被覆盖的API,但是它无法在内部使用该API,则可能会出现问题。
作为一个简单的例子,考虑一个非最终属性访问器。对象可以跳过访问器并直接读取(或更糟,写入)字段。假设在子类中重写访问器以使用该字段以及来自子类的一些其他信息来计算属性值。现在课程被打破了,因为它没有遵守自己的合同。
考虑下面的(有些人为的)Point
和OffsetPoint
类。派生类OffsetPoint
编写正确,但它继承的toString()
方法将无法正常工作,因为父类Point
错误地无法使用自己的访问器。
public class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
@Override
public final String toString() {
/* Here's the bug; should be getX() and getY() instead of x and y */
return String.format("(%d,%d)", x, y);
}
}
class OffsetPoint extends Point {
private int dx, dy;
OffsetPoint(Point point, int dx, int dy) {
super(point.getX(), point.getY());
this.dx = dx;
this.dy = dy;
}
@Override
public int getX() { return super.getX() + dx; }
@Override
public int getY() { return super.getY() + dy; }
}
答案 2 :(得分:2)
在非静态上下文中使用同一个类中的公共方法是否遵循面向对象编程和封装的规则?
不,封装没有问题,因为该方法是公开的,所以任何人(甚至this
)都可以调用它。
然而,对于像防撞系统这样的东西,依赖公共方法可能是不好的安全方法。
让我们使用这个内部碰撞检测器的例子来调用公共方法brake()
。如果有人将汽车分类并超越方法怎么办?
public class BrokenCar extends Car{
@Override
public void brake(final double amplitude) {
//BREAKS CUT!!!
}
}
因此,有些安全规则不依赖于可覆盖的方法。制作brake
和accelerate
最终方法可以解决此问题。
答案 3 :(得分:0)
是的,我认为在OO环境中调用您自己的公共方法是正确的。当一个方法存在重载时,这是非常常见的,除了一个方法之外,所有方法都调用最具体的方法,要么填写缺失参数的默认值,要么转换参数的类型。我还看到了所有重载调用到同名的私有或受保护方法的模式,其中_internal或_impl添加到结尾。例如,ComputeSpeed的几个重载可能都调用ComputerSpeed_internal。如果公共方法中存在您不想做两次或不适合内部呼叫的参数验证,则此模式是合适的。
你可以通过没有明确的关注点分离来引入问题。例如,如果collisionPreventionActions的调用者也决定设置制动器是个好主意,那么你可能会对制动器的应用程序产生冲突。
KC
答案 4 :(得分:0)
一般来说,调用公共方法很好。需要考虑的是汽车的界面应该是什么。在这种情况下,preventCollision()
是否属于Car类或其他CollisionPrevention
类。
使用单一职责打破您的代码多个类,然后使用像Car
这样的更大的类,通常是一个好主意。
答案 5 :(得分:0)
我完全同意你的观点,一个类本身可以解决其私有成员和方法的问题。但我不明白为什么它在面向对象范式方面不合法。请考虑以下示例:
public class Human {
public Human() {
liveYourLife();
}
private void liveYourLife() {
while(alive){
createYourDay();
}
}
private void createYourDay() {
drink();
eat();
sleep();
awake();
drink();
}
private void eat() {}
private void drink() {}
private void sleep() {}
private void awake() {}
}
可能有人会批评上面例子中所示的简单生活规则。但是我想用上面的几行来证明,&#34;通常&#34; 允许人类决定他的日常生活。
OO-Paradigm的基本原则是描述现实世界实体的行为和属性。因此,只要您自己决定何时想吃,喝,睡等,您的上述模型绝对正确。但是,如果您在问题域中发现了一些您希望在软件中解决的特殊情况(例如,您被捕,等等),您应该更新您的OO设计。
如果存在严重影响另一个实例状态的内容,则应将&#34;干扰实例&#34; 视为另一个引用的对象实际的实例。
public class Prisoner extends Human {
@Override
private void liveYourLife() {
while(jailed){
createYourDay();
}
}
@Override
private void createYourDay() {
// A bit different :)
}
}
public class Prison {
private List<Prisoner> prisoners;
}