Dart 语法原来这么好玩儿(二)


上一篇 Dart 语法的文章中介绍了对 FutureStreamsunwrap 操作以及 ListMap的展开、合并和过滤等等,总觉得有点意犹未尽,还有很多有意思的并没有提到,本篇文章来介绍一下 Dart 语言中面向对象的内容——构造函数,在我们日常开发中,对象的初始化(构造函数)有哪些方式,它们之间的区别在哪里,不同的构造函数都适用于哪些场景呢,本篇文章来聊聊这些内容。

Named 构造函数

如果没有定义构造函数,Dart 会默认创建一个不带参数的默认构造函数。它用于创建类的简单实例,通常不带参数或仅初始化对象的基本属性。Dart 还允许在同一类中定义多个构造函数,每个构造函数通过名称区分。命名构造函数提供了更多灵活性,适用于不同初始化需求。命名构造函数使用类名加点(ClassName.constructorName)的形式定义。

class Rectangle {
  double width, height;

  // 常规构造函数
  Rectangle(this.width, this.height);

  // 命名构造函数
  Rectangle.square(double size) : width = size, height = size;
}

这里还可以使用重定向构造函数,简化代码逻辑,将多个构造函数重定向到同一个主构造函数,从而减少重复代码,修改 Rectangle.square 构造函数的代码:

// 重定向构造函数
Rectangle.square(double size) : this(size, size);

Flutter源码中这种类似用法有很多,如 EdgeInsets 类,有EdgeInsets.fromLTRB、EdgeInsets.all、EdgeInsets.only 等等。

Factory 构造函数

工厂构造函数使用 factory 关键字,可以在每次调用构造函数时返回相同的实例,也可以根据逻辑返回不同的实例。它适用于需要缓存或复杂实例创建逻辑的情况,比如我们常见的单例模式(Singleton Pattern)。

class Logger {
  static final Logger _instance = Logger._internal();

  // 私有命名构造函数
  Logger._internal();

  // 工厂构造函数返回单例
  factory Logger() {
  	return _instance;
  }
}

如下代码复杂一点的用法,利用 Factory 构造函数 来根据不同参数返回不同类型的形状对象(CircleSquare),可实现灵活的创建对象,不同于标准构造函数,Factory 构造函数可以返回子类,

enum ShapeType { circle, square }

class Shape {
  final ShapeType type;
  const Shape(this.type);
  factory Shape.fromRadius(double radius) => Circle(radius);
  factory Shape.fromRect(Rect rect) => Square(rect);
  
  String toString() => 'I am a shape of type $type';
}

class Circle extends Shape {
  final double radius;
  const Circle(this.radius) : super(ShapeType.circle);
  
  String toString() => super.toString() + ' and I am a circle';
}

class Square extends Shape {
  final Rect rect;
  const Square(this.rect) : super(ShapeType.square);
  
  String toString() => super.toString() + "and I am a square";
}

Factory 构造函数在对象创建的过程中提供了统一且灵活的接口,特别适用于需要根据不同条件创建不同子类实例的场景。实际项目中 Factory 构造函数的应用场景也很多,尤其在处理复杂对象创建和资源优化方面。

以下是 Factory 构造函数和普通构造函数的区别。

Static 构造函数

