你想要使用 Python 程序以特定方式解析 Apache access.log 文件,并且希望使用面向对象编程 (OOP) 来实现解析工作。你想创建一个 ApacheAccessLog 类,但只能想到 readline 方法。你不知道在这种情况下从内置 file 类继承是否正确,即是否能让类表现得像 file 类实例一样。
2、解决方案
方法一:委托,而非继承
在这种情况,你可以使用委托而非继承。这意味着你的类应该包含 file 对象作为属性,并在其上调用 readline 方法。你可以在 Logger 类构造器中传递 file 对象。
这种方法至少有两个原因:
- 委托降低了耦合。例如,你可以使用任何实现
readline方法的其他对象来替代file对象(鸭子类型派上用场了)。 - 从
file继承时,你的类的公共接口变得不必要地宽泛。它包含file定义的所有方法,即使这些方法在 Apache 日志的情况下没有意义。
方法二:不要继承你不了解和不控制其实现的类
总的来说,你不应该继承你不了解和不控制其实现的类,除非该类是专门为继承而设计的。如果类是按这种方式设计的,它应该在文档中清楚地说明这一点。
继承可能会将你绑定到所继承类的实现细节。例如,如果我们要扩展 ArrayList 类以计算在其生命周期中添加到其中的项数(不一定是当前包含的项数),我们可能倾向于编写类似这样的代码:
public class CountingList extends ArrayList {
int counter = 0;
public void add(Object o) {
counter++;
super.add(0);
}
public void addAll(Collection c) {
count += c.size();
super.addAll(c);
}
// 等。
}
现在,这个扩展看起来可以准确地计算添加到列表中的元素数,但实际上可能无法做到。如果 ArrayList 通过迭代提供的 Collection 并为每个元素调用其界面方法 addAll 来实现 addAll,那么我们将两次计算通过 addAll 方法添加的每个元素。现在,我们类的行为取决于 ArrayList 的实现细节。
除了讨论过的无法使用其他 List 实现与我们的 CountingList 类一起使用的缺点外,这种行为也不是很完美。另外,继承自具体类的缺点是在这里。
方法三:继承是可行的,但要谨慎
在某些情况下,从内置类继承很有用。在这种情况下,继承是合适的,因为 apache 日志“就是”一个文件,所以这种情况是合适的。
一般规则是,狗“是”动物,因此从动物继承。主人“有”一个动物,因此不要从动物继承。
方法四:考虑用例并设计数据类
虽然在某些情况下从内置函数继承很有用,但这里真正的问题是你想用输出做什么以及你的宏观设计是什么。我通常会编写一个读者(它使用文件对象)并吐出我需要保存刚刚读取的信息的任何数据类。然后,很容易设计数据类以适应我的设计其余部分。
方法五:权衡继承的利弊
你应该可以从“内置”类继承,因为对这些类的后续修改通常与当前版本兼容。但是,你应该认真考虑你是否真的想将类与内置类提供的附加功能捆绑在一起。正如另一个答案中提到的,你应该考虑(甚至可能更喜欢)使用委托代替。
作为避免继承(如果你不需要的话)的理由的一个例子,你可以看看 java.util.Stack 类。由于它扩展了 Vector,因此它继承了 Vector 上的所有方法。其中大多数方法破坏了 Stack 隐含的契约,例如 LIFO。最好使用 Vector 内部实现 Stack,仅将 Stack 方法公开为 API。那么以后很容易将实现更改为 ArrayList 或其他东西,由于继承,现在这些都不可能了。