假设我有一个List<Apple>
对象,每个苹果都有一种颜色。
我实现了另一个对象,该对象是用其输入苹果对象列表构造的。我可能会在这个对象上实现一些功能,比如“让我知道青苹果的数量。”,我可以在不知道该对象的内部表示的情况下调用它。
你会怎么称呼这个人?这似乎是基本的 OO,但我在想一个描述性的名称时遇到了麻烦。
假设我有一个List<Apple>
对象,每个苹果都有一种颜色。
我实现了另一个对象,该对象是用其输入苹果对象列表构造的。我可能会在这个对象上实现一些功能,比如“让我知道青苹果的数量。”,我可以在不知道该对象的内部表示的情况下调用它。
你会怎么称呼这个人?这似乎是基本的 OO,但我在想一个描述性的名称时遇到了麻烦。
由于您在谈论一种List<Apple>
可能性,因此您也在谈论一种Collection<Apple>
(列表继承集合)。
如果您想强调“收藏”,那么我将其命名为“收藏”。苹果传统上以蒲式耳出售,但您不关心测量,所以我会使用更通用(不取决于大小)的术语“篮子”。
public class Basket {
private int greenCount;
public void addApple(Apple apple) {
if (apple.isGreen()) {
greenCount++;
}
}
public int getGreenAppleCount() {
return greenCount;
}
}
这恰当地抓住了计算篮子中青苹果的责任。
另一方面,实用程序似乎将任务的责任与特定类型绑定的对象分开。例如,让我们看一个假设的AppleCounterUtil
.
public class AppleCounterUtil {
public int getCount(Collection<Apple> apples, AppleCondition condition) {
int count = 0;
for (Apple apple : apples) {
if (condition.isSatisfied(apple)) {
count++;
}
}
}
}
现在我们有了一个很好的实用程序,它实际上不维护计数,但每次需要从可能不同的苹果列表中重新计算计数。
关键是后一个例子不是面向对象的,因为没有概念对象。声明没有对象的基本原理是因为包含该实用程序的类缺少状态。对象是数据和密切相关的代码的集合。当您只有代码时,可以合理地论证您有一个名称空间函数而不是一个对象。
为了再次看到差异,让我们比较两个代码片段。使用我们的第一个“篮子”方法:
...
int greenOnes = basket.getGreenAppleCount();
basket.addApple(new GreenApple());
greenOnes = basket.getGreenAppleCount();
...
变得非常笨重,但是由于我们是面向对象的,我们可以很容易地添加一个Basket Listener
接口到Basket
public class Basket {
public void addListener(BasketListner listener) {
...
}
public void addApple(Apple apple) {
...
for (BasketListnener listner : listeners) {
listener.appleAdded(this);
}
}
}
... some other class ... implements BasketListener {
int greenOnes = 0;
...
public void appleAdded(Basket basket) {
greenOnes = basket.getGreenAppleCount();
}
}
而使用面向实用的技术
...
int greenOnes = AppleCounterUtil.getCount(apples, new GreenCountCondition());
apples.addApple(new GreenApple());
greenOnes = AppleCounterUtil.getCount(apples, new GreenCountCondition());
...
表面上看起来很相似,直到您尝试添加更多功能。在尝试添加“苹果列表监听器”时,您很快就会意识到它AppleCounterUtil
不负责维护计数,并且无法被监听。“苹果列表”也不负责维护计数,通用列表通常会提供错误的监听接口。
不幸的是,当一个人变得面向实用程序时,他们通常会尝试通过添加更多实用程序方法来解决问题。这最终可能意味着特定的关注点(谁管理苹果组)可以分布在许多实用程序中,而无需任何实用程序单独负责该任务。每个实用程序只提供一些可以按需计算的“功能”。
在某些情况下,按需计算功能可能会依赖于其他按需计算功能,这样您就可以在两者之间获得功能耦合(即使没有直接的代码耦合)。这种有效的耦合意味着如果您未能按顺序调用特定的一组实用程序,或者省略了所需的实用程序调用,您的代码就会中断。一个极端的情况是对象最终失去了它们的行为,以至于它们变成了数据结构,或者失去了通常与对象相关联的行为。它是一种面向对象的设计反模式,被称为“贫血对象”。