从 Java/Kotlin 中学习 Dart 的 extends,implements,mixin 概念
前言
Flutter开发这么久了,其实 Dart 语法我并没有系统的学习过,一些概念还是比较模糊,只专注于业务代码,一些基础语法也就是依葫芦画瓢,甚至连继承、实现、混入等概念都一知半解。
今天就学习一下 extends,implements 到底怎么使用,使用方式和Java/kotlin有什么异同呢?
Dart独有的混入概念 Mixin 有什么魔法?Java/Kotin 有类似的实现方式吗?
从 Java/Kotlin 中学习 Dart 语法,这样对于学习掌握一门语言有很大的帮助,他山之石可以攻玉。
话不多说,直接开始吧!

一、extends,implements 与Java/Kotlin 相同
是的,在 Dart、Java 和 Kotlin 中,extends 和 implements 关键字的基本概念是相似的,遵守继承和接口实现的通用面向对象原则。
甚至在 Dart 中的关键字使用方式都与 Java 类似,只是在 Java 中我们定义接口是 interface 而在 Dart 中我们定义接口的方式是 class,当然也可以是 abstract class。
这样就导致 Dart 中的类既可以集成这个类,又能实现这个类,这也是他们的区别,更加的灵活。
例如我们定义一个Class,它既可以是接口,也可以是类。
javascript
class Animal {
String getName() {
return "animal";
}
}
我们也能定义抽象类:
csharp
abstract class Flyer {
void fly();
}
abstract class Swimmer {
void swim();
}
我们定义一个类来继承类与实现接口:
scala
class Dark extends Animal implements Swimmer, Flyer {
@override
void swim() {
print("implements 之后 我就要自己实现游泳了");
}
@override
void fly() {
print("implements 之后 我就要自己实现飞翔了");
}
}
可以看到 Animal 是被继承的,它的方法是不需要重写的,但是当我们实现 Animal 接口的时候则需要重写内部的方法:

