2019-09-20: 九: Flutter之Dart第六节(类和对象)?

九: Flutter之Dart第六节(类和对象)?

Dart 是一个面向对象的语言、面向对象中非常重要的概念就是类、类产生了对象。
这一节,我们就具体来学习类和对象、但是Dart对类进行了很多其他语言没有的特性、一起探讨。

9.1: 类的定义

在Dart中、定义类用class关键字。
类通常有两部分组成:成员(member) 和 方法 (method)。
定义类的伪代码如下:

class 类名 {
   类型 成员名
   返回值类型 方法名(参数列表) {
     方法体
   }
 }

编写一个简单的Person类

  • 这里有一个注意点:我们在方法中使用属性(成员变量/实例变量)时、并没有加this;
  • Dart的开发风格中、在方法中通常使用属性时、会省略this、但是有命名冲突时、this不能省略。
class Person {
 String name;

 eat() {
   print('$name在吃东西');
 }
}

main(List<String> args) {
 // 2: 我们在方法中使用属性并没有使用this、一般我们都是省略的、但是如果在方法中使用属性的时候、如果有命名
 // 冲突的话、this不能省略

 // 1: 创建类的对象, 直接使用Person() 也可以创建
 var p = new Person();

 // 2: 给对象的属性赋值
 p.name = 'lishengbing';

 // 3: 调用对象方法
 p.eat();

 //打印: lishengbing在吃东西
}

9.2: 构造方法

9.2.1: 普通构造方法

我们知道、当通过类创建一个对象时、会调用这个类的构造方法

  • 当类中没有明确指定的构造方法时、将默认拥有一个无参的构造方法

  • 前面的Person中我们就是在调用这个构造方法、我们也可以根据自己的需求、定义自己的构造方法。

  • 注意1: 当有了自己的构造方法时、默认的构造方法将会失效、不能使用
    -- 当然、你可能希望明确的写一个默认的构造方法、但是会和我们自定义的构造方法冲突
    -- 这是因为Dart语言本身不支持函数的重载(名称相同、参数不同的方式)

  • 注意2: 这里我还实现了toString方法。

class Person {
 String name;
 int age;

 // 明确指定了构造方法、如果没有指定默认是Person()
 Person(String name, int age) {
   this.name = name;
   this.age = age;
 }

 @override
 String toString() {
   return 'name = ${name}, age = ${age}';
 }
}

另外、在实现构造方法时、通常做的事情就是通过 **参数给属性 **赋值,如上this.name = name
为了简化了这一过程、Dart提供了一种更加简洁的语法糖形式
上面的构造方法可以优化成下面的写法:

// 明确指定了构造方法、如果没有指定默认是Person()
 Person(String name, int age) {
   this.name = name;
   this.age = age;
 }

 // 等同于上面优化构造方法
 Person(this.name, this.age);

9.2.2: 命名构造方法

但是在开发中、我们确实希望实现更多的构造方法、怎么办呢?

  • 因为不支持方法(函数)的重载、所以我们没办法创建相同名称的构造方法。我们需要使用命名构造方法。
class People {
 String name;
 int age;

 People() {
  name = '';
   age = 0;
 }

 // 命名构造方法, withArgments可以随便变化
 People.withArgments(String name, int age) {
   this.name = name;
   this.age = age;
 }

// 优化上面的构造方法
 //People.withArgments(this.name, this.age);

 @override
 String toString() {
     return 'name=${name} age=${age}';
  }
}

/*
* name= age=0
 name=lishengbing age=28
*/
main(List<String> args) {
 var p = new People();
 print(p);
 var p1 = new People.withArgments('lishengbing', 28);
 print(p1);
}

在之后的开发中、我们也可以利用命名构造方法、提供更加便捷的创建对象的方式

  • 比如开发中、我们需要经常将一个Map转成对象、可以提供如下的构造方法
// 新的构造方法
 People.fromMap(Map<String, Object> map) {
   this.name = map['name'];
   this.age = map['age'];
 }
var p3 = new People.fromMap({'name': 'lishengbing', 'age': 29});
 // name=lishengbing age=29
 print(p3);

9.2.3: 初始化列表

我们来重新定义一个类Point、传入x/y、可以得到它的距离distance:

