2.3、Dart語言基礎(chǔ):面向?qū)ο?/h2>

學(xué)習(xí)筆記,旨在于快速入門和學(xué)習(xí)Dart,其中可能會(huì)有理解錯(cuò)誤,請(qǐng)指出,一起學(xué)習(xí)。

系列文章

2.1、Dart語言基礎(chǔ):變量、運(yùn)算符
2.2、Dart語言基礎(chǔ):函數(shù)與閉包
2.3、Dart語言基礎(chǔ):面向?qū)ο?/a>
2.4、Dart語言基礎(chǔ):異步
2.5、Dart語言基礎(chǔ):庫與包
...

零、概述

Dart語言在面向?qū)ο缶幊烫峁┝祟?code>Class、繼承Inheritance、混合Mixins、接口和抽象類Interfaces and abstract classes,還有泛化generics等。

泛化在業(yè)務(wù)開發(fā)層面用到的比較少,本篇章介紹前面幾個(gè)概念,后續(xù)專門一章節(jié)介紹泛化。

包括以下內(nèi)容:
1、類的定義 和 繼承,成員變量、成員方法,構(gòu)造器。
2、支持類的靜態(tài)化,即支持類方法和類變量。
3、支持抽象類Abstract Class。
4、支持實(shí)現(xiàn)Implements、擴(kuò)展Extension、混淆mixins。


一、類 與 繼承

  • Dart中萬物皆為對(duì)象,所有的類都繼承于 Object基類(null除除外)。
  • 類聲明關(guān)鍵字class,繼承關(guān)鍵字extends。
  • 關(guān)鍵字this,指向當(dāng)前對(duì)象。
    Dart規(guī)范是忽略this的,只有在存在命名沖突的情況下才需要用this。
  • 關(guān)鍵字super,指向父類對(duì)象,顯式的調(diào)用父類的方法。

1、類的聲明 和 成員變量的訪問

class Pet {
  String? name;
  int weight = 0;
  void saySomething() {
    print('name is $name');
  }
}

  // 0、默認(rèn)的無參數(shù)構(gòu)造器
  var pet = Pet();
  pet.name = "gigi";

  // 1、訪問實(shí)例屬性:gigi
  print(pet.name);
  // 2、調(diào)用實(shí)例方法:name is gigi; weight = 0
  pet.saySomething();
  // 3、對(duì)象的類型是:Pet
  print('對(duì)象的類型是:${pet.runtimeType}');
  • 語法格式:關(guān)鍵字class。
  • 使用 操作符. 訪問 成員變量 和 成員方法。
  • .runtimeType 獲取對(duì)象類型。
  var p2;
  // 1、null,p2為空則返回null
  print(p2?.name);
  • 為了避免左操作符為空導(dǎo)致的異常,可以使用操作符?. 。

2、成員變量

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}
  • 未初始化的成員變量,默認(rèn)為null。
  • 非延遲的成員變量 在類對(duì)象被創(chuàng)建的時(shí)候,已經(jīng)賦值。
2.1、final 成員變量
  • 不管任何場(chǎng)景下,只會(huì)被初始化一次。
  • 使用構(gòu)造器或者初始化列表初始化final成員變量。
class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

3、構(gòu)造函數(shù)和析構(gòu)函數(shù)

  • 由于Dart支持自動(dòng)垃圾回收機(jī)制,因此無需手動(dòng)寫析構(gòu)函數(shù)。

3.1、未命名構(gòu)造器 和 命名構(gòu)造器

class Point {
  double? x;
  double? y;
}
  
  // 1、null,null
  var p4 = new Point();
  print("${p4.x}, ${p4.y}"); 
  • 如果沒有提供構(gòu)造器,則會(huì)提供沒有參數(shù)的默認(rèn)未命名構(gòu)造器。
class Point {
  double? x;
  double? y;

  // 1、帶參數(shù)的未命名構(gòu)造器
  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  // 語法糖,等價(jià)于上面語法
  Point(this.x, this.y);
  