实现之后:
typescript
class Dark implements Swimmer, Flyer, Animal {
@override
void swim() {
print("implements 之后 我就要自己实现游泳了");
}
@override
void fly() {
print("implements 之后 我就要自己实现飞翔了");
}
@override
String getName() {
// TODO: implement getName
throw UnimplementedError();
}
}
至于继承与实现的区别?这不需要我多说吧,大家肯定都懂的啦!
二、mixin 概念类比Kotlin委托
mixin 是 Dart 特有的概念,允许代码重用而不需要继承,与 Kotlin 的委托的用法简直是如出一辙。
例如我们定义了一个游泳的接口类,并且我们定义一个 mixin 类去预实现了:
csharp
abstract class Swimmer {
void swim();
}
mixin SwimmerMixin implements Swimmer {
@override
void swim() {
print('Mixin中实现了真正的游泳');
}
}
那么在使用的时候我们就能使用 with 关键字加入我们预实现的 Mixin 对象了:
scala
class Dolphin with SwimmerMixin {
void doSwim() {
swim(); //这里就是直接调用 SwimmerMixin 中的 swim 方法了
}
}
如果你还是不明白我用 Kotlin 的委托实现一次,一看就懂!
kotlin
interface ISwimmer {
fun swim()
}
class SwimmerImpl : ISwimmer {
override fun swim() {
YYLogUtils.w("Impl中实现了真正的游泳")
}
}
使用Kotlin委托:
kotlin
class Dolphin : ISwimmer by SwimmerImpl() {
fun doSwim() {
swim()
}
}
你就是说是不是一模一样吧!
什么?mixin 可以混入多个对象?你就说Kotlin 委托行不行吧!
csharp
abstract class Swimmer {
void swim();
}
mixin SwimmerMixin implements Swimmer {
@override
void swim() {
print('Mixin中实现了真正的游泳');
}
}
abstract class Flyer {
void fly();
}
mixin FlyerMixin implements Flyer {
@override
void fly() {
print('Mixin中实现了真正的飞翔');
}
}
我们现在来一个海燕,又能游又能飞,直接混入多个类。
scala
class Tern with SwimmerMixin, FlyerMixin {
void doSiwm() {
swim();
}
void doFly() {
fly();
}
}
没毛病,但是 Kotlin 的接口委托是一样的逻辑啊,甚至他们连思路都是一致的都是在实现类中已经预实现了,这边直接调用即可。
kotlin
interface ISwimmer {
fun swim()
}
class SwimmerImpl : ISwimmer {
override fun swim() {
YYLogUtils.w("Impl中实现了真正的游泳")
}
}
interface IFlyer {
fun fly()
}
class FlyerImpl : IFlyer {
override fun fly() {
YYLogUtils.w("Impl中实现了真正的飞翔")
}
}
使用的时候也能分别对实现的接口进行委托:
kotlin
class Tern : IFlyer by FlyerImpl(), ISwimmer by SwimmerImpl() {
fun doFly() {
fly()
}
fun doSwim() {
swim()
}
}
尽管在语言成面,在使用的方面他们有一些差异,比如 mixin 除了可以混入方法行为,还能混入字段、状态、构造器等,当然这些不常用。
但是让他们的思路可以说是基本类似了,并且看起来用 with mixin 的方法比委托写起来更加简便。
三、Java用设计模式实现仿mixin效果
在 Java 中没有直接等价于 Dart 的 mixin 或 Kotlin 的委托模式的特性,相对比较"低级",但是我们一样可以通过设计模式的方式间接的实现类型 mixin 的效果。
下面列出几种可用的设计模式和策略:
组合模式
组合模式允许你构造一个对象结构来表示部分-整体的层次结构。组合中的单个对象和对象的组合应该被一致地处理。你可以在一个类中组合多个对象,并将调用委托给这些对象:
csharp
public interface Swimmer {
void swim();
}
public class SwimmerImpl implements Swimmer {
@Override
public void swim() {
System.out.println("Swimming");
}
}
public interface Flyer {
void fly();
}
public class FlyerImpl implements Flyer {
@Override
public void fly() {
System.out.println("Flying");
}
}
public class Tern {
private Swimmer swimmer = new SwimmerImpl();
private Flyer flyer = new FlyerImpl();
public void doSwim() {
swimmer.swim();
}
public void doFly() {
flyer.fly();
}
}
策略模式
策略模式定义了一系列的算法,并将每个算法封装起来,使它们可以互相替换,并使算法的变化独立于使用算法的客户。这样,你可以根据需要动态地替换算法:
csharp
public class Tern {
private Swimmer swimmer;
private Flyer flyer;
public Tern(Swimmer swimmer, Flyer flyer) {
this.swimmer = swimmer;
this.flyer = flyer;
}
public void doSwim() {
swimmer.swim();
}
public void doFly() {
flyer.fly();
}
}
如果要模仿 Dart 的 mixin 或 Kotlin 的委托模式,组合模式和策略模式更为适合,因为它们可以让你简单地将一个类的行为委托给另一个类的实例。重要的是,Java 要求这种类型的委托必须是显式的,即在类中通过持有引用来实现,这与 Kotlin 和 Dart 的隐式委托或混入相比,更为繁琐和不透明。
四、mixin 的限制符 on 的用法
在 Dart 中,mixin 有一个可选的限制器 on,用于指定一个或多个类作为该 mixin 可以混入的目标,指定 on 关键字后面的类是为了限制 mixin 的适用范围,这等于是声明了一个约束:只有那些继承自指定类的类,或者是指定类本身,才能使用这个 mixin。
这样的限制可以防止 mixin 被错误地应用到没有提供必要方法或属性的类,编译时会捕获这些错误。
例如我们修改代码如下:
dart
mixin SwimmerMixin on Animal implements Swimmer {
@override
void swim() {
print('Mixin中实现了真正的游泳');
}
}
mixin FlyerMixin on Animal implements Flyer {
@override
void fly() {
print('Mixin中实现了真正的飞翔');
}
}
我们限制了 FlyerMixin 的混入只有在 Animal类或其继承类才能生效。
scala
class Tern extends Animal with SwimmerMixin, FlyerMixin {
void doSiwm() {
swim();
}
void doFly() {
fly();
}
@override
String getName() {
return "Tern";
}
}
mixin 使用 on 关键字明确指定了要混入的类必须是某个特定类的子类。为了让 SwimmerMixin 和 FlyerMixin 能够被 Tern 类混合,Tern 必须是 Animal 的子类。也就是说具体的逻辑没有发生任何变化,只是加上了限制条件而已。
总结
在 Flutter SDK 中,mixin 的使用非常广泛。它们通常用于在类之间共享通用的行为,而不需要成为类层次结构的一部分。Flutter framework 使用 mixin 来提供特定功能,如动画、手势处理等,并且 Flutter 已经帮我们实现好了,这样开发者可以将这些功能混入到他们自己的类中直接使用而不影响类继承结构,可以说是相当的方便。
常见的比如 TickerProviderStateMixin(动画控制器),AutomaticKeepAliveClientMixin(页面间切换时保持状态)等。
在平常的开发中,我们也可以在多种场景下使用 mixin,尤其是当你希望在多个类之间共享通用功能而不创建严格的继承关系时。
例如定义保存数据到本地的逻辑,我们的混入类就可以实现本地存储的逻辑。
例如我们修改 Flutter App 的状态栏与颜色,由于都是一些固定代码我们就能定义mixin类,那么我们就能在想要改变状态的页面混入这部分功能,也是比较方便。
例如我后期讲到的基于 GetxController 的指定混入 CancelToken 处理逻辑的场景等等,敬请期待。
总之理解和掌握 Dart 的单继承、多实现、多混入这些概念对于写代码和阅读代码都有很大的帮助。
那么本期内容就到这里,如讲的不到位或错漏的地方,希望同学们可以评论区指出。
由于代码比较简单,本文全部贴出,如果感觉本文对你有一点点点的启发,还望你能点赞
支持一下,你的支持是我最大的动力啦。
Ok,这一期就此完结。
