Dart 入门教程(四),类的使用

这是 Dart 入门教程第四篇,学习 Dart 中类的使用。

Dart 入门教程(一),变量、集合篇

Dart 入门教程(二),函数篇

Dart 入门教程(三),运算符篇

Dart 入门教程(四),类的使用

Dart 入门教程(五),同步和异步

构造函数

要构造类,这就需要构造函数,Dart 的构造函数和其他语言是一样的。但 Dart 有一个构造函数语法糖,这是其他语言没有的。

  class Point {
    int x;
    int y;

    // 使用构造函数语法糖
    Point(this.x, this.y);
    
    //使用上面语法糖取代传统写法
    Point(int x, int y){
    	this.x = x;
    	this.y = y;
    };
  }

  void main() {
    Point point = Point(10, 20);
    print('x: ${point.x}, y: ${point.y}'); // 输出:x: 10, y: 20
  }

和其他语言还有一个区别,因为 Dart 不支持函数重载,所以如果我们想创建多个构造函数,这得用到命名构造函数。

  class Rectangle {
    int width;
    int height;

    // 默认构造函数
    Rectangle(this.width, this.height);

    // 命名构造函数
    Rectangle.square(int side) {
      width = side;
      height = side;
    }
  }

  void main() {
    Rectangle rect1 = Rectangle(10, 20);
    print('rect1 - width: ${rect1.width}, height: ${rect1.height}'); // 输出:rect1 - width: 10, height: 20

    Rectangle rect2 = Rectangle.square(15);
    print('rect2 - width: ${rect2.width}, height: ${rect2.height}'); // 输出:rect2 - width: 15, height: 15
  }

命名构造函数用的非常多,最常见就是实体类的转换,我在Flutter 中用 Dart 解析 JSON 对象、数组、嵌套对象等数据中有分享工具,可以将 json字符串转成实体类,工具就生成了多个构造函数,其中也包括命名构造函数。

class Autogenerated {
  int? stationId;
  String? stationName;

  Autogenerated({this.stationId, this.stationName});

  Autogenerated.fromJson(Map<String, dynamic> json) {
    stationId = json['stationId'];
    stationName = json['stationName'];
  }
}

初始化列表

我之前学习 Dart 时,这初始化列表直接就跳过了,以为没什么用。结果翻一些开源控件的源码时,总是看不懂。加上自己如果写自定义控件,初始化列表是必学知识点。

初始化列表的特征如下,在方法参数后面使用 :

  class Circle {
    final double radius;
    final String color;

    // 使用初始化列表为 final 属性设置默认值
    Circle({double radius, String color})
        : this.radius = radius ?? 10.0,
          this.color = color ?? 'red';
  }

这里要注意,就我看到的情况,初始化列表 99% 和 final 搭配使用,而且使用的场景都是为了给 final 定义的属性设置初始默认值

final 定义的属性是不可变的,那如果定义时就给它设置默认值,当传入真正的设置值时,并不会赋值修改成功,因为 final 定义的属性是不可变的。

比如说,我将上面代码修改成如下,尽管构造时给 color 传入了颜色,但并未生效,打印出来的还是默认值,因为 final 定义的属性不能被二次修改。

  class Circle {
    final double radius = 1;
    final String color = 'red';

    // 使用初始化列表为 final 属性设置默认值
    Circle({double? radius, String? color});
  }

  void main() {
    Circle circle1 = Circle(radius: 5, color: 'blue');
    print(circle1.color); // red
  }

所以上面这种给 final 定义属性设置默认值的方式不可取,会采用初始化列表。

  class Circle {
    final double radius;
    final String color;

    // 使用初始化列表为 final 属性设置默认值
    Circle({double? radius, String? color})
        : this.radius = radius ?? 10.0,
          this.color = color ?? 'red';
  }

  void main() {
    Circle circle1 = Circle(radius: 5, color: 'blue');
    print(circle1.color); // blue
  }

通过 this.radius = radius ?? 10.0 就能实现一种比较好的效果,如果传入的值是空,则使用默认值,如果不是空,则赋值传入的值。

那上面的写法可以简写吗?不使用初始化列表,使用下面方法也可以。

  class Circle {
    final double radius;
    final String color;

    // 使用初始化列表为 final 属性设置默认值
    Circle({this.radius = 10.0, this.color = 'red'});
  }

  void main() {
    Circle circle1 = Circle(radius: 5,color: 'blue');
    print(circle1.color); // blue
  }

