类 - Part I

类的知识点较多,所以分成两节进行讲解,本节是Part I,下一节是Part II


类是对象的蓝本,用于创建对象。

类的声明是使用关键字class,所有类都直接或间接继承最顶端的Object类。

*示例代码使用了dart核心库的identical函数,用于检查两个变量是否指向同一对象

属性和方法

类中可以声明数据和函数,即对象的属性和方法。

普通的属性和方法是与对象实例绑定,它们被称之为实例变量和实例方法;使用static修饰的属性和方法,它们与类绑定,通常称为类变量和类方法。

实例变量和实例方法、类变量和类方法,在类中都可以通过名称直接访问,实例变量和实例方法还可以通过this访问;在类的外部,实例变量和方法是通过实例.变量方式进行访问,而类变量和类方法必须通过类名.变量方式才能访问

  1. // 声明一个表示'大剑'(《黑暗之魂》系列游戏的一类武器)的类
  2. class GreatSword {
  3. // 实例变量
  4. String name; // 名称
  5. final int damage = 100; // 基础伤害值(不可变)
  6. int extraDamage = 0; // 其他附加伤害
  7. // 类变量
  8. static String category = 'weapon'; // 大剑的分类是武器
  9. // 类方法
  10. static upgrade(GreatSword sword) { // 升级,即增加附加伤害
  11. sword.extraDamage += 10;
  12. }
  13. // 实例方法
  14. info() => 'GreatSword - damage: ${damage} , extraDamage: ${this.extraDamage}'; // 通过名称或this访问实例变量
  15. }
  16. main() {
  17. // 使用默认构造函数(见下一小节)进行实例化
  18. var sword = new GreatSword();
  19. print(sword.damage);
  20. print(sword.info()); // GreatSword - damage: 100 , extraDamage: 0
  21. // 使用类方法
  22. GreatSword.upgrade(sword);
  23. print(sword.extraDamage);
  24. print(sword.info()); // GreatSword - damage: 100 , extraDamage: 10
  25. }

构造函数

构造函数用于将类进行实例化(即创建对象),需要配合关键字newconst使用(在 Dart 2.0 中,newconst变为可选)。

没有显式声明构造函数的类,将默认拥有一个与类同名且没有参数的构造函数(无参默认构造函数)。

构造函数最常见的操作是使用参数对属性赋值,Dart 为此提供了简写方式(在参数列表中直接使用this.属性)。

除了普通的构造函数,Dart 还支持类名.构造函数名方式的命名构造函数

  1. class GreatSword {
  2. String name; // 名称
  3. final int damage = 100; // 基础伤害值
  4. int extraDamage = 0; // 其他附加伤害
  5. // 构造函数
  6. GreatSword(String name) {
  7. this.name = name;
  8. }
  9. // 以上构造函数的简写方式
  10. // GreatSword(this.name);
  11. // 命名构造函数
  12. GreatSword.enhanced(this.name, this.extraDamage);
  13. // 等同于以下写法
  14. // GreatSword.enhanced(String name, int extraDamage) {
  15. // this.name = name;
  16. // this.extraDamage = extraDamage;
  17. // }
  18. }
  19. main() {
  20. // 使用不同构造函数进行实例化
  21. var sword = new GreatSword('Claymore'); // 普通大剑
  22. var enhancedSword = new GreatSword.enhanced('Claymore', 20); // 强化版大剑
  23. }

默认情况下,构造函数总是会自动新建一个对象,函数体内不需要也不能使用return语句。

如果不需要构造函数每次调用都新建对象,比如从缓存中获取已存在的对象,则可以使用工厂构造函数。

使用factory修饰的构造函数是工厂构造函数,跟普通构造函数不同,它必须使用return语句返回对象

  1. class GreatSword {
  2. String name; // 名称
  3. // 使用Map类型(后续章节将进行讲解)作为对象缓存
  4. static final Map cache = {};
  5. // 工厂构造函数
  6. factory GreatSword(String name) {
  7. // 对象已存在缓存中
  8. if (cache.containsKey(name)) {
  9. return cache[name];
  10. } else {
  11. // 对象不存在缓存中,使用库私有的(后续章节将进行讲解)构造函数新建对象
  12. final sword = new GreatSword._(name);
  13. cache[name] = sword; // 存入缓存
  14. return sword;
  15. }
  16. }
  17. // 库私有的(后续章节将进行讲解)命名构造函数
  18. GreatSword._(this.name);
  19. }
  20. main() {
  21. // 进行多次实例化
  22. var sword1 = new GreatSword('Claymore');
  23. var sword2 = new GreatSword('Claymore');
  24. // 使用顶层函数identical检查是否同一对象
  25. print(identical(sword1, sword2)); // true - 是同一对象
  26. }

如果希望创建不可变对象,可以将所有实例变量使用final修饰,构造函数使用const修饰,最后使用const创建对象即可

  1. // 声明一个表示'篝火'(《黑暗之魂》系列游戏的存盘点)的类
  2. class Bonfire {
  3. final String name; // 名称
  4. const Bonfire(this.name);
  5. }
  6. main() {
  7. // 使用const进行多次实例化
  8. var bonfire1 = const Bonfire('home');
  9. var bonfire2 = const Bonfire('home');
  10. // 使用顶层函数identical检查是否同一对象
  11. print(identical(bonfire1, bonfire2)); // true - 是同一对象
  12. // bonfire1.name = 'castle'; // 错误,尝试修改不可变对象
  13. }

初始化列表

构造函数还支持初始化列表,书写方式是在参数列表后跟一个以冒号开头的,使用逗号分隔的赋值列表。

初始化列表先于构造函数体执行,常用于final属性的初始化,即没有初始化的final属性可以在初始化列表中进行赋值。