  // 2、命名構(gòu)造器ClassName.identifier
  Point.fromJson({x=0, y=0}) {
    this.x = x;
    this.y = y;
  }
}

void class_demo_2() {
  var p1 = new Point(6, 8);
  print("${p1.x}, ${p1.y}");

  var p2 = new Point.fromJson(x:10.0, y: 10.0);
  print("${p2.x}, ${p2.y}"); 
}
  • 構(gòu)造函數(shù)的名稱 可以是 類名本身,或者 命名構(gòu)造器類名.方法名
    Constructor names can be either ClassName or ClassName.identifier
class Point {
  double? x;
  double? y;
  
  // 聲明帶參數(shù)的未命名構(gòu)造器
  Point(this.x, this.y);

  // Error: 'Point' is already declared in this scope.
  Point();
}
class Rect extends Point {
}

// 2、構(gòu)造器無法被繼承。Error: Too many positional arguments: 0 allowed, but 2 found.
var p3 = Rect(6, 8);
  • 未命名構(gòu)造器,即ClassName,有且只有一個(gè),不管參數(shù)多少。
    否則編譯報(bào)錯(cuò)Error: 'Point' is already declared in this scope.
  • 父類構(gòu)造器無法被子類繼承。
    如上Point(this.x, this.y); Rect無法使用。
  • 如果父類定義了 帶參數(shù)的未命名構(gòu)造器,則子類無法使用默認(rèn)構(gòu)造器。
    否則編譯報(bào)錯(cuò)Error: The superclass, 'Point', has no unnamed constructor that takes no arguments.

3.2、繼承鏈中的構(gòu)造器

  • 繼承鏈中,構(gòu)造器的調(diào)用順序
    1、初始化列表,initializer list
    2、父類的未命名無參數(shù)構(gòu)造器,superclass’s no-arg constructor
    3、子類的未命名無參數(shù)構(gòu)造器,main class’s no-arg constructor

  • 子類 需要手動(dòng)調(diào)用 父類構(gòu)造器。

class Point {
  double? x;
  double? y;
  
  Point(this.x, this.y);

  Point.fromMap(Map data) : x=data['x'], y=data['y'] {

  }
}

class Rect extends Point {
  Rect(double x, double y) : super(x, y);

  Rect.fromMap(Map data) : super.fromMap(data) {

  }
}

void class_demo_2() {
  var p3 = Rect(10, 10);
  print("${p3.x}, ${p3.y}");

  var p5 = Rect.fromMap({'x':20.0, 'y':20.0});
  print("${p5.x}, ${p5.y}");
}
  • 初始化列表作用:調(diào)用其他構(gòu)造器、簡(jiǎn)單語句執(zhí)行等
class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(this.x, this.y);
  
  // 1、簡(jiǎn)單的計(jì)算語句
  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
  // 2、簡(jiǎn)單的判斷語句
  Point.withAssert(this.x, this.y) : assert(x >= 0) {
  }
  // 3、簡(jiǎn)單的賦值語句
  Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! {
  }
  
  // 4、重定向構(gòu)造器
  Point.alongXAxis(double x) : this(x, 0);
}

3.3、常量構(gòu)造器,constant constructors

  • 常量構(gòu)造器在編譯期已決議,并且所有的變量必須為final類型。
class ImmutablePoint {
  final double x, y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
  const ImmutablePoint(this.x, this.y);
}
// 實(shí)例化
var p = const ImmutablePoint(2, 2);
  • 構(gòu)建兩個(gè)完全相同的常量類,只會(huì)創(chuàng)建一個(gè)常量類,多次調(diào)用值是相同的。
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

// They are the same instance!
assert(identical(a, b)); 

常量構(gòu)造器的初始化

