Flutter入门-Dart语法2-面向对象

329 阅读10分钟

Dart 是面向对象编程语言,对象都是由类创建的,所有类都是由 Object 类派生出来的子类,除了 Object , 所有类只有一个父类(即只能继承一个父类)

尽管 Dart 语言中一个类只能继承一个父类,但是 Dart 语言提供了 mixin 机制,可以复用多个类,达到类似多继承的效果

类和对象

1)、Dart 没有 public、protected 和 private 等成员访问限定符。默认情况下属性,方法,类等都是共有的,类似 Java 的 public。如果想要表示私有,则以下划线_开头去命名

2)、Dart 中实例化对象和 Java 类似,new 关键字可写可不写

3)、当我们在类中创建私有属性时,我们应该给私有属性提供 getter 和 setter 方法供外界访问:

get 方法语法格式:返回值类型 get 方法名 { 方法体 }

set 方法语法格式:set 方法名 ( 参数 ) { 方法体 }

class Person {
  // 定义类成员属性,默认类的成员属性和方法都是共有的,类似 java 的 public
  var name;
  // 以下划线 ( _ ) 开头命名的属性代表私有成员属性
  var _age;
​
  // 跟类名同名的方法,为构造方法
  // 这里自定义了一个携带参数的构造方法。
  // 如果我们没有自定义构造方法,会自动生成一个不带参数的默认构造方法
  Person(var name, var age) {
    // 因为参数名和类属性名同名,可以使用this引用当前对象
    this.name = name;
    // 可以忽略this关键字,直接引用类成员
    _age = age;
  }
  
  //为 _age 提供 getter 和 setter 方法
  int get age{
    return _age;
  }
  //getter 方法还可以简化为此写法:int get age => _age;
​
  set age(int age){
    _age = age;
  }
​
  // 定一个 public 的方法
  String greet(String who) => 'Hello, $who. I am $name, my age is $_age !';
}
​
void main(){
  var person = Person("erdai",18);
  //下面这句就是调用了 age 的 set 方法
  person.age = 20;
  var greet = person.greet("lucy");
  print(greet);
}
​
//打印结果
Hello, lucy. I am erdai, my age is 20 !

构造方法

如果我们没有自定义一个构造方法,会自动生成一个不带参数的默认构造方法

// 这个类会生成默认的构造方法
class Person {
    String name;
}
​
// 通过默认构造方法实例化对象
var p = Person();

自定义构造方法

class Point{
  var x,y;
  
  Point(var x,var y){
    // 通过this访问成员属性,当然一般除非出现命名冲突,否则可以忽略this
    this.x = x;
    this.y = y;
  }
}

对于构造方法中,简单的赋值操作,Dart语言提供了更简洁的语法,如下:

class Point{
  var x,y;
​
  // 直接将构造方法的第一个参数赋值给this.x, 第二个参数赋值给this.y
  Point(this.x,this.y);
}

初始化参数列表

Dart 还为构造方法提供了 参数初始化列表 的语法,用于初始化对象参数

class Point{
  var x,y;
​
  // 冒号 : 后面的表达式就是参数初始化列表,每个表达式用逗号分隔
  Point(var x,var y): this.x = x,this.y = y{
    // 使用参数初始化列表初始化对象属性,这里如果没有别的初始化工作要做,可以是空的
  }
}

命名构造方法

1)、Dart 可以使用命名构造方法语法,创建多个构造方法,命名构造方法语法格式: 类名.构造方法名(参数列表)

class Point{
  var x,y;
​
  Point(this.x,this.y);
​
  // 命名构造方法 namedConstructor
  Point.namedConstructor(){
    x = 0;
    y = 0;
  }
}
​
void main(){
  // 使用命名构造方法实例化对象
  var point = Point.namedConstructor();
}

上面的例子也可以改写为:

class Point{
  var x,y;
​
  Point(this.x,this.y);
  // 命名构造方法 namedConstructor
  // 这里使用参数初始化列表,直接通过 this 调用上面的构造方法,传入两个参数 0,初始化对象
  Point.namedConstructor():this(0,0);
}

工厂构造方法

