Flutter学习日记Flutter中文社区Flutter圈子

Mixins in Dart

2019-07-22  本文已影响1人  光明自在

官方文档如此描述Dart语言:

Dart is an object-oriented language with classes and mixin-based inheritance. Every object is an instance of a class, and all classes descend from Object. Mixin-based inheritance means that although every class (except for Object) has exactly one superclass, a class body can be reused in multiple class hierarchies.

与Java、OC等App开发者常用的面向对象编程语言一样,Dart是单继承的,除了根类,每个类有且只有一个父类;不同的是,Dart的继承是基于Mixin的,一个类的代码可以在多个类层次结构中被重用。

对于大多数iOS、Android开发者来说,Mixin是一个陌生的概念。由于其功能强大,Flutter库中大量使用了Mixin,因此有必要研究一番。

Mixin是什么?

简单来说,Mixin是在多个类层次结构中重用代码的一种方式。

在面向对象编程语言中,mixin是一个类,它的代码供其它类使用,但无需成为其它类的父类。其它类如何获得mixin方法的使用权取决于具体的编程语言。Mixin有时被描述为”包含的“而不是”继承的“。

Mixin鼓励代码重用,可用于解决多继承的菱形问题,或解决语言中缺乏对多继承支持的问题。Mixin也可以被看作带有默认实现方法的接口。

— 维基百科

在Dart语言中,通过with关键字加一个或多个mixin名字的方式使用mixin:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

定义mixin的方式跟类一样,只不过没有构造方法。除非希望mixin可以像普通类一样使用,否则使用mixin关键字而不是class。例如:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

通过on关键字可以限定mixin允许被哪些类使用,这样mixin中可以调用非自身定义的方法。

mixin MusicalPerformer on Musician {
  // ···
}

其实,在Swift等现代编程语言中,也有类似Mixin的特性,我们可以使用协议扩展的方式在Swift中实现Mixin效果:

protocol ErrorDisplayable {
    func error(message:String)
}

extension ErrorDisplayable {
    func error(message:String) {
        // Do what it needs to show an error
        //...
        print(message)
    }
}

struct NetworkManager : ErrorDisplayable {
    func onError() {
        error("Please check your internet Connection.")
    }
}

历史

Mixin最初出现在Symbolics的面向对象风味系统,该系统是Lisp Machine Lisp语言使用的面向对象方法。其名字的灵感来源于冰淇淋:冰淇淋店提供基本口味的冰淇淋(香草、巧克力等),然后混合上其它物品(坚果、饼干、巧克力等),称为"mix-in"。

优势

  1. 提供了一种多继承机制,允许多个类使用公共功能,但是没有复杂的多继承语义
  2. 代码重用性
  3. 只继承或使用父类的必要特性,而不是所有特性

多重继承的问题

假设有如下抽象类:

abstract class Performer {
   void perform();
}

两个子类Dancer和Singer:

class Dancer extends Performer {
   void perform() {
      print('Dance Dance Dance ');
   }
}
class Singer extends Performer {
   void perform() {
      print('lalaaa..laaalaaa....laaaaa');
   }
}

假设Dart支持多重继承,有一个Musician类继承了Dancer和Singer。

Note: Dart只支持单继承,此处仅是假设,在C++这种支持多重继承的语言中会出现此种情形

class Musician extends Dancer,Singer {
   void showTime() {
      perform();  
   }
}

当我们调用Musician类的perform()方法时,具体的哪个方法会被执行:

[图片上传失败...(image-d1884a-1563757829589)]

困惑!这就是著名的菱形问题(DDD),多重继承的一个核心问题。

Mixin如何解决菱形问题?

将上面的代码用mixin重写:

class Performer {
   void perform() {
     print('performing...');
   }
}

mixin Dancer {
   void perform() {
     print('Dance...Dance...Dance..');
   }
}

mixin Singer {
   void perform() {
     print('lalaaa..laaalaaa....laaaaa');
   }
}

class Musician extends Performer with Dancer,Singer {
   void showTime() {
     perform();
   }
}

多个Mixin之间是层叠的,而不是平行的,因此不会出现冲突。

为了确定使用mixin时具体的调用方法,可以遵循如下简单的步骤:

[图片上传失败...(image-ae7ee1-1563757829589)]

[图片上传失败...(image-4ef54d-1563757829589)]

然后:

[图片上传失败...(image-11204f-1563757829589)]

[图片上传失败...(image-b5a0e4-1563757829589)]

只继承或使用父类的必要特性,而不是所有特性

这一点也是面向协议编程的优势,需要用心理解。我们拿Mac开发中的例子来说明,只要关注继承层次结构就好,不涉及具体开发知识。

在OC中,button有如下继承层次结构:

protocol NSObjectProtocol { /* ... */ }
open class NSObject: NSObjectProtocol { /* ... */ }
open class NSResponder: NSObject { /* ... */ }
open class NSView: NSResponder { /* ... */ }
open class NSControl: NSView { /* ... */ }
open class NSButton: NSControl { /* ... */ }

NSButtonNSView是典型的继承:在使用NSView的地方都可以使用NSButtonNSButton共享NSView的所有数据和行为。

然而上面的继承结构中,可不仅仅是NSButtonNSView

NSObject类是NSObjectProtocol协议的默认实现,如果协议支持默认实现的话,我们完全可以去掉NSObject这个类,我们并不需要实例化NSObject类来实现什么具体功能。

NSViewNSResponder完全是正交的概念 - 显示和事件。因此,NSView是一个NSResponder并不是那么明显。更重要的是,从理论上说,任何对象只要实现相应的接口并插入到响应链中,就应该可以执行响应链的逻辑。

NSControl更难区分好坏,它提供了一个交互和数据行为混合包,并扩展了NSResponder的一些行为。在某些方面,继承NSControl的确有助于代码重用 - 因此有一种观点认为它的设计是合适的。不过,它是继承层次结构中的一个抽象类(不需要直接实例化),这意味着它作为协议可能会更好 - 可能需要拆分为更细小的协议。

而在Swift中,之前OC中的很多类都可以用协议+默认实现来替代。由于协议不是单继承的,因此可以更灵活地组合。NSButton的继承结构可以这样实现(粗糙版):

protocol NSObjectProtocol { /* ... */ }
protocol NSResponder { /* ... */ }
protocol NSControl { /* ... */ }
protocol NSTwoStateControl: NSControl { /* ... */ }
open class NSView: NSObjectProtocol, NSResponder { /* ... */ }
open class NSButton: NSView, NSTwoStateControl { /* ... */ }

NSObject被拿掉了(通过NSObjectProtocol的默认实现替代),NSResponderNSControl变成了协议,同时NSControl被拆分为一些更简单的协议(更大的功能通过混合可以很容易地实现)。

概况来说,协议/mixin更灵活、更易于组合。

那么,什么时候应该用继承呢?由于mixin/协议的灵活性和强大功能,我们应该将继承限定在:

  1. 想要共享基类的绝大多数行为,并且,
  2. 希望合并基类的所有数据
上一篇下一篇

猜你喜欢

热点阅读