本文由 简悦SimpRead 转码,原文地址 hannesdorfmann.com
在Java 8中,我们可以使用mixins,也被称为默认实现的接口来做继承人......。
从Android N开始,谷歌增加了一些Java 8的语言功能。其中一个特点是可以向接口添加默认方法。令人惊讶的是(因为java 8已经发布两年了),我还没有找到好的文章来描述接口默认方法的优势。Mixins!
让我们直接跳到一个简单的例子。假设我们有一个 Ship 类。一个 船 可以携带 货物 。让我们在Java中这样建模。
public class Ship {
List<Cargo> cargoes;
public void addCargo(Cargo c){
cargoes.add(c);
}
public void removeCargo(Cargo c){
cargoes.remove(c);
}
}
我们还有一个 Airport ,Aircraft 可以降落和离开。
public class Airport {
List<Aircraft> aircrafts;
public void land(Aircraft a) {
aircrafts.add(a);
}
public void depart(Aircraft a) {
aircrafts.remove(a);
}
}
鉴于此,如果我们想有另一个同时是 Airport 和 Ship 的类,怎么办?不现实吗?那么一个 AircraftCarrier 呢。由于java不支持多重继承,我们不能声明一个从 Airport 和 Ship 扩展的 AircraftCarrier 类。
class AircraftCarrier extends Airport, Ship // doesn't compile
那么,我们有什么办法可以使 AircraftCarrier 同时成为 船舶 和 机场 ?好吧,我们可以使用委托("倾向于组合而不是继承")或aspect导向的编程中的一些技巧,但这些都不是java编程语言所真正支持的。
然而,java确实支持接口的多重继承,并且允许类实现任意多的接口。所以我们把_Ship_和_Airport_转换为接口。
public interface Ship {
void addCargo(Cargo c);
void removeCargo(Cargo c);
}
public interface Airport {
void land(Aircraft a);
void depart(Aircraft a);
}
class AircraftCarrier implements Ship, Airport {
List<Aircraft> aircrafts;
List<Cargo> cargoes;
public void land(Aircraft a) {
aircrafts.add(a);
}
public void depart(Aircraft a) {
aircrafts.remove(a);
}
public void addCargo(Cargo c){
cargoes.add(c);
}
public void removeCargo(Cargo c){
cargoes.remove(c);
}
}
但是现在我们必须手动实现每个接口的方法。在这个简单的例子中,这不是什么大问题,因为基本上每个方法只有一行代码,但我想你会明白的。
Ruby程序员是如何处理继承问题的?有经验的Ruby程序员不会在这种事情上使用继承。他们使用Mixins。
module Airport # for simplicity: module is same as class
@aircrafts = Array.new # variable
def land(aircraft)
aircrafts.push(aircraft)
end
def depart(aircraft)
aircraft.delete(aircraft)
end
end
class AircraftCarrier
include Ship
include Airport
end
通过Mixins,我们可以通过 "包括"(或混合)Ship和Airport的功能来组成_AircraftCarrier_,而无需继承。为了提高可读性,我们省略了Ship的定义,但我想你可以想象_module Ship_的代码会是什么样子。请注意,这不是像C++提供的多重继承(钻石问题)。混合元素是一个不同的概念。Ruby编程语言对Mixins有本地支持。那么其他语言如Scala呢?Scala也有对Mixins的支持。在Scala中,这些被称为Traits。Traits就是Mixins,只是从编程语言设计者的角度来看,它有一些稍微不同的属性,比如Mixins需要线性化,而Traits是扁平化的,Traits传统上不包含状态。但这不应该让你太担心。为了简单起见,我们可以说Mixins和Traits是一样的。
trait Ship {
val cargoes : ListBuffer[Cargo]
def addCargo(c : Cargo){
cargoes += c
}
def removeCargo(c : Cargo){
cargoes -= c
}
}
然后用Scala,我们可以做这样的事情。
class AircraftCarrier with Ship with Airport
你看,Ruby和Scala都有对Mixins的原生语言支持。但是java呢?通过Java 8和接口的默认方法,我们可以做同样的事情。
public interface Airport {
// To be implemented in subclass
List<Aircraft> getAircrafts();
default void land(Aircraft aircraft) {
getAircrafts().add(aircraft);
}
default void depart(Aircraft aircraft) {
getAircrafts.remove(aircraft);
}
}
我们将对 Ship(具有默认方法实现的接口)做同样的事情,然后我们可以做这样的事情。
class AircraftCarrier implements Ship, Airport {
List<Aircraft> aircrafts = new ArrayList<>();
List<Cargo> cargoes = new ArrayList<>();
@Override
public List<Aircraft> getAircrafts(){
return aircrafts;
}
@Override
public List<Cargo> getCargoes(){
return cargoes;
}
}
AircraftCarrier carrier = new AircraftCarrier();
carrier.addCargo(c);
carrier.land(a);
此外,现在我们可以更容易地组成新的类,而且不需要传统的继承。
class Houseboat implements House, Ship { ... }
class MilitaryHouseboat implements House, Ship, Airport { ... }
我想你已经明白了。有了Java 8和接口的默认方法,我们可以使用Mixins来代替继承。Kotlin也通过接口的默认方法提供Mixins。
interface Ship {
val cargoes : List<Cargo>
fun addCargo(c : Cargo){
cargoes.add(c)
}
fun removeCargo(c : Cargo) {
cargoes.remove(c)
}
}
class AircraftCarrier : Ship, Airport {
override val cargoes = ArrayList()
override val aircrafts = ArrayList()
}
希望你能看到Mixins可能是一个比传统继承更好的选择。但这对android开发有什么帮助呢。在android中,我们总是不得不从Activity或Fragment中扩展,这显然是两个不同的基类。通过Mixins,我们可以在从Activity扩展的类和从Fragment扩展的类之间共享代码。当然,这不是Mixins的主要用途(委托可能是更好的选择),因为Mixin中定义的那些方法将在内部被实现Mixin接口的Fragment或Activity本身使用,但这是一个有效的选择。不幸的是,Android上带有默认方法的接口需要 min sdk 24(Android N) 和Jack作为编译器。
这个故事的寓意是,有一个替代继承的方法是很好的,我感觉很不幸的是,在java世界里,通过默认实现的接口的Mixins并没有被使用很多。
我对继承的一般建议。
继承并不能很好地扩展! 使用继承来继承属性,但不用于继承功能 Mixins更适合于 "继承 "功能。