1)、Dart 提供了一个特殊的构造方法,类似设计模式中的工厂模式,用来创建对象

2)、factory 构造方法只能访问静态属性和静态成员方法,因此不能访问 this 引用

//1、定义个日志类
class Logger {
  final String name;
  bool mute = false;
​
  // 定义一个私有的_cache属性,用来保存创建好的Logger对象
  static final Map<String, Logger> _cache = {};
​
  // 注意这个构造方法,前面使用了factory关键字修饰,这代表这个构造方法是一个工厂构造方法
  // 工厂构造方法不会每次都创建一个新的Logger对象
  factory Logger(String name) {
    // 根据name判断缓存的Logger对象是否存在
    if (_cache.containsKey(name)) {
      // 返回缓存的Logger对象
      return _cache[name]!;
    } else {
      // 如果没有缓存,则调用命名构造方法_internal创建一个Logger对象
      final logger = Logger._internal(name);
      // 根据name缓存logger
      _cache[name] = logger;
      // 返回新的Logger对象
      return logger;
    }
  }
​
  // 注意这个是一个私有的命名构造方法。
  Logger._internal(this.name);
​
  void log(String msg) {
    if (!mute) print(msg);
  }
}
​
//2、测试
void main(){
  var logger = Logger("erdai");
  logger.log(logger.name);
}
​
//打印结果
erdai

静态成员

跟 TS 一样,使用 static 来声明静态成员。

class Rect {
  static int height = 10;
  static int width = 10;
  static getArea() {
    print(height * width);
  }
}

void main(List<String> args) {
  Rect.getArea();
}

有两点需要注意:

  • 静态成员不能访问实例变量

    dart
    复制代码
    class Rect {
      int height = 10;
      static int width = 10;
      static getArea() {
        print(this.height * width); // 报错了 不能访问 实例属性 height
      }
    }
    
  • 实例方法可以访问静态成员

    class Rect {
      int height;
      static int width = 10;
      Rect(this.height);
      getArea() {
        print(this.height * width);// 如果访问实例属性,推荐加上 this。
      }
    }
    
    void main(List<String> args) {
      new Rect(10).getArea();
    }
    

继承和多态

继承

构造函数不能被继承,使用 extendssuper 关键字来继承父类的属性和方法。

  • 纯继承父类
class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal(this.name);
}

class Dog extends Animal {
  Dog([String name = 'dog']) : super(name);
}

void main(List<String> args) {
  var dog = new Dog();
  print(dog.name); // dog
  dog.sound('汪汪'); // 汪汪
}

其中Dog([String name = 'dog']) : super(name);需要解释一下:

  • : super(name)这种语法是用初始化列表在构造 Dog 时调用其父类的构造函数来设置 name

  • Dog([String name = 'dog'])这种语法是调用new Dog()name 是可选的,默认值为dog

  • 扩展子类的属性和方法

class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal.create(this.name);
}

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog']) : super.create(name);
  void run() {
    print('${this.name} runrun');
  }
}
  • 重写父类的属性和方法
class Animal {
  String name;
  void sound(voice) {
    print(voice);
  }

  Animal.create(this.name);
}

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog']) : super.create(name);
  void run() {
    print('${this.name} runrun');
  }

  @override
  void sound(voice) {
    print('${this.name} $voice');
  }
}

void main(List<String> args) {
  var dog = new Dog('雄');
  print(dog.name); // dog
  dog.sound('汪汪'); //dog 汪汪
}

推荐使用@override来重写父类的属性和方法

  • 子类中调用父类的方法

通过 super 来调用父类的方法

class Dog extends Animal {
  String sex;
  Dog(this.sex, [String name = 'dog']) : super.create(name);
  void run() {
    super.sound('汪汪');
    print('${this.name} runrun');
  }
}

抽象类与多态

1)、抽象类就是不能实例化的类,通过 abstract 关键字声明

2)、抽象方法就是没有实现的方法,Dart 中的抽象方法不能用 abstract 声明,Dart 中没有方法体的方法就称为抽象方法

3)、继承抽象类,子类必须要实现所有抽象方法,否则会报错

