學(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)多分支流程控制。