初始化列表

class Point {
 final num x;
 final num y;
 final distance;

// 错误写法

Point(this.x, this.y) {
  distance = sqrt(x * x + y * y);
}*/

 // 正确写法
 Point(this.x, this.y) : distance = sqrt(x * x + y * y);

}

上面这种初始化变量的方法、我们称之为初始化列表(Initializer list)

9.2.4: 重定向构造方法

在某些情况下、我们希望在一个构造方法中去调用另一个构造方法、这时候我们就可以使用重定向构造方法。

  • 在一个构造函数中、去调用另一个构造函数(注意:是在冒号后面使用this调用)
class Person01 {
 String name;
 int age;

 Person01(this.name, this.age);
 Person01.fromName(String name) : this('lishengbing', 30);
}

9.2.5: 常量构造方法

在某些时候下、传入相同值时、我们希望返回同一个对象、这个时候、可以使用常量构造方法
默认情况下、创建对象时、即使传入相同的参数、创建出来的对象也不是同一个对象,看下面的代码:

  • 我们使用identical(对象1, 对象2)函数来判断两个对象是否是同一个对象
class Person02 {
 String name;
 Person02(this.name);
}

main(List<String> args) {
 var p1 = Person02('lishengbing');
 var p2 = Person02('lishengbing');

 // false
 print(identical(p1, p2));
}
  • 但是如果将构造方法前加上 const进行修饰的话、那么可以保证同一个参数、创建出来的对象是相同的。
    这样的构造函数就叫做常量构造方法
class Person02 {
final  String name;
const  Person02(this.name);
}

main(List<String> args) {
 var p1 = const Person02('lishengbing');
 var p2 = const Person02('lishengbing');

 // true
 print(identical(p1, p2));
}
常量构造方法有一些注意点:
  • 注意一:拥有常量构造方法的类中、所有的成员变量必须是final学校修饰的、如final String name;
  • 注意二:为了可以使用常量构造方法、创建出相同的对象,不再使用new 关键字、而是使用const关键字
    -- 如果是将结果赋值给const修饰的标识符时、const可以省略。

9.2.5: 工厂构造方法

Dart 提供了factory关键字、用于通过工厂去获取对象。
工厂获取得到的对象传入同一个参数、得到的是同一个对象
传入不同的参数、获取的对象不是同一个对象。

main(List<String> args) {
 var p1 = Person03('object');
 var p2 = Person03('object');

 // 工厂构造方法获取对象比较=true
 print('工厂构造方法获取对象比较=${identical(p1, p2)}');
}

class Person03 {
 String name;

 static final Map<String, Person03> _cache = <String, Person03>{};

 factory Person03(String name) {
   if (_cache.containsKey(name)) {
       return _cache[name];
  }else {
       final p = Person03._internal(name);
       _cache[name] = p;
       return p;
   }
 }

 Person03._internal(this.name);

}

9.2.6: setting & getting

默认情况下、Dart中类定义的属性是可以被外界直接访问的
但是某些情况下、我们希望监控这个类的属性被访问的过程、这个时候就可以使用setting 和 getting了

main(List<String> args) {
 final d = Dog('黄色');

 //打印就是: dog color = 黑色
 d.setColor = '黑色';
 
 //打印就是: dog color = 红色
 d.color = '红色';
 print('dog color = ${d.getColor}');
}


class Dog {
 String color;

 String get getColor {
   return color;
 }

 set setColor(String color) {
   this.color = color;
 }

 Dog(this.color);
}

9.2.7: 类的继承(仅支持单继承、多继承不可以)

1: 面向对象的其中一大特性就是继承、继承不仅可以减少我们的代码量、也是多态的使用前提

2: dart 中的继承使用extends 关键字、子类中使用super来访问父类;

3: 父类中的所有成员变量和方法都会被继承、但是构造方法除外.

main(List<String> args) {
 var p = new Person04();
 p.age = 10;
 p.run();
 /// 奔跑ing
 /// 继承=10
 print('继承=${p.age}');
}

class Animal {
 int age;
 run() {
  print('奔跑ing');
 }
}

