类 - Part I

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


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

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

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

属性和方法

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

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

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

  1. // 声明一个表示'大剑'(《黑暗之魂》系列游戏的一类武器)的类
  2. classGreatSword{
  3. // 实例变量
  4. String name;// 名称
  5. finalint damage =100;// 基础伤害值(不可变)
  6. int extraDamage =0;// 其他附加伤害
  7. // 类变量
  8. staticString 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 =newGreatSword();
  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. classGreatSword{
  2. String name;// 名称
  3. finalint 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 =newGreatSword('Claymore');// 普通大剑
  22. var enhancedSword =newGreatSword.enhanced('Claymore',20);// 强化版大剑
  23. }

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

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

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

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

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

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

初始化列表

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

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

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

  1. classGreatSword{
  2. String name;// 名称
  3. finalint 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 =newGreatSword('Claymore');
  19. var bastard =newGreatSword.bastard();
  20. var moonlight =newGreatSword.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. classGreatSword{
  2. String name;// 名称
  3. finalint 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. intget 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 =newGreatSword();
  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. classGreatSword1{
  3. finalString name ='GreatSword';
  4. }
  5. // 版本2的GreatSword,将属性name转换为一个getter
  6. classGreatSword2{
  7. Stringget name {
  8. return greatName();
  9. }
  10. greatName()=>'GreatSword';
  11. }
  12. main(){
  13. // 使用两个不同版本的GreatSword,对name的访问方式不变
  14. var sword =newGreatSword1();
  15. print(sword.name);// GreatSword
  16. sword =newGreatSword2();
  17. print(sword.name);// GreatSword
  18. }

子类

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

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

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

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

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