// 使用 abstract 关键字修饰的类,就是抽象类
abstract class Doer{
  // 抽象类跟普通类一样,可以定义成员变量,成员方法。
  String name = "";
  // 定义个抽象方法,这个方法我们没有实现具体的功能
  void doSomething();
}
​
// 继承抽象类 Doer
class EffectiveDoer extends Doer{
  // 实现抽象类的抽象方法
  @override
  void doSomething() {
    print('doSomething');
  }
}
​
void main(){
  var doer = EffectiveDoer();
  doer.doSomething();
  doer.name = "erdai";
  print(doer.name);
}
​
//打印结果
doSomething
erdai

接口

1)、Dart 中的接口没有使用 interface 关键字定义,而是普通类和抽象类都可以作为接口被实现。但是一般都是用抽象类来定义接口

2)、子类通过 implements 来实现接口

3)、默认情况每一个类都隐含一个包含所有公有成员(属性和方法)的接口定义

abstract class Fruit{
  // 包含在隐式接口里面
  String name = "";
  
  // 构造方法不包含在隐式接口里面
  Fruit(this.name);
  
  // 包含在隐式接口里面
  void eat();
}
​
class Apple implements Fruit{
  @override
  String name = "苹果";
​
  @override
  void eat() {
    print('吃$name');
  }
}
​
void main(){
  var fruit = Apple();
  fruit.eat();
}
​
//打印结果
吃苹果

实现多线接口

和Java中一样,dart中继承是单继承,但是实现接口是可以实现多个接口的(抽象类)

abstract class A {
  late String name;
  getA();
}

abstract class B {
  getB();
}

class C implements A, B {
  @override
  getA() {}

  @override
  getB() {}

  @override
  late String name;
}

多态

多态就是同一操作作用于不同的对象时,可以产生不同的解释和不同的效果。Dart中的多态是通过子类重写父类定义的方法,这样每个子类都有不同的表现。 使用抽象类的话就只需要定义父类的方法而不用实现,让继承它的子类去实现,每个子类就是多态的。

abstract class Animal {
  sound(); // 抽象方法
}

class Dog extends Animal {
  @override
  sound() {
    print('汪汪');
  }

  run() {}
}

class Cat extends Animal {
  @override
  sound() {
    print('喵喵');
  }

  run() {}
}

void main(List<String> args) {
  var dog = new Dog();
  var cat = new Cat();
  print(dog.sound());
  print(cat.run());
  // 下面两个不能调 run 方法
  Animal _dog = new Dog();
  Animal _cat = new Cat();
}

minxins 混入

为什么需要minxins

其实,使用vue的各位大佬们对mixins应该不陌生,当然在vue3中使用组合式API进行了变相的替换,但是mixin这种思路是没有问题的,其实这也是现在编程语言的两种设计思路,既组合和继承,minxins显然是属于组合的。 对于上文所说的继承与多态,假设我们一个类——Animal,它有三个子类——MammalBirdFish,而这三个类也有其对应的子类,以及相应的方法,那么如下的实体用继承的方法实现将会变得非常冗余,如下:

image.png

//Dart代码
class Animal {...}
class Mammal extends Animal {...}
class Bird extends Animal {...}
class Fish extends Animal {...}
class Dolphin extends Mammal {...}
class Bat extends Mammal {...}
class Cat extends Mammal {...}
class Dove extends Bird {...}
class Duck extends Bird {...}
class Shark extends Fish {...}
class FlyingFish extends Fish {...}

//行走行为
abstract class Walk{
  walk();
}
//游泳行为
abstract class Swim{
  swim();
}
//飞翔行为
abstract class FLying {
  flying();
}
//海豚可以游泳
class Dolphin extends Mammal implements Swim {
    @Override
    public void swim() {...}
}
//蝙蝠可以飞、行走
class Bat extends Mammal implements Flying,Walk {
    @Override
    void walk() {...}

    @Override
    void flying() {...}
}
//猫可以行走
class Cat extends Mammal implements Walk {
    @Override
    void walk() {...}
}
//鸽子可以行走、飞
class Dove extends Bird implements Walk,Flying {
    @Override
    void walk() {...}

