未来是移动端的天下,既然学了GO了,再学一门flutter吧!
前言
Dart 是一门面向对象的语言, 所有的类都是继承自 Object
, 除了支持传统的 继承、封装、多态
, 还有基于组合(Mixin-based)的继承特性, 这个特性也是本文将会重点介绍的地方
继承 子集合拥有父集合的全部属性,子集可以自建其他属性,但是得包含父集的全部属性。
封装 通俗点讲就是拥有自己的函数和返回值,只需要给他东西,返回加工后的成品即可
多态 多态是一个特性,最开始的时候不指定数据类型,运行的时候才指定类型,而且这个类型 可以被其他类型替换。相当于动态变量。
继承·封装·多态参考链接
既然是面向对象的语言, 先让我们来看下如何定义一个类:
通过 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
除此以外, 开发者还可以使用 get
和 set
关键字实现 getter
和 setter
方法来创建新的属性, 如:
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
中这是允许的, 在 Java
中 implements
关键后面只能放接口
如果 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 的类需要满足一定的条件:
经过测试发现会使用 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
有依赖, 那么其他类在 with
类 A
的时候, 必须继承或者实现 SuperA
类
1
2
|
class B extends SuperA with A{
}
|
从中我们可以看出 mixin
机制对复用代码提供了非常大的便利和灵活性
关于 Dart 面向对象就先分析到这里
联盟少侠 阅读总结
首先的感谢 大佬 Chiclaim ,我自己是想不到这么多的,从阅读来看,对面向对象有了一定的基础了解。思路更加清晰了一些!我在其中放入了不少基础的知识,方便自己阅读,同时也方便后来者阅读。我对原理和源代码阅读不敢兴趣,我只需记住怎么使用,会使用就好。
很多东西基础就是基础,阅读这篇文章前后共花费2小时左右,有时候,只待你认真起来世界都为你低头。