首页图片 未来是移动端的天下,既然学了GO了,再学一门flutter吧!

前言

Dart 是一门面向对象的语言, 所有的类都是继承自 Object , 除了支持传统的 继承、封装、多态, 还有基于组合(Mixin-based)的继承特性, 这个特性也是本文将会重点介绍的地方

继承 子集合拥有父集合的全部属性,子集可以自建其他属性,但是得包含父集的全部属性。
封装 通俗点讲就是拥有自己的函数和返回值,只需要给他东西,返回加工后的成品即可
多态 多态是一个特性,最开始的时候不指定数据类型,运行的时候才指定类型,而且这个类型 可以被其他类型替换。相当于动态变量。

继承·封装·多态参考链接

既然是面向对象的语言, 先让我们来看下如何定义一个类:

1
2
    class ClassName{
    }

通过 class 关键字定义类, 语法和其他主流的语言没有什么差别

1
2
3
4
5
6
class Test{
  void tests(){}
}
void main(){
  new Test().tests();
}

class 表示一个类,每个class 类下面可以定义很多子函数,可以通过 ClassName().VoidName()调用,本例子中使用Test().tests() void 一个普通的没有返回值的经典函数。

class参考链接

有过使用面向对象语言经验的开发者知道, 一般类由成员变量, 成员方法, 构造方法, 静态变量, 静态方法组成

成员变量和静态变量

下面我们来看下如何声明成员变量:

1
2
3
4
5
6
class Point {
  // 声明成员变量 x
  num x;
  // 声明成员变量 y
  num y;
}

成员变量 只能在class类内有效,作用域的问题。

成员变量默认会生成一个隐式的 getter 方法, 并且 非final 的成员变量也会隐式的生成 setter 方法

final 要求就是 其声明的变量在赋值之后就不再改变,它并不要求=的右边是编译时常数,可为函数可为常数可为其他

final static const 参考链接

getter setter 仅仅只是 一个方法,是dart 内置的一中方法,调用方式为 set 和get

