[Java翻译]Java 8中作为继承的替代物的混合器

166 阅读4分钟

本文由 简悦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);
  }
}

我们还有一个 AirportAircraft 可以降落和离开。

public class Airport {

  List<Aircraft> aircrafts;

  public void land(Aircraft a) {
    aircrafts.add(a);
  }

  public void depart(Aircraft a) {
    aircrafts.remove(a);
  }
}

鉴于此,如果我们想有另一个同时是 AirportShip 的类,怎么办?不现实吗?那么一个 AircraftCarrier 呢。由于java不支持多重继承,我们不能声明一个从 AirportShip 扩展的 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更适合于 "继承 "功能。


www.deepl.com 翻译