Dart 06 类

1 类

Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object. 。 基于 * Mixin 继承* 意味着每个类(除 Object 外) 都只有一个超类, 一个类中的代码可以在其他多个继承类中重复使用。

1.1 构造函数

构造器的名字可以是 ClassName(无名构造器),也可以是 ClassName.identifier(命名构造器),实例化时的 new 关键字可以省略。

所有的实例变量都会自动生成隐式的 getter 方法,非 final 的实例变量也会自动生成隐式的 setter 方法,使用 get 和 set 关键词可以创建额外的属性和其 getter/setter 方法的实现。

1.1.1默认构造函数

  • 在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。

  • 当创建一个类时 该类会有一个默认构造函数 不需要显式的写出来其默认存在

  • 如果需要添加参数 则要写带参数构造 或者命名构造函数

    • 比如

      • class Perosn{
           	    String name;
            int age;
                   		//默认构造函数 不需要显式的写出来
             		Person(){}
         }
        

1.1.2 带参数普通构造函数

  • Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    
  • 简化写法

    • Person(String this.name, int this.age);
      

1.1.3 命名构造函数

  • 使用命名构造函数可为一个类实现多个构造函数, 也可以使用命名构造函数来更清晰的表明函数意图:

    • class Perosn{
         String name;
         int age;
         //命名构造函数
         Person.fromDictionary(String name){
            this.name=name;
         }
          // 命名构造函数 fromDictionary1 括号中可以为任意类型
        Person.fromDictionary1(Map dic) {
          this.age = dic["age"];
          this.name = dic["name"];
        }
      }
      

1.1.3.1注意

1.1.3.1.1构造函数不被继承
  • 子类不能继承父类的构造器,所以,如果如果子类想利用父类的命名构造器创建实例,就必须实现父类的命名构造器。如果父类没有无名无参构造器,那么你就必须手动调用父类的一个构造器:

    • class Point {
        int x;
        int y;
        // 在构造函数体执行之前,
      // 通过初始列表设置实例变量。
        Point.fromJson(Map<String, int> map)
            : x = map["x"],
              y = map["y"] {
          print('In Point.fromJson(): ($x, $y)');
        }
      }
      class Child extends Point {
          // 子类调用父类
        Child.fromJson(Map<String, int> data) : super.fromJson(data) {
          print('in Child');
        }
      }
      main() {
        var emp = new Child.fromJson({"x":10,"y":10});
      }
      //输出
      //In Point.fromJson(): (10, 10)
      //in Child
      //代码初始化的顺序为: (初始化参数列表) → (父类的无名构造函数) → (主类的无名构造函数)
      
1.1.3.1.2初始化列表

除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量。 各参数的初始化用逗号分隔。

  • // 调用方式参考上面代码 通过super.  
    // 在构造函数体执行之前,
    // 通过初始列表设置实例变量。
    // 初始化列表不支持访问this
      Point.fromJson(Map<String, int> map)
          : x = map["x"],
            y = map["y"] {
        print('In Point.fromJson(): ($x, $y)');
      }
    
  • 默认构造函数不需要显式声明 默认已经存在 若仍显示声明,则显示声明的默认构造函数不能与带参数构造函数并存 会提示默认构造函数已经声明,将其中一个声明为命名构造函数

    • 说明 dart 不允许存在同名构造函数