初始化列表还可使用this进行构造函数转发,使得构造函数逻辑得以复用,构造函数转发和赋值操作不能同时出现

  1. class GreatSword {
  2. String name; // 名称
  3. final int damage; // 基础伤害值(没有初始化)
  4. int extraDamage = 0; // 其他附加伤害
  5. // 在初始化列表中设置final变量damage
  6. GreatSword(this.name, [this.extraDamage = 0]) : damage = 100; // 普通大剑基础伤害100
  7. // 在初始化列表中调用静态方法计算extraDamage
  8. GreatSword.magic(this.name, this.damage) : extraDamage = magicPower(damage); // 魔法大剑的附加伤害要根据基础伤害计算得出
  9. // 通过初始化列表转发到其他构造函数,其中this代表类名
  10. GreatSword.bastard(): this('Bastard Sword', 10); // 混种大剑
  11. GreatSword.moonlight(): this.magic('Moonlight GreatSword', 90); // 月光大剑
  12. // 计算魔法武器的附加伤害
  13. static magicPower(int damage) {
  14. return damage * 0.15;
  15. }
  16. }
  17. main() {
  18. var sword = new GreatSword('Claymore');
  19. var bastard = new GreatSword.bastard();
  20. var moonlight = new GreatSword.moonlight();
  21. print(sword.name); // Claymore
  22. print(sword.damage); // 100
  23. print(sword.extraDamage); // 0
  24. print(bastard.name); // Bastard Sword
  25. print(bastard.damage); // 100
  26. print(bastard.extraDamage); // 10
  27. print(moonlight.name); // Moonlight GreatSword
  28. print(moonlight.damage); // 90
  29. print(moonlight.extraDamage); // 13.5
  30. }

Getter/Setter 方法

跟很多语言不同,Dart 通过一种特殊的getter/setter方法对对象属性进行读写,它们虽是方法却有跟属性一样的访问方式。

所有普通属性都有一对隐含的getter/setterfinal属性只有getter

自定义getter/setter也是支持的,书写方式是在方法名前添加getsetgetter有返回值无参数而setter正好相反

  1. class GreatSword {
  2. String name; // 名称
  3. final int damage = 100; // 基础伤害值
  4. int extraDamage = 0; // 其他附加伤害
  5. // damage隐含的getter
  6. // int get damage => damage;
  7. // name和extraDamage隐含的getter和setter
  8. // String get name => name;
  9. // set name(String name) => this.name = name;
  10. // int get extraDamage => extraDamage;
  11. // set extraDamage(int extraDamage) => this.extraDamage = extraDamage;
  12. // 自定义的getter和setter
  13. // 总伤害
  14. int get totalDamage {
  15. return damage + extraDamage; // 基础伤害与附加伤害之和
  16. }
  17. set totalDamage(int totalDamage) {
  18. extraDamage = totalDamage - damage; // 通过总伤害计算出附加伤害
  19. }
  20. // 打印信息
  21. info() {
  22. return '${name} - totalDamage: ' + this.totalDamage.toString(); // 通过this或直接访问属性
  23. }
  24. }
  25. main() {
  26. // 所有对属性的读写都是通过getter和setter
  27. var sword = new GreatSword();
  28. sword.name = 'GreatSword';
  29. sword.totalDamage = 200;
  30. print(sword.damage); // 100
  31. print(sword.extraDamage); // 100
  32. print(sword.info()); // GreatSword - totalDamage: 200
  33. sword.damage = 180; // 错误,damage的setter不存在
  34. }

gettersetter带来的好处是,你可以随时更改属性的内部实现,而且不用修改外部调用代码

  1. // 版本1的GreatSword
  2. class GreatSword1 {
  3. final String name = 'GreatSword';
  4. }
  5. // 版本2的GreatSword,将属性name转换为一个getter
  6. class GreatSword2 {
  7. String get name {
  8. return greatName();
  9. }
  10. greatName() => 'GreatSword';
  11. }
  12. main() {
  13. // 使用两个不同版本的GreatSword,对name的访问方式不变
  14. var sword = new GreatSword1();
  15. print(sword.name); // GreatSword
  16. sword = new GreatSword2();
  17. print(sword.name); // GreatSword
  18. }

子类

使用extends来创建子类,子类中使用super来访问父类。

除了构造函数,父类所有的属性(setter/getter)和方法都被子类继承,而且都可以被重写(override)。

子类的构造函数在执行前,将隐含调用父类的无参默认构造函数(没有参数且与类同名的构造函数)。

如果父类没有无参默认构造函数,则子类的构造函数必须在初始化列表中通过super显式调用父类的某个构造函数。

  1. // 大剑
  2. class GreatSword {
  3. String name; // 名称
  4. final int damage = 100; // 基础伤害值
  5. // 总伤害的getter
  6. int get totalDamage => damage; // 总伤害
  7. // 一个参数的构造函数
  8. GreatSword(this.name);
  9. }
  10. // 特大剑
  11. class UltraGreatSword extends GreatSword {
  12. int extraDamage = 20; // 附加伤害值
  13. // 重写getter
  14. int get totalDamage => super.damage + extraDamage; // 父类的伤害加上自己的附加伤害为总伤害(super可省略)
  15. // 父类没有无参默认构造函数,必须使用super(代表父类名)调用父类的构造函数
  16. UltraGreatSword(String name) : extraDamage = 50, super(name);
  17. }
  18. main() {
  19. var black = new UltraGreatSword('Black Knight Greatsword');
  20. print(black.name); // Black Knight Greatsword
  21. print(black.damage); // 100
  22. print(black.extraDamage); // 50
  23. }