class Person04 extends Animal {

}
9.2.7(1): 子类可以拥有自己的成员变量、并且可以对父类的方法进行重写
class Person04 extends Animal {
 @override
 run() {
  // TODO: implement run
   1: 如果子类重写了父类的方法,打印父类的方法
   //我是Person04类的重写run方法
   //继承=10
   print('我是Person04类的重写run方法');
 }
}
9.2.7(2): 子类中可以调用父类的构造方法、对某些属性进行初始化
  • 子类的构造方法在执行前, 将隐式调用父类的默认的无参数的构造方法(没有参数且类同名的构造方法)
  • 如果父类没有无参默认构造方法、则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法。
class Animal {
 int age;
 run() {
   print('奔跑ing');
 }

 // 只要这样写、该类就没有了无参默认构造函数
 Animal(this.age);
}

class Person04 extends Animal {

 String name;

 Person04(String name, int age) : name=name, super(age);

 @override
 run() {
   // TODO: implement run
   // 1: 如果子类重写了父类的方法,打印父类的方法
   // 我是Person04类的重写run方法
   // 继承=10
   print('我是Person04类的重写run方法');
 }

 @override
 String toString() {
     print('toString-');
  }
}

9.2.8: 抽象类

我们知道、继承是多态使用的前提
所以在定义很多通用的通用接口时我们通常会让调用者传入父类、通过多态类实现更加灵活的调用方式。

但是、父类本身可能并不需要对某些方法进行具体的实现、所以父类中定义的方法、我们可以定义为抽象类。

什么是抽象方法?在Dart中没有具体实现的方法(没有方法体)、就是抽象方法。

  • 抽象方法、必须存在于抽象类中。
  • 抽象类是使用abstract 声明的类。
    下面这个Shape类就是一个抽象类、其中包含了一个抽象方法
abstract class Shape {
  getArea();
}

class Circle extends Shape {

  double r;
  Circle(this.r);

  @override
  getArea() {
    // TODO: implement getArea
    return r * r * 3.14;
  }
}

class Reactangle extends Shape {

  double w;
  double h;
  Reactangle(this.w, this.h);

  @override
  getArea() {
    // TODO: implement getArea
    return w * h;
  }
}
注意事项:
  • 注意一:** 抽象类中不能实例化;
  • 注意二:** 抽象类中的抽象方法必须被子类实现、抽象类中的已经被实现方法、可以不被子类重写;

9.2.9: 隐式接口

Dart中的接口比较特殊、没有一个专门的关键字来声明接口
默认情况下、定义的每一个类都相当于默认也声明了一个接口、可以由其他的类来实现(因为Dart不支持多继承)

在开发中、我们通常将用于给别人来实现的类声明为抽象类:

abstract class Runner {
  run();
}

abstract class Flyer {
  fly();
}

class SuperMan implements Runner, Flyer {
  @override
  run() {
    // TODO: implement run
    print('超人在跑');
  }

  @override
  fly() {
    // TODO: implement fly
    print('超人在飞');
  }
}

9.2.10: Mixin混入

在通过implements实现某个类时、类中所有的方法都必须被重载实现(无论这个类原来是否已经实现过该方法)

但是在某些情况下、一个类可能希望直接复用之前类的原有实现方案、怎么做呢?

  • 使用继承吗?但是Dart只支持单继承、那么意味着你只能复用一个类的实现

Dart提供了另外一种方案:Mixin混入的方式

  • 除了可以class 定义类之外、也可以通过mixin关键字来定义一个类
  • 只是通过mixin定义的类用于被其他类混入使用、通过with关键字来进行混入。
main(List<String> args) {
  var superMan = SuperMain();
  superMan.run();
  superMan.fly();
  /*
  在奔跑...
  在飞翔... 
  */
}

mixin Runner1 {
  run() {
    print('在奔跑...');
  }
}

mixin Flyer1 {
  fly() {
    print('在飞翔...');
  }
}

/// implements的方式必须要求对其中的方法进行重新实现
class SuperMain0 implements Runner1, Flyer1 {

  @override
  run() {
    // TODO: implement run
    return null;
  }

  @override
  fly() {
    // TODO: implement fly
    return null;
  }
}

class SuperMain with Runner1, Flyer1 {

}

