上一篇 Dart
语法的文章中介绍了对 Future
、Streams
的 unwrap
操作以及 List
、Map
的展开、合并和过滤等等,总觉得有点意犹未尽,还有很多有意思的并没有提到,本篇文章来介绍一下 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
构造函数 来根据不同参数返回不同类型的形状对象(Circle
或 Square
),可实现灵活的创建对象,不同于标准构造函数,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
静态工厂方法是单例实例的全局访问入口。延迟加载常用于当初始化逻辑较为复杂,需要耗费大量资源时,例如,加载配置文件、初始化数据库连接等,或者不希望类在加载时就立即初始化,而是按需使用时才初始化。
Factory
和 Static
构造函数区别
上面分别介绍了 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);
}
小结
文章中介绍了命名构造函数、Factory
和 Static
构造函数、构造函数与抽象类以及它们之间的区别,也提到它们各自的应用场景,怎么样,这些构造函数的打开方式你 Get 到了吗?如有疑问,欢迎留言。