1.1.4 重定向构造函数

  • 有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (😃 之后。

    • class Point {
        num x;
        num y;
      
        // 主构造函数
        Point(this.x, this.y) {
          print("Point($x, $y)");
        }
      
        // 重定向构造函数,指向主构造函数,函数体为空
        Point.alongXAxis(num x) : this(x, 0);
      }
      

1.1.5 常量构造函数(单例)

如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个 const 构造函数, 并且声明所有实例变量为 final

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
//常量构造函数创建的实例并不总是常量。

1.1.6 工厂构造函数

  • 工厂类构造函数无法访问this

当执行构造函数并不总是创建这个类的一个新实例时,如果之前存在过就可以从缓存中获取返回;使用 factory 关键字。

例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。

class Logger {
  final String name;
  bool mute = false;

  // 从命名的 _ 可以知,
  // _cache 是私有属性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

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

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

1.2 方法

  • 即类中的函数 可以访问this和实例变量

1.2.1 getter setter

  • 所有的实例变量都会自动生成隐式的 getter 方法,非 final 的实例变量也会自动生成隐式的 setter 方法,使用 get 和 set 关键词可以创建额外的属性和其 getter/setter 方法的实现。

    • class Rectangle {
        num left, top, width, height;
      
        Rectangle(this.left, this.top, this.width, this.height);
      
        // 定义两个计算属性: right 和 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;
      }
      
      void main() {
        var rect = Rectangle(3, 4, 20, 15);
        assert(rect.left == 3);
        rect.right = 12;
        assert(rect.left == -8);
      }
      
  • 抽象方法

    • 只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中。

      定义一个抽象函数,使用分号 (😉 来代替函数体:

      • abstract class Doer {
          // 定义实例变量和方法 ...
        
          void doSomething(); // 定义一个抽象方法。
        }
        
        class EffectiveDoer extends Doer {
          void doSomething() {
            // 提供方法实现,所以这里的方法就不是抽象方法了...
          }
        }
        

2 抽象类

使用 abstract 修饰符来定义 抽象类 — 抽象类不能实例化但是可以实例化其子类。 抽象类通常用来定义接口,以及部分实现。

抽象类中可以有抽象方法和非抽象方法

abstract class Doer {
// 定义实例变量和方法 ...

void doSomething(); // 定义一个抽象方法。
}

3 隐式接口(接口)

在dart 中类就是接口,每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。

一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API

  • 示例

    • 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。

    • // person 类。 隐式接口里面包含了 greet() 方法声明。
      class Person {
        // 包含在接口里,但只在当前库中可见。
        final _name;
      
        // 不包含在接口里,因为这是一个构造函数。
        Person(this._name);
      
        // 包含在接口里。
        String greet(String who) => 'Hello, $who. I am $_name.';
      }
      
      // person 接口的实现。
      class Impostor implements Person {
        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()));
      }
      
  • 实现多个接口

    • class Point implements Comparable, Location {...}
      

4 扩展类(继承)

使用 extends 关键字来创建子类, 使用 super 关键字来引用父类:

class Television {
void turnOn() {

}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
 super.turnOn();
}
// ···
}

4.1 重写父类方法 通过@override

  • class Television {
      void turnOn() {
     	////
      }
      // ···
    }
    class SmartTelevision extends Television {
     	@override
        void turnOn() {
       
      }
      // ···
    }
    

5 枚举类

5.1使用枚举

使用 enum 关键字定义一个枚举类型:

enum Color { red, green, blue }

使用枚举的 values 常量, 获取所有枚举值列表( list )。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 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'
}

枚举类型具有以下限制:

  • 枚举不能被子类化,混合或实现。
  • 枚举不能被显式实例化。

6 类变量和类方法

使用 static 关键字实现类范围的变量和方法。

6.1 静态变量

静态变量(类变量)对于类级别的状态是非常有用的:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量只到它们被使用的时候才会初始化。

6.2 静态方法

静态方法(类方法)不能在实例上使用,因此它们不能访问 this 。 静态方法只能访问静态变量例如:

class Child extends Point {
  Child.fromJson(Map<String,int> data) : super.fromJson(data) {
    print('in Child');
  }
  static var i=10;
  var j=10;
  static void m(){
    i++;
    j++;// 这句话会有err
  }
}

提示: 对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法。

静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。

7 Mixin 为类添加功能

什么是Mixin

mix-in 上面1 类中提到过 这是一种继承机制 往下看

mix in官方解释:?

  • 复用代码的一种途径复用的类可以在不同层级,之间可以不用存在继承关系

什么意思?

  • 学过java的应该都知道继承的概念,当一个类A 想要复用类B中的代码或者功能那非常简单,直接A 继承 B ;如果有一个共同模块功能多数时候我们通过抽象类(或者接口)定义功能类由 A,B去继承抽象类;