实际上在 Dart 中, Static 构造函数并不是像有的编程语言(如 C#)中那样的有内置特性。也就是说 Dart 中没有直接的静态构造函数概念,但可以通过静态方法来实现类似的功能。静态方法可以用于初始化类级别的共享资源、执行单例模式和延迟加载等场景。将上面的单例类(Logger)改成 Static 构造函数来实现。

class Logger {
  static final Logger _instance = Logger._internal();

  // 私有构造函数
  Logger._internal();

  // 静态构造函数:通过静态方法返回单例实例
  static Logger initialize() {
    print("Logger initialized");
    return _instance;
  }

  void log(String message) {
    print("Log: $message");
  }
}

void main() {
  // 调用静态构造函数
  final logger = Logger.initialize();
  logger.log("This is a log message.");
}

静态构造函数结合 late 变量可以达到也可以延迟加载的机制,例如,设计一个依赖初始化数据库的类,来看看是怎么实现的。

class DatabaseConnection {
  static late DatabaseConnection _instance;
  static bool _isInitialized = false;

  DatabaseConnection._create();

  // 静态构造函数用于初始化
  static void initialize() {
    if (!_isInitialized) {
      _instance = DatabaseConnection._create();
      print("Database connection initialized.");
      _isInitialized = true;
    }
  }

  static DatabaseConnection get instance => _instance;

  void query(String sql) {
    print("Executing query: $sql");
  }
}

void main() {
  DatabaseConnection.initialize();
  DatabaseConnection.instance.query("SELECT * FROM users");
}

上面的代码中,late 关键字保证变量在第一次使用时才初始化,从而实现延迟加载, _internal 私有构造函数防止外部直接实例化。 getInstance 静态工厂方法是单例实例的全局访问入口。延迟加载常用于当初始化逻辑较为复杂,需要耗费大量资源时,例如,加载配置文件、初始化数据库连接等,或者不希望类在加载时就立即初始化,而是按需使用时才初始化。

FactoryStatic 构造函数区别

上面分别介绍了 Factory 构造函数和 Static 构造函数,那么它们之间有什么区别呢?Factory 构造函数返回已有实例或子类实例,控制具体对象的创建过程,而不是初始化与类绑定的静态资源;而 Static 构造函数用于初始化与类本身相关的资源,如设置静态变量或执行静态初始化逻辑,而不创建具体实例。

还有需要补充的一点就是对泛型参数的支持,在工厂构造函数中可以直接使用该类泛型参数,而无需在其函数中重新定义它,而是静态构造函数,必须重新定义泛型参数,因为静态构造函数不能从其类中继承泛型参数,借助代码来理解一下。


class Person<P> {
  final String name;
  final P property;

  const factory Person(String name, P property) = Person.fromProperties;

  const Person.fromProperties(this.name, this.property);

  // 无需重新定义泛型 P,直接使用
  factory Person.fromPropertiesFactory(String name, P property) =>
      Person.fromProperties(name, property);

  // 必须重新定义泛型参数 <P>
  static Person<P> fromPropertiesStatic<P>(String name, P property) =>
      Person.fromProperties(name, property);

  factory Person.fooBar(P property) =>
      Person.fromProperties('Foo Bar', property);

  static Person<P> bazQux<P>(P property) =>
      Person.fromProperties('Baz Qux', property);
}

此外,工厂构造函数支持重定向等功能,而静态方法则无法提供这些功能。使用工厂构造函数创建对象有助于明确代码的目的和意图。

构造函数与抽象类

和其它语言中的抽象类一样,Dart的抽象类也是不能被实例化的,但抽象类构造函数可以用于初始化被继承的字段或逻辑,子类可以在构造函数中使用 super 调用父类的构造函数,这样方便为子类提供模板化的构造流程。在设计模式中,抽象类的构造函数常用于定义公共初始化逻辑,确保子类继承时遵循一致性。还有当一个类定义了 final 修饰的字段时,可以通过抽象类的构造函数初始化这些字段。也把二者之间的区别做个对比。

结合代码来理解一下:

enum Type { dog, cat }

abstract class Animal {
  final Type type;
  // 初始化被继承的字段或逻辑
  const Animal({required this.type});
}

class Cat extends Animal {
  // 使用 super 调用父类的构造函数
  const Cat() : super(type: Type.cat);
}

class Dog extends Animal {
 // 使用 super 调用父类的构造函数
  const Dog() : super(type: Type.dog);
}

小结

文章中介绍了命名构造函数、FactoryStatic 构造函数、构造函数与抽象类以及它们之间的区别,也提到它们各自的应用场景,怎么样,这些构造函数的打开方式你 Get 到了吗?如有疑问,欢迎留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值