除此以外, 开发者还可以使用 getset 关键字实现 gettersetter 方法来创建新的属性, 如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Rectangle {

  num left, top, width, height;
  
  // 构造函数
  Rectangle(this.left, this.top, this.width, this.height);
  提示:请不要在意 Rectangle 是什么 只需要看后面的就好了.【其实就是此类的本身而已】
  // 定义两个属性: right and bottom.
  
  num get right => left + width;
  set right(num value) => left = value - width;
  
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

看懂 Rectangle 类 在class 中将传入的变量 left top width height 等作为参数传入。 箭头函数 => expr 语法是一个针对{ return expr; } 的简写,只能写一行。

箭头函数 参考

本代码重点 set 可以创建新属性,成员变量可以被引用


声明静态变量, 在前面声明成员变量的基础上加上 static 关键字:

static 意味着成员是类变量.static 修饰 *成员(members)* const const 意味着这些对象的所有状态都在编译期就确定了,另外这个对象将会被冻结并且完全不可改变

static const 参考

1
2
3
4
5
6
7
  class Person{
  // 静态变量
  static var count = 0;
    
   // 静态常量
  //static const var count = 0;
   }

成员方法和静态方法

如果定义函数已经在 (三)Flutter学习之Dart函数 介绍过了 类的成员方法就是把以前介绍的方法定义到类里面而已

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Person {
  void sayHello() {
    print("hello...");
  }
}

void main() {
  var p = Person();
  p.sayHello();
}

类的静态方法, 就是在成员方法基础上加上 static 关键字, 访问的时候只需要类名即可而不需要对象, 这个和 Java 是一样的

1
2
3
4
5
6
7
8
9
class Person {
  static void printDesc() {
    print("this is person description...");
  }
}

void main() {
  Person.printDesc();
}

没看懂有没有static的区别? Person.printDesc()与Person().printDesc()的区别.

构造方法

在类中创建一个和类名一样的函数这就是构造函数, 如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
   class Point {
      // 成员变量
      num x, y;

      // 构造方法
      Point(num x, num y) {
        this.x = x;
        this.y = y;
      }
    }

Dart 为我们提供了语法糖来简化上面的代码:

1
2
3
4
5
6
class Point {
  num x, y;
  
  // 声明构造方法, 接受两个参数, 并且将参数分别赋值给对应的变量上
  Point(this.x, this.y);
}

Dart 还为我们提供了 Named constructor, 让代码可读性更高, 让开发者通过名字就知道该构造方法是干什么的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Person {
  String firstName;

  // 声明 Named constructor
  Person.fromJson(Map data) {
    print('in Person');
  }
}

void main() {
  var p = Person.fromJson({});
}

如果想将构造方法声明为 private 的, 加上下划线即可:

1
2
3
4
// 私有构造函数, 只能在当前类可用
Person._fromJson(Map data) {
  print('in Person');
}

构造方法还可以调用另一个构造方法:

1
2
3
4
5
6
7
8
class Point {
  num x, y;

  Point(this.x, this.y);

  // 通过 this 关键字调用其他构造函数
  Point.alongXAxis(num x) : this(x, 0);
}

这里的this this 重定向到主构造函数,传入的x 作为 this 后面接的函数的 参数,不够的自己补齐。正确的理解应该如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
正确的 :this 函数用法
    
重定向构造函数的主体为空,构造函数调用出现在冒号( :)之后 
大意就是在创建类时,我定义一个命名构造函数,但是这个构造函式的主体我不实现。直接通过:调用另外一个构造函数,实现对外界传入的参数的接收并赋值给内部的变量

class Point {
  num x, y;
  //类名构造函数
  Point(this.x, this.y);
  // 命名构造函数
 Point.order(this.x,this.y);
 Point.origin(num a,num b):this.order(a,b);  
 //重定向构造函数,origin构造函数将外界的传值,指向给了order构造函数。
}

重定向构造函数

初始化器列表(Initializer list)

Dart 还支持 初始化器列表(Initializer list), 它在构造方法体执行之前执行, 在构造方法后用 冒号(:) 分隔初始化器列表, 例如:

1
2
3
4
5
 Point.fromJson(Map<String, num> json)
        : x = json['x'],
          y = json['y'] {
      print('In Point.fromJson(): ($x, $y)');
    }

这里的 冒号 这里只有冒号 ,不是冒号this【:this】

由于初始化器列表初始化成员变量操作是在构造方法体执行前执行, 所以初始化器列表中不能使用 this 关键字 在开发期间还可以在初始化器列表中使用断言:

断言 如果表达式值为true,则继续后面的语句,如果值为false,则报异常assert只有在调试模式下生效,生产模式会忽略

断言参考

1
2
3
    Point.withAssert(this.x, this.y) : assert(x >= 0) {
      print('In Point.withAssert(): ($x, $y)');
    }

如果某个类创建对象后不再发生更改, 可以将对象声明为运行时常量, 将对象声明为常量, 需要将类的构造方法声明为常量构造方法

并且所有的成员变量都必须是 final

1
2
3
4
5
    class ImmutablePoint {
      final num x, y;
          
     const ImmutablePoint(this.x, this.y);
    }

如果创建两个常量对象, 实际上他们是一个对象实例:

1
2
3
4
    var a = const ImmutablePoint(1, 1);
    var b = const ImmutablePoint(1, 1);
    // They are the same instance!
    assert(identical(a, b)); 

identical identical通过比较两个引用的是否是同一个对象判断是否相等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    **工厂构造器(Factory Contructor)**
 工厂构造器就构造方法前面使用 `factory` 关键字修饰:

 factory Person(String name){
    }

 其实这不是严格意义上的构造函数, 在其方法体内不能访问 `this`, 只是为了调用该方法的时候就像调用普通的构造方法一样
而不用关心到底是返回了一个新的对象还是缓存的对象. 例如下面一个单例模式的代码:

 class Singleton {
      static final Singleton _singleton = new Singleton._internal();

      factory Singleton() {
        return _singleton;
      }

      // 将默认的构造函数声明为private
      Singleton._internal();
    }

    main() {
      // 就像调用普通的构造方法一样
      var s1 = Singleton();
      var s2 = Singleton();
      print(identical(s1, s2));  // true
      print(s1 == s2);           // true
    }

抽象类(Abstract classes)

抽象类使用 abstract 修饰符来修饰, 抽象类不能被实例化, 如果抽象类里的方法没有方法体, 那么表示该方法是抽象方法:

1
2
3
4
5
6
7
8
  abstract class AbstractContainer {
          // 抽象方法
       void updateChildren();
  
          List getChildren(){
              return container;
          }
        }

接口(interfaces)

定义接口并没有特殊的关键字, 使用 class 关键字来定义接口, 只不过里面的方法都是没有方法体的抽象方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
     
        // A person. The implicit interface contains greet().
        class Person {
         // In the interface, but visible only in this library.
        final _name;
    
          // Not in the interface, since this is a constructor.
          Person(this._name);
		// In the interface.
      String greet(String who) => 'Hello, $who. I am $_name.';
    }

实现接口的时候需要使用 implements 关键字:

1
    class Point implements Comparable, Location {...}

类的继承

不管是继承抽象类还是普通类, 使用 extends 关键字

如果重载父类的方法时, 需要调用父类的方法, 使用 super 关键字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
        class Television {
          void turnOn() {
            _illuminateDisplay();
            _activateIrSensor();
         }
        }
    
        class SmartTelevision extends Television {
          @override
          void turnOn() {
            super.turnOn();
            _bootNetworkInterface();
            _initializeMemory();
            _upgradeApps();
          }
        }

extends VS implements

通过上面介绍我们知道继承一个类使用 extends 关键字, 实现一个接口使用 implements 关键字

如果使用 implements 关键字实现一个非接口的类, 如:

1
2
3
 class A {}

 class B implements A{}

​ 在 Dart 中这是允许的, 在 Javaimplements 关键后面只能放接口 ​
​ 如果 implements 一个非接口类, 需要实现该类的里面的所有方法: ​

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    class A {
      void sayHello(){
        print("My name is A");
      }
    }
    class B implements A{
      @override
      void sayHello() {
        print("My name is B");
      }
    }

mixin 特性

Dart 中的 mixin 特性主要是用来多个层级类的代码重用

Dart 和 Java 一样都是单继承的,也就是说如果要复用多个类的代码,通过继承的方式是行不通的

在 Dart 中通过 with 关键字来实现 mixin 特性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    class Fly{
      void fly(){
        print("flying");
      }
    }

    class Animal{
    }

    class Bird extends Animal with Fly{
    }

    main(){
      Bird().fly();
    }


​ 从中可以看出,我们可以通过非继承的方式来使用 Fly 类的功能 ​
Bird 除了具有 fly 的功能,还有 run 的功能, 我们通过 mixin 多个类来实现: ​

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    class Run {
      void run() {
        print("running");
      }
    }

    class Bird extends Animal with Fly, Run {}

    main() {
      var bird = Bird();
      bird.fly();
      bird.run();
    }

能够被 mixin 的类需要满足一定的条件:

  • 不能有包含默认构造函数

  • 只能继承自 Object

    上面的 Bird 继承了 Animal , 然后 mixin Fly 这个类, 如果 Animal 中也有 fly 方法它会选择执行哪个 fly 方法呢

    1
    2
    3
    4
    5
    
    class Animal {
      void fly() {
        print("Animal flying");
      }
    }

经过测试发现会使用 Fly 类里的 fly 方法, 也就是说 mixin Class 比 extends Class 优先级要高

说人话 with 优先级更高。

如果我们 mixin 多个类, 在这多个类中包含了相同的方法, Dart会选择执行哪个呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    class FlyEnhance{
      void fly() {
        print("fly enhance");
      }
    }

    class Bird extends Animal with Run, Fly, FlyEnhance {}

    main() {
      var bird = Bird();
      bird.fly();
      bird.run();
    }

    //输出结果:
    fly enhance
    running

Fly, FlayEnhance 中都有 fly 方法, 经测试发现, 使用哪个类的 fly 方法取决于 with 后面顺序, 后面的 FlyEnhance 会覆盖前面的 Fly 中的 fly 方法 > 提示 尽量避免重名!!

mixin 多个类的原理分析

上面的代码:

1
class Bird extends Animal with Run, Fly, FlyEnhance {}

底层会创建多个类来实现这个功能, 相当于下面的代码:

1
2
3
4
    class $AnimalRun Animal with Run
    class $AnimalRunFly extends $AnimalRun with Fly
    class $AnimalRunFlyFlyEnhance extends $AnimalRunFly with FlyEnhance
    class Bird extends $AnimalRunFlyFlyEnhance

​ 所以上面的代码会额外创建三个类, 由于 mixin 优先级高于 extends , 所以: ​
​ // 使用 Fly 里的 fly 方法 ​

1
class $AnimalRunFly extends $AnimalRun with Fly  

​ // 使用 FlyEnhance 里的 fly 方法 ​

1
class $AnimalRunFlyFlyEnhance extends $AnimalRunFly with FlyEnhance

​ 这就是 Dart 为什么最终会选择使用 FlyEnhance 而不是 Fly 里的 fly() 方法 ​
​ 如果一个类使用 mixin 特性, 那么这个类的实例也是 mixin 类的子类型: ​

1
2
3
4
5
6
        print(bird is Animal);
        print(bird is Run);
        print(bird is Fly);
        print(bird is FlyEnhance);
    
        //true

​ 如果一个类只用于被 mixin, 可以在声明类的时候使用 mixin 关键字代替 class 关键字 ​ 这个类则不能被继承(extends), 但可以被实现(implements)

还可以通过 on 关键字来指定 mixin 类的需要的父类

1
2
3
4
5
        class SuperA{
        }
    
        mixin A on SuperA{
        }

​ 也就是说类 A, 对 SuperA 有依赖, 那么其他类在 withA 的时候, 必须继承或者实现 SuperA 类 ​

1
2
        class B extends SuperA with A{ 
        }


​ 从中我们可以看出 mixin 机制对复用代码提供了非常大的便利和灵活性 ​
​ 关于 Dart 面向对象就先分析到这里


联盟少侠 阅读总结

首先的感谢 大佬 Chiclaim ,我自己是想不到这么多的,从阅读来看,对面向对象有了一定的基础了解。思路更加清晰了一些!我在其中放入了不少基础的知识,方便自己阅读,同时也方便后来者阅读。我对原理和源代码阅读不敢兴趣,我只需记住怎么使用,会使用就好。

很多东西基础就是基础,阅读这篇文章前后共花费2小时左右,有时候,只待你认真起来世界都为你低头。