常量構(gòu)造器并不總是創(chuàng)建常量對(duì)象。 Constant constructors don’t always create constants.

  • 常量構(gòu)造器 調(diào)用前 必須使用const,才能得到常量對(duì)象。
    If a constant constructor is outside of a constant context and is invoked without const, it creates a non-constant object:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!
  • 在 "常量上下文" 中,在構(gòu)造器或字面量前面可以忽略const關(guān)鍵字。
    Within a constant context, you can omit the const before a constructor or literal.
// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

// 等價(jià)于
// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

3.4、工廠構(gòu)造器(Factory constructors),關(guān)鍵字factory

主要用途:
  • 1、創(chuàng)建 不返回新實(shí)例對(duì)象的構(gòu)造器。例如,內(nèi)存緩存中的實(shí)例對(duì)象、返回對(duì)象的子類型。
    Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class.

  • 2、初始化類的 final 屬性變量 。

class Logger {
  final String name;
  bool mute = false;

  // 內(nèi)部存儲(chǔ)
  static final Map<String, Logger> _cache = <String, Logger>{};

  // 內(nèi)部 命名構(gòu)造器
  Logger._internal(this.name);

  factory Logger(String name) {
    // 存在key就獲取值,不存在則添加到map, 然后返回值
    // 1、在構(gòu)造器函數(shù)內(nèi),初始化final成員變量。
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  void log(String msg) {
    if (!mute) print(msg);
  }
}

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
  • 在構(gòu)造器函數(shù)內(nèi),初始化final成員變量。
    If you need to assign the value of a final instance variable after the constructor body starts.
  factory Logger(String name) {
    // 存在key就獲取值,不存在則添加到map, 然后返回值
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }
  • 工廠構(gòu)造器無法訪問this關(guān)鍵字。
    Factory constructors have no access to this.

4、成員方法

  • 4.1、運(yùn)算符方法,語法:operator 運(yùn)算符
    運(yùn)算符 是特殊名稱的實(shí)例方法。Operators are instance methods with special names.
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
  • 4.2、Getters and setters方法,關(guān)鍵字get\set
    提供簡(jiǎn)單便捷的方式,支持簡(jiǎn)單計(jì)算,類似Swift的計(jì)算屬性功能。
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

5、類繼承

5.1、關(guān)鍵字extends、super
class Television {
  void turnOn() {

  }
}

class SmartTelevision extends Television {
  void turnOn() {
    // 調(diào)用父類方法
    super.turnOn();
  }
}
5.2、重寫方法,關(guān)鍵字@override
class SmartTelevision extends Television {
  @override
  void turnOn() {
    ...
  }
}
  • 可以重寫的方法,包括實(shí)例方法、操作符方法、getter/setter方法。
  • 重寫的方法必須滿足一下幾點(diǎn)
    1、重寫方法的返回值的類型,必須和原方法一樣(或是其類型的子類)。
    2、重寫方法的參數(shù)類型,必須和原方法一樣(或是其類型的子類)。
    3、重寫方法的位置參數(shù)個(gè)數(shù),必須和原方法的位置參數(shù)個(gè)數(shù)一樣。
    4、泛型方法和非泛型的方法無法相互轉(zhuǎn)化,必須同為泛型方法或者都不是。
  • 捕獲調(diào)用類中不存在的方法或者變量,可以重寫void noSuchMethod(Invocation invocation)方法。
class HelloClass {
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ${invocation.memberName}');
  }
}

二、類方法和類變量(Class variables and methods)

  • 聲明類方法和類變量,必須在前面使用關(guān)鍵字static。

1、類變量(靜態(tài)變量)

// 類變量
class Queue {
  static const initialCapacity = 16;
}
void main() {
  assert(Queue.initialCapacity == 16);
}
  • 類變量:適用于 類范圍(class-wide) 內(nèi)的狀態(tài)和常量。
  • 類變量 是在第一次被使用的時(shí)候 才進(jìn)行初始化。

2、類方法(靜態(tài)方法)

  • 靜態(tài)方法無法訪問this指針,不能訪問實(shí)例變量;可以訪問類變量(static variables)