那么现在问题来了?

  • 如果A,B 并不是同一类对象,比如一个是水鸟,一个是鱼,二者同时具备了游泳的能力,如果B(鱼)想复用A(水鸟)游泳的代码,二者并不属于统一类别物种,这时候使用继承也不合适,那我们要重写一份代码吗?
    • 显然不用,我们可以单独将游泳定义成一个抽象类或者接口ClassSwim A,B去继承ClassSwim,这时候A,B 与ClassSwim存在继承关系
    • dart在 dart 2.1 加入的特性MixIin来代替抽象类,这时候我们要实现上述功能只需要将ClassSwim分别混入A,B, A,B类就可以复用swim

在这时候是不是还不太理解为什么要引入MixIn ,定义单一抽象类不就行了 ?

  • 官网描述:
    • 在支持类和继承的语言中,类隐式定义了mixinmixin通常是隐式的-由类主体定义,并构成类与其超类之间的增量。该类实际上是一个mixin应用程序将其隐式定义的mixin应用于其超类的结果。
    • 所以这里只是将概念提出来并不是新引入。
  • Mixin带来的是更方便的功能组合,的确我们可以通过extends 继承一个抽象类,通过implements实现多个接口从而来扩展实现类的功能,那我为啥要用mixin
    • 我从文档里找到了这么一段描述
    • mixin只能从没有声明构造函数的类中提取。此限制避免了由于需要向继承链上传递构造函数参数而产生的复杂情况。
      • 前面构造函数我们提到过继承类构造函数执行顺序的问题
    • 从我的浅显的理解上我感觉是
      • 不用重写一份代码,直接混入,引用需要的模块 方便维护
      • 避免继承链带来的传递构造函数参数传递带来的问题
      • 如果你想了解的更多
        • 复制连接去看吧https://www.dartcn.com/articles/language/mixins

使用Mixin

  • 通过 with 后面跟一个或多个混入的名称(只要是类都可以,class mixin abstract class),来 使用 Mixin ,

    • 比如一个音乐家 首先是个表演者 其次具有音乐技能

      • class Musician extends Performer with Musical {
          // ···
        }
        
  • 通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。 如果 Mixin 不希望作为常规类被使用,使用关键字 mixin 替换 class 。 例如:

    • mixin Musical {
        bool canPlayPiano = false;
        bool canCompose = false;
        bool canConduct = false;
      
        void entertainMe() {
          if (canPlayPiano) {
            print('Playing piano');
          } else if (canConduct) {
            print('Waving hands');
          } else {
            print('Humming to self');
          }
        }
      }
      
  • 使用 on 来指定可以使用 Mixin 的父类类型:

    • 比如 音乐表演者父类是 音乐家

      • mixin MusicalPerformer on Musician {
          // ···
        }
        

注意

  • with 后面可以跟任何类 只要是类-无论是 mixin adstruct class ,class 修饰的类都可以

  • Mixin 修饰的类不能有构造函数

  • Mixin 内可以存在抽象方法和普通方法 混入后需要实现所有的抽象方法,普通方法则不需要

  • 如果不想让类想普通类一样被使用 用mixin修饰即可

总结

  • mixin 一种继承机制(混入) 使超类和子类不存在继承关系
  • mixin作用就是为类扩展功能,替代了abstract class
    • 替代abstract class 就说明dart 初期通过 abstract class来给类扩展功能 将共有行为定义抽象方法 让子类实现 (多态?)
  • mixin 方便维护 避免了超类子类构造函数参数传递实现超类功能只要混入超类重写其中需要的功能方法,不需要完全继承超类,
  • mixin 方便扩展 提高代码复用 扩展功能只要定义好模块直接混入即可 也不用指定功能属于哪个超类 所有的类都可以混入

这是我的一点理解 肯定不全面 或者理解偏差 如果你看到这个文章我建议你跟着https://www.dartcn.com/articles/language/mixins这个连接去了解下 来自己理解一下 这个文章只是记录了我看的时候的一些理解并不深刻可能有些是错的 如果有不同看法 欢迎评论区大家一起讨论

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章