    @Override
    void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird implements Walk,Flying,Swim {
    @Override
    void swim() {...}

    @Override
    void walk() {...}

    @Override
    void flying() {...}
}
//鲨鱼可以游泳
class Shark extends Fish implements Swim {
    @Override
    void swim() {...}
}
//飞鱼可以游泳、飞
class FlyingFish extends Fish implements Swim,Flying {
    @Override
    void swim() {...}

    @Override
    void flying() {...}
}

``WalkSwimFlying。由于这些行为并不是所有类通用的,所以不能将这些行为放在父类, 所以每个类都得实现一遍,但是代码是一样的!!!

mixins 用法

下面将上面的继承关系用mixins实现将会变得简洁很多

//行走
mixin Walker{
  void walk(){...}
}
//游泳行为
mixin Swim{
  void swim(){...}
}
//飞翔行为,由于这个是抽象方法,所以必须在要实现,不能调用super.flying()
mixin Flying {
  void flying();
}
//海豚可以游泳
class Dolphin extends Mammal with Swim{
  @override
  void swim() {
    super.swim();
  }
}
//蝙蝠可以飞、行走
class Bat extends Mammal with Flying,Walk{
  @override
  void flying() {...}
  //覆盖Walk类中的walk方法
  @override
  void walk() {
    super.walk();
  }
}
//猫可以行走,这里没有重写Walk中的方法
class Cat extends Mammal with Walk{}
//鸽子可以行走、飞
class Dove extends Bird with Flying,Walk{

  @override
  void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird with Walk,Flying,Swim{
  @override
  void flying() {...}

  @override
  void walk() {...}
}
//鲨鱼可以游泳
class Shark extends Fish with Swim{...}
//飞鱼可以飞及游泳
class FlyingFish extends Fish with Flying,Swim{
  @override
  void flying() {...}
}

mixins线性化

在上面的示例中,我们发现with关键字后有多个类。那么这里就产生了一个问题——如果with后的多个类中有相同的方法,那么当调用该方法时,会调用哪个类里的方法尼

  • 如果当前使用类重写了该方法,就会调用当前类中的方法。
  • 如果当前使用类没有重写了该方法,则会调用距离with关键字最远类中的方法
//BindingBase
abstract class BindingBase {
  void initInstances() {
    print("BindingBase——initInstances");
  }
}
//GestureBinding
mixin GestureBinding on BindingBase{
@override
void initInstances() {
  print("GestureBinding——initInstances");
  super.initInstances();
}
}
//RendererBinding
mixin RendererBinding{
@override
void initInstances() {
  print("RendererBinding——initInstances");
  super.initInstances();
}
}
// WidgetsBinding
mixin WidgetsBinding on BindingBase
{

@override
void initInstances() {
  print("WidgetsBinding——initInstances");
  super.initInstances();
}
}
//WidgetsFlutterBinding
class WidgetsFlutterBinding extends BindingBase
    with GestureBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    return WidgetsFlutterBinding();
  }
}
//main
main(List<String> arguments) {
  var binding = WidgetsFlutterBinding();
  binding.initInstances();
}

image.png 可以看到,最终调用的是WidgetsBinding.initInstances(), 我们在WidgetsBindingRendererBindingGestureBinding中都调用了父类的initInstances方法,也因此会一级一级往上调用

将其最终实现用一张图表示如下

class S {
  fun()=>print('A');
}
mixin MA {
  fun()=>print('MA');
}
mixin MB {
  fun()=>print('MB');
}
class A extends S with MA,MB {}
class B extends S with MB,MA {}

image.png

Dart 中的 mixin 通过创建一个类来实现,该类将 mixin 的实现层叠在一个超类之上以创建一个新类,因此而实现了一条继承链,如上图所示。

下面再看一个例子

class S { fun()=>print('A'); }
mixin MA on S {
  fun() {
    super.fun();
    log();
    print('MA');
  }

  log() {
    print('log MA');
  }
}
mixin MB on S {
  fun() {
    super.fun();
    print('MB');
  }

  log() {
    print('log MB');
  }
}