import 'dart:math';
class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

三、抽象類

1、抽象類 和 抽象方法,關(guān)鍵字abstract

  • 抽象方法不能被實(shí)例化,主要用來抽象通用的邏輯。
abstract class Doer {
  void doSomething(); 
}

class EffectiveDoer extends Doer {
  void doSomething() {
  }
}
  • 抽象方法:實(shí)例方法、getter/setter方法,都可以聲明為抽象方法。

  • If you want your abstract class to appear to be instantiable, define a factory constructor.



四、實(shí)現(xiàn),implements

1、類的隱式接口

  • 任何的類 都隱式定義 一個(gè)接口,這個(gè)接口包含了 類全部的實(shí)例成員(變量和方法,但不包括構(gòu)造函數(shù)) 及 類實(shí)現(xiàn)的所有接口。
    Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements.
// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _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.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';
  
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

2、類 支持多個(gè) 接口實(shí)現(xiàn)。

class Point implements Comparable, Location {...}

五、擴(kuò)展,extension

主要用來為 現(xiàn)有庫(library)和APIs 動(dòng)態(tài)增加新的功能。

1、擴(kuò)展的定義

extension <extension_name> on <type> {
  (<member definition>)*
}
  • 擴(kuò)展可以有名字,如下NumberParsing
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  
  double parseDouble() {
    return double.parse(this);
  }
}
  • 可以擴(kuò)展的方法,包括操作符方法、getter/setter方法、成員方法、靜態(tài)方法。
  • 支持 私有(本地)擴(kuò)展,下劃線_,該擴(kuò)展只在當(dāng)前文件或者庫內(nèi)部可見。

2、支持泛化擴(kuò)展

例如:下面是 List<T> 的擴(kuò)展 MyFancyList。

extension MyFancyList<T> on List<T> {
  int get doubleLength => length * 2;
  List<T> operator -() => reversed.toList();
  List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}

3、命名沖突,API conflicts

  • 方法1:導(dǎo)入import的時(shí)候,顯示聲明顯示/隱藏(show/hide) 某個(gè)方法。
// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
  • 方法2:顯示定義為不同的擴(kuò)展名稱。
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.

// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
  • 方法3:前綴調(diào)用,即導(dǎo)入import的時(shí)候重命名,關(guān)鍵字as
    類似于JavaScript的import-as功能。
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // Doesn't work.

// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

六、混淆,mixins

  • 主要用處是實(shí)現(xiàn)多繼承,復(fù)用多個(gè)現(xiàn)有類的代碼。

1、語法格式:

  • 關(guān)鍵字mixin,一般來說,混淆是一個(gè)無構(gòu)造函數(shù)代碼集合。
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    ...
  }
}
  • 關(guān)鍵字 'on' 用來 指定 混淆 只有被 特定的類或其子類 使用。
    Sometimes you might want to restrict the types that can use a mixin.
class Musician {
}

// 只能被Musician及其子類使用
mixin MusicalPerformer on Musician {
}

class SingerDancer extends Musician with MusicalPerformer {
}

2、混淆的使用

關(guān)鍵字with 用來聲明類 遵循 一個(gè)或者多個(gè)混淆。

class Musician extends Performer with Musical {
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
  }
}

七、枚舉

1、枚舉的約束

  • 枚舉不支持繼承subclass、不支持mix、不支持implement。
  • 枚舉無法實(shí)例化。

2、語法格式

  • 關(guān)鍵字enum。
enum Color { red, green, blue }
// .index 枚舉值
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

// .values 獲取所有的枚舉值
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

// 結(jié)合switch
var aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: 
    print(aColor); // 'Color.blue'
}
  • 每一個(gè)枚舉值都包含index的getter方法。
  • 獲取所有的枚舉值列表,.values。
  • 配合 switch 可以實(shí)現(xiàn)多分支流程控制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容