接口无益

你认为接口怎么样?

你的意识是Java或者C#的interface

是的,接口是一个好的语言特性吗?

当然,接口很好用!

真的吗,呃。接口是什么?一个类吗?

不是,和类不一样。

怎么说?

接口没有方法实现。

这是一个接口吗?

1
2
3
public abstract class MyInterface {
public abstract void f();
}

这不是,这是一个抽象类。

比起接口,有什么不同?

好吧,抽象类可以有方法实现。

对,但是这个没有,为什么它不是一个接口呢?

好吧,一个抽象类可以包含非静态变量,而接口不能。

对,但是这个也没有。再问,为什么它不是一个接口?

因为它本来就不是。

这不是一个令人满意的答案。如何区分它和接口呢?有什么是可以使用接口但是不能使用抽象类的呢?

类可以继承其他类,但是不能同时实现你的这个抽象类。

为什么不能?

因为,在Java里,你不能同时继承多个类。

为什么不能?

因为Java编译器不允许你这样做。

这太死板了。好吧,为什么不能实现那个抽象类而不是继承它呢?

因为编译器只允许你实现一个抽象接口。

这种规则太奇怪了。

才不是,这很合情合理。编译器允许你实现多个接口但是只能让你继承一个类。

为什么Java编译器允许你实现多个接口,但是不允许你继承多个类呢?

因为类多重继承很危险。

真的吗?怎么回事?

因为多重继承会导致致命方块。

天呐,听起来很可怕。什么是致命方块?

当某个类继承其他两个类,这两个类就同时继承另外一个类。

你的意思是:

1
2
3
4
class B{}
class D1 extends B {}
class D2 extends B {}
class M extends D1, D2 {}

对,这很糟糕!

为什么这很糟糕?

因为类B可能有实例变量!

你的意思像这样?

1
class B {private int i;}

对呀!这样的话某个M的实例会有多少个i变量呢?

嗯,我明白了。由于D1和D2都有i变量,又因为M继承与D1和D2,这种情况下你也许期盼着M有两个不同的i变量。

对!但是由于M也间接继承与B,B只有一个i变量,你也许只期望M只包含一个i变量。

嗯,这种继承关系有点模糊不清。

对!

因此Java和C#不能有多重继承是因为可能造成致命方块问题?

不是的,因为每个人都可能造成致命方块问题由于所有的类都是继承与Object类的。

嗯,明白了。编译器作者不会把Object特殊处理吗?

呃…他们不会。

我有点好奇为什么呢?有其他编译器作者解决这个问题吗?

当然有,C++允许你创建致命方块问题。

对,我想Eiffel也可以。

对了,天呐,我想Ruby有解决方法。

是的,使用CLOS(common-lisp object system)—好吧,我们说致命方块问题几十年前就解决了并且不是致命的,也没有导致更坏的结果。

呃。对,我猜你是对的。

让我们回到我原来的问题上。为什么这不是一个接口?

1
2
3
public abstract class MyInterface {
public abstract void f();
}

因为它使用的关键字是class;而且这门语言也不允许你做多重继承。

对。所以关键字interface的发明是为了避免多重继承。

是的,可能是事实。

为什么Java或者C#的作者不适用已知的解决方案解决多重继承呢?

我不知道。

我也不知道,但是我可以猜。

你猜到了什么?

懒惰。

什么,懒惰?

对,它们根本不想处理这个问题。因此创建了一个新的东西绕过这个问题。这个东西就是interface。

你是说Java的作者创建interface是为了避免一些额外的工作量?

除此之外,我不能解释这个问题。

这有点太过鲁莽。我确信他们的意图肯定不只是这个。不管怎么样,有interface不会是什么坏事吧?我的意思是,它应该也没什么坏处吧?

尝试问你自己这个问题:为什么某个类需要知道它实现某个接口?这种东西是不是应该隐藏起来呢?

你的意思某个派生类应该知道这个并判断使用正确的关键字,extends或者implements,对吗?

对!如果你把class改成接口,有多少派生类需要同时修改?

都得改,至少在Java里面来说。在C#倒是解决了这个问题。

确实是这样。关键字implements和extends确实是多余而且危险的。Java还不如C#和C++使用冒号。

好,好吧,但是究竟什么时候需要使用多重继承呢?

下列情况我会用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Subject {
private List<Observer> observers = new ArrayList<>();
private void register(Observer o) {
observers.add(o);
}
private void notify() {
for (Observer o : observers)
o.update();
}
}

public class MyWidget {...}

public class MyObservableWidget extends MyWidget, Subject {
...
}

哦,这是观察者模式!

是的,这是观察者模式——完全正确。

但是你不能编译因为你不能继承多个类。

对,这就很悲剧。

悲剧?但是为什么这样说呢?我的意思是你可以让MyWidget继承Subject类!

但是我并不想MyWidget知道任何关于观察者的信息。我想保持其关注点分离。观察者与小部件的关注点分离。

之后只需要在MyObservableWidget类实现register和notify方法。

什么?在每个观察者里面重复这些代码?我不认同这种做法。

在MyObservableWidget里持有Subject的引用把功能代理给Subject。

什么?在每个观察者里面重复这些代理代码?如此愚蠢。如此拙劣。呸。

好吧,你不得不在一个或多个观察者做这事。

我知道。但是我讨厌这样做。

呃,似乎也没办法避免。要么你违反关注点分离原则,要么重复部分代码。

是的。这种情况下语言强迫我不得不这样做。

是的,很不幸。

到底是语言的什么特性导致这种糟糕的局面呢?

关键字interface。

所以说…?

关键字interface没什么好处,接口无益。


翻译自‘Interface’ Considered Harmful

文章目录
分享到