class A extends S with MA,MB {}
A a = A();
a.fun();

输出结果如下

A
log MB
MA
MB

仔细思考一下,按照上面的工作原理,在 mixin 的继承链建立的时候,最后声明的 mixin 会把后声明的 minxin 的函数覆盖掉。这时候即使我们从代码逻辑中认为在 MA 中调用了 log 函数,实际上这时候 A 类中的 log 函数已经被 MB 给覆盖了。所以最终,log 函数调用的是 MB 中的 log 函数逻辑

注意事项

  • 被 mixins 的类只能继承自 Object,不能继承其他类。

    class A {
      void getA() {}
    }
    
    class B extends A { 
      void getB() {}
    }
    
    class C with A, B {} // ❌报错,B 是被 mixins 的类,不能继承
    

    为了让 mixins 类更加直观,推荐使用 mixin 关键字来定义 mixin

    mixin A {
      void getA() {}
    }
    
    mixin B extends A { // ❌报错,B 是被 mixins 的类,不能继承
      void getB() {}
    }
    
    class C with A, B {} 
    
  • 被 mixins 的类不能有构造函数

    mixin A {
      void getA() {}
    }
    
    mixin B {
      B(); // ❌报错 B 是被 mixins 的类,不能有构造函数
      void getB() {}
    }
    
    class C with A, B {} 
    
  • 一个类可以 mixins 多个 mixins 类

  • 一个类可以继承某个类再 mixins 一些 mixins 类

    class A {
      void getA() {}
    }
    
    class B {
      void getB() {}
    }
    
    class C extends A with B {}
    
  • mixins 不是继承,也不是接口,当使用 mixins 后,相当于创建了一个超类,能够兼容下所有类

    class A {
      void getA() {}
    }
    
    mixin B {
      void getB() {}
    }
    
    class C extends A with B {}
    
    void main(List<String> args) {
      var c = new C(); 
      print(c is A);// true
      print(c is B);// true
      print(c is C);// true
    }
    
  • 使用 on 关键字可以指定哪些类可以使用该 Mixin 类

    class A {
      void getA() {}
    }
    
    mixin B on A {
      void getB() {}
    }
    
    // class C with B {}     ❌这样写是报错的
    class C extends A with B {}
    

泛型

跟 TS 一样,Dart 也支持泛型,泛型就是泛用的类型,是一种将指定权交给用户的不特定类型。

比如下面的函数就由用户指定传入的类型。

  T getData<T>(T data) {
    return data;
  }

// 调用者可以指定类型
  getData<String>('123');
  getData<num>(123);
	getData<List>([1, 2, 3]);

泛型类

在实例化一个类时可以通过泛型来指定实例对象的类型。

下面就是实例化 List 后指定了List 对象属性值的类型。

  List l1 = new List<int>.filled(2, 1);
  List l2 = new List<String>.filled(2, '');
  • 定义泛型类

    class A<T> {
      T age;
      A(T this.age);
      T getAge() {
        return this.age;
      }
    }
    
  • 使用泛型类

    void main(List<String> args) {
      // 使用泛型类
      var a = new A<int>(12);
      var b = A<String>('12');
    }
    

泛型接口

泛型接口的定义方式就是接口跟泛型类的集合体,可以这么定义

// 泛型接口
abstract class Cache<T> {
  void setKey(String key, T value);
}
// 类匹配这个接口
class FileCache<T> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}

class MemoryCache<T> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}

使用时指定泛型的具体类型

  var f = new FileCache<String>();// 指定 String
  f.setKey('key', 'string');
  var m = new MemoryCache<int>();// 指定 int
  m.setKey('key', 123);

限制泛型

跟 Typescript 一样,泛型约束使用 extends 关键字。

abstract class Cache<T> {
  void setKey(String key, T value);
}
// 这里约束MemoryCache只能为 int
class MemoryCache<T extends int> implements Cache<T> {
  @override
  void setKey(String key, T value) {}
}
void main(List<String> args) {
  // var m = new MemoryCache<String>(); 这里就不能是 String 类型了
  var m = new MemoryCache<int>();
  m.setKey('key', 123);
}