9.2.11: 类成员和方法

前面我们在类中定义的成员和方法都属于对象级别的、在开发中、我们有时候也需要定义类级别的成员和方法

在Dart中我们使用static关键字类定义:

main(List<String> args) {
  var stu = Student();
  stu.name = 'lishengbing';
  stu.age = 66;
  stu.study();

  Student.time = '早上10点';
  Student.attendClass();
  // lishengbing在学习
  // 去上课
}

class Student {
  String name;
  int age;

  static String time;

  study() {
    print('$name在学习');
  }

  static attendClass() {
    print('去上课');
  }
}

9.2.12: 枚举的类型

枚举在开发中也非常常见、枚举也是一种特殊的类、通常用于表示数量的常量值。

1: 枚举的定义

枚举使用enum关键字来进行定义

// 1: 枚举的定义

main(List<String> args) {
  // Colors.red
  print(Colors.red);
  // values, [Colors.red, Colors.green, Colors.blue]
  print(Colors.values);
  // index, 0
  print(Colors.red.index);
}

enum Colors {
  red,
  green,
  blue
}
2: 枚举的属性

枚举类型中有两个比较常见的属性

  • index: 用于表示每个枚举常量的索引、从0开始
    如:print(Colors.red.index);
  • values:包含每个枚举值的List
    如:print(Colors.values);
3: 枚举的注意事项
  • 注意1: 您不能子类化、混合或者实现枚举。
  • 注意2: 不能显式实例化一个枚举。

9.3: 泛型

1: 为什么使用范型?

......

2: List和Map的范型
main(List<String> args) {
// 1: 创建List的方式
var name1 = ['lishengbing', 'zhangsan', 'wangxiao', 111];
// List<String>
// List<Object>
print(name1.runtimeType);

// 限制类型
var name2 = <String>['1', '2', '3'];
// List<String>
print(name2.runtimeType);



// 2: Map使用泛型的写法
var info1 = {'name': 'li', 1: 'one', 'age': 20};
// _InternalLinkedHashMap<Object, Object>
print(info1.runtimeType);


// 2: 对类型进行限制
Map<String, String> info2 = {'name': 'li', 'age': '20'};
// 限制写法1
// info2=_InternalLinkedHashMap<String, String>
print('info2=${info2.runtimeType}');

var info3 = <String, String>{'name': 'li', 'age': '20'};
// 限制写法2
// info3=_InternalLinkedHashMap<String, String>
print('info3=${info3.runtimeType}');

}
3: 类定义的泛型

如果我们需要定义一个类、用于存储位置信息Location、但是并不确定使用者使用的是int类型、还是double类型、甚至是一个字符串类型、这个时候该怎么定义呢?

  • 一种方案是使用Object类型、但是在之后使用时候非常不方便
  • 另一种方案就是使用泛型
    Location类的定义:Object类 || 泛型方式
main13(List<String> args) {
  Location l = Location(10, 20);
  /// int
  print(l.x.runtimeType);
}

class Location {
  Object x;
  Object y;

  Location(this.x, this.y);
}


main(List<String> args) {
  Location2 l1 = Location2<int>(10, 30);
  // Location2<int>
  print('int-l1=${l1.runtimeType}');

  Location2 l2 = Location2<String>('30', '40');
  // Location2<String>
  print('String-l2=${l2.runtimeType}');
}

// 泛型写法
class Location2<T> {
  T x;
  T y;

  Location2(this.x, this.y);
}

  • 如果我们希望类型只能是num类型、怎么做呢?
// 如果希望类型只能是num类型


main(List<String> args) {
  Location3 l3 = Location3(10, 20);
  // Location3<num>
  print(l3.runtimeType);
}


class Location3<T extends num> {
  T x;
  T y;

  Location3(this.x, this.y);
}
4: 泛型方法的定义

最初、Dart仅仅在类中支持泛型、后来一种称之为泛型方法的新语法允许在方法和函数中使用类型参数。

main(List<String> args) {
  var names = ['1', '2'];
  var first = getFirst(names);
  /// first = 1, type=String
  print('first = ${first}, type=${first.runtimeType}');
}

T getFirst<T>(List<T> ts) {
    return ts[0];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容