但上面的方案有缺点,如果默认值不是一个具体的值,而是表达式,这就不行了。比如我换乘表达式,就会报错提醒:

Circle({this.radius = 10.0, this.color = DateTime.now().toString()});

但使用初始化列表是没问题的,所以优势就在这里。

Circle({double? radius, String? color})
      : this.radius = radius ?? 10.0,
        this.color = color ?? DateTime.now().toString();

工厂构造函数

工厂构造函数主要用来创建缓存的对象实例,避免创建多个相同对象,或者可以用来根据条件创建不同类型的对象。下面是从缓存中获取对象实例的代码

  abstract class Animal {
    void makeSound();
  }

  class Dog implements Animal {
    @override
    void makeSound() {
      print('Woof!');
    }
  }

  class Cat implements Animal {
    @override
    void makeSound() {
      print('Meow!');
    }
  }

  class AnimalFactory {
    // 缓存已创建的实例
    static final Map<String, Animal> _cache = {};

    // 工厂构造函数
    factory AnimalFactory(String animalType) {
      if (_cache.containsKey(animalType)) {
        return _cache[animalType];
      } else {
        Animal animal;
        switch (animalType) {
          case 'Dog':
            animal = Dog();
            break;
          case 'Cat':
            animal = Cat();
            break;
          default:
            throw Exception('Unknown animal type: $animalType');
        }
        _cache[animalType] = animal;
        return animal;
      }
    }
  }

  void main() {
    Animal animal1 = AnimalFactory('Dog');
    animal1.makeSound(); // 输出:Woof!

    Animal animal2 = AnimalFactory('Dog');
    animal2.makeSound(); // 输出:Woof!
    
    print(identical(animal1, animal2)); // 输出:true
  }

抽象类

Dart 和 JAVA 类有一个很重要区别是 Dart 没有接口,Dart 只有抽象类。它不能被实例化,只能被其他类继承。抽象类中可以定义抽象方法,这些方法没有具体的实现,需要由继承抽象类的子类来实现。并且某个类继承自抽象类后,必须实现抽象类的抽象方法

  // 抽象类 Shape
  abstract class Shape {
    // 抽象方法,用于计算形状的面积
    double getArea();
  }

  // 实现抽象类的子类:矩形
  class Rectangle extends Shape {
    final double width;
    final double height;

    Rectangle(this.width, this.height);

    @override
    double getArea() {
      return width * height;
    }
  }

虽然抽象类不能被实例化,但如果抽象类中创建了工厂构造函数,我们可以实例化继承抽象类的之类,曲线实例化抽象类,比如像 Map、List,都是抽象类,但却可以被实例化,就是因为这个这。

虽然 Dart 中没有接口,但它有implements 关键字,只不过不常用,因为使用 implements后,必须要实现类里所有方法,那如果有的方法已经实现,还得重新再写一遍。比如像下面案例的 methodA ,本身已经实现,结果被继承后,又得再重新实现,因为使用 implements 后,必须要实现类里所有方法。

  class AB {
    void methodA() {
      print("Method A");
    }

    void methodB() {}
  }

  class C {
    void methodC() {}
  }

  class MyClass implements AB, C {
    @override
    void methodA() {
      print("MyClass: Method A");
    }

    @override
    void methodB() {
      print("MyClass: Method B");
    }

    @override
    void methodC() {
      print("MyClass: Method C");
    }
  }

为了解决上面的问题,大部分情况不用 implements ,而是用 with 混入。将上面代码修改如下:

 mixin AB {
    void methodA() {
      print("Method A");
    }

    void methodB() {}
  }

  mixin C {
    void methodC() {}
  }

  class MyClass with AB, C {
    @override
    void methodB() {
      print("MyClass: Method B");
    }

    @override
    void methodC() {
      print("MyClass: Method C");
    }
  }

OK,以上就是 Dart 类的知识点,下篇分享 Dart 中异步和同步的实现,也是 Dart 入门最后一篇文章。

Dart 入门教程(一),变量、集合篇

Dart 入门教程(二),函数篇

Dart 入门教程(三),运算符篇

Dart 入门教程(四),类的使用

Dart 入门教程(五),同步和异步

本文由老郭种树原创,转载请注明:https://guozh.net/dart-getting-started-tutorial-class/

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注