ZetCode

Dart 中的工厂构造函数

最后修改于 2025 年 6 月 8 日

本教程将探讨 Dart 中的工厂构造函数,这是一种特殊的构造函数,可提供灵活的对象创建机制。与生成构造函数不同,工厂构造函数可以从缓存、子类甚至完全不同的类型返回实例。

工厂构造函数概述

工厂构造函数使用 factory 关键字声明,并且在几个关键方面与常规构造函数不同。它们不直接创建新实例,而是控制实例化过程。

工厂构造函数的主要特性

功能 工厂构造函数 生成构造函数
实例化 控制对象创建 始终创建新实例
返回 可以返回现有/缓存的实例 始终返回新实例
子类 可以返回子类实例 仅返回当前类
初始化列表 无法访问 this 可以使用初始化列表

工厂构造函数通常用于实现单例、工厂方法或对象池等设计模式。当使用需要灵活构造的不可变类或进行反序列化时,它们也很有用。

基本工厂构造函数

本示例演示了一个实现先前创建实例缓存的简单工厂构造函数。Logger 类确保每个标签只有一个实例。

basic_factory.dart
class Logger {
  final String tag;
  static final Map<String, Logger> _cache = {};

  // Private generative constructor
  Logger._internal(this.tag);

  // Factory constructor
  factory Logger(String tag) {
    return _cache.putIfAbsent(tag, () => Logger._internal(tag));
  }

  void log(String message) {
    print('[$tag] $message');
  }
}

void main() {
  var logger1 = Logger('main');
  var logger2 = Logger('main');
  var logger3 = Logger('network');

  logger1.log('Hello There');
  logger2.log('Hello Again');

  print('logger1 and logger2 are the same: ${identical(logger1, logger2)}');
  print('logger1 and logger3 are the same: ${identical(logger1, logger3)}');
}

Logger 类使用私有的生成构造函数(_internal)和一个公共工厂构造函数。工厂维护实例缓存,并在请求具有相同标签的实例时返回现有实例。

putIfAbsent 方法确保仅在需要时线程安全地创建新实例。此模式对于管理昂贵的资源或为每个唯一标识符维护单个实例很有用。

$ dart run basic_factory.dart
[main] Hello There
[main] Hello Again
logger1 and logger2 are the same: true
logger1 and logger3 are the same: false

带子类实例化的工厂

工厂构造函数可以返回子类的实例。本示例展示了一个 Shape 工厂,它根据输入参数创建不同的形状类型。

subclass_factory.dart
abstract class Shape {
  double get area;
  
  factory Shape(String type, double size) {
    switch (type.toLowerCase()) {
      case 'circle':
        return Circle(size);
      case 'square':
        return Square(size);
      default:
        throw ArgumentError('Unknown shape type: $type');
    }
  }
}

class Circle implements Shape {
  final double radius;
  
  Circle(this.radius);
  
  @override
  double get area => 3.14159 * radius * radius;
  
  @override
  String toString() => 'Circle(radius: $radius)';
}

class Square implements Shape {
  final double side;
  
  Square(this.side);
  
  @override
  double get area => side * side;
  
  @override
  String toString() => 'Square(side: $side)';
}

void main() {
  var circle = Shape('circle', 5.0);
  var square = Shape('square', 4.0);
  
  print(circle);
  print('Area: ${circle.area}');
  
  print(square);
  print('Area: ${square.area}');
  
  try {
    var triangle = Shape('triangle', 3.0);
    print(triangle.area);
  } catch (e) {
    print('Error: $e');
  }
}

Shape 抽象类定义了一个充当简单工厂方法的工厂构造函数。根据输入类型,它创建并返回 CircleSquare 实例。

此模式将具体实现隐藏在客户端之外,客户端仅与抽象 Shape 接口交互。工厂负责实例化逻辑并验证输入参数。

$ dart run subclass_factory.dart
Circle(radius: 5.0)
Area: 78.53975
Square(side: 4.0)
Area: 16.0
Error: ArgumentError: Unknown shape type: triangle

JSON 反序列化工厂

工厂构造函数通常用于 JSON 反序列化。本示例展示了一个 User 类,其中有一个工厂可以从 JSON 映射创建实例。

json_factory.dart
class User {
  final String id;
  final String name;
  final int age;
  final DateTime signUpDate;

  // Private generative constructor
  User._({
    required this.id,
    required this.name,
    required this.age,
    required this.signUpDate,
  });

  // Factory for JSON deserialization
  factory User.fromJson(Map<String, dynamic> json) {
    if (!json.containsKey('id') ||
        !json.containsKey('name') ||
        !json.containsKey('age') ||
        !json.containsKey('signUpDate')) {
      throw ArgumentError('Missing required fields in JSON');
    }

    return User._(
      id: json['id'] as String,
      name: json['name'] as String,
      age: json['age'] as int,
      signUpDate: DateTime.parse(json['signUpDate'] as String),
    );
  }

  @override
  String toString() => 'User($id, $name, $age, ${signUpDate.toLocal()})';

}

void main() {
  var json = {
    'id': 'u123',
    'name': 'Alice',
    'age': 30,
    'signUpDate': '2023-05-25T10:30:00Z',
  };

  var user = User.fromJson(json);
  print(user);

  // Handle potential invalid JSON
  try {
    var badJson = {'id': 'u124', 'name': 'Bob'};
    var invalidUser = User.fromJson(badJson);
    print(invalidUser.name);
  } catch (e) {
    print('Error creating user: $e');
  }
}

User.fromJson 工厂验证 JSON 数据并将其转换为类型正确的 User 对象。它使用私有构造函数来确保所有字段都得到正确初始化。

当与 API 或持久化存储配合使用时,此模式特别有用。工厂可以处理数据验证、类型转换,并为无效输入提供有意义的错误消息。

$ dart run json_factory.dart
User(id: u123, name: Alice, age: 30, signed up: 2023-05-25 10:30:00.000)
Error creating user: type 'Null' is not a subtype of type 'int' in type cast

带工厂的单例模式

工厂构造函数可以实现单例模式,确保类只有一个实例。本示例展示了 Dart 中线程安全的单例。

singleton_factory.dart
class AppConfig {
  final String environment;
  final String apiUrl;

  // Private static instance
  static AppConfig? _instance;

  // Private generative constructor
  AppConfig._({
    required this.environment,
    required this.apiUrl,
  });

  // Factory constructor for Singleton access
  factory AppConfig.getInstance() {
    return _instance ??= AppConfig._(
      environment: 'production',
      apiUrl: 'https://api.example.com',
    );
  }

  // Factory with lazy initialization
  factory AppConfig({String? environment, String? apiUrl}) {
    _instance ??= AppConfig._(
      environment: environment ?? 'development',
      apiUrl: apiUrl ?? 'https://dev.api.example.com',
    );
    return _instance!;
  }

  void printConfig() {
    print('Environment: $environment');
    print('API URL: $apiUrl');
  }
}

void main() {
  var config1 = AppConfig.getInstance();
  var config2 = AppConfig.getInstance();

  print('config1 and config2 are the same: ${identical(config1, config2)}');
  config1.printConfig();

  // Alternative initialization
  var devConfig = AppConfig(environment: 'staging');
  print('\nDev config:');
  devConfig.printConfig();
}

AppConfig 类演示了单例实现的两种方法。getInstance 工厂提供严格的单例访问,而命名工厂允许一次性配置。

空感知赋值运算符(??=)确保了线程安全的惰性初始化。此模式对于全局配置或共享资源很有用,在这些资源中,多个实例可能会导致问题。

$ dart run singleton_factory.dart
config1 and config2 are the same: true
Environment: production
API URL: https://api.example.com

Dev config:
Environment: staging
API URL: https://dev.api.example.com

复杂对象构造

工厂构造函数可以封装复杂的创建逻辑。本示例展示了一个 Document 工厂,它根据文件内容分析创建不同的文档类型。

complex_factory.dart
abstract class Document {
  final String path;
  final String content;
  
  Document(this.path, this.content);
  
  factory Document.load(String path) {
    // Simulate file reading
    var content = _readFileContent(path);
    
    // Determine document type
    if (path.endsWith('.md') || content.startsWith('#')) {
      return MarkdownDocument(path, content);
    } else if (path.endsWith('.html') || content.contains('<html>')) {
      return HtmlDocument(path, content);
    } else {
      return PlainTextDocument(path, content);
    }
  }
  
  static String _readFileContent(String path) {
    // Simulated file content based on path
    if (path.endsWith('.md')) {
      return '# Markdown Header\n\nContent here';
    } else if (path.endsWith('.html')) {
      return '<html><body>HTML Content</body></html>';
    } else {
      return 'Plain text content';
    }
  }
  
  void display();
}

class MarkdownDocument extends Document {
  MarkdownDocument(super.path, super.content);
  
  @override
  void display() {
    print('Rendering Markdown: ${content.substring(0, 15)}...');
  }
}

class HtmlDocument extends Document {
  HtmlDocument(super.path, super.content);
  
  @override
  void display() {
    print('Rendering HTML: ${content.substring(0, 15)}...');
  }
}

class PlainTextDocument extends Document {
  PlainTextDocument(super.path, super.content);
  
  @override
  void display() {
    print('Displaying text: ${content.substring(0, 15)}...');
  }
}

void main() {
  var documents = [
    Document.load('document.md'),
    Document.load('page.html'),
    Document.load('notes.txt'),
  ];
  
  for (var doc in documents) {
    doc.display();
  }
}

Document.load 工厂会分析文件扩展名和内容,以确定合适的文档类型。这封装了复杂的创建逻辑,同时为客户端提供了简单的接口。

工厂处理关于实例化哪个具体类的所有决策,允许在不更改客户端代码的情况下添加新的文档类型。这是工厂方法设计模式的一个示例。

$ dart run complex_factory.dart
Rendering Markdown: # Markdown Head...
Rendering HTML: <html><body>HTM...
Displaying text: Plain text con...

来源

Dart 工厂构造函数
Dart 语言之旅:工厂

工厂构造函数是 Dart 中的一项强大功能,可实现灵活的对象创建模式。它们可以管理对象缓存、实现设计模式、处理反序列化以及封装复杂的实例化逻辑。通过正确使用工厂构造函数,您可以创建更易于维护和更灵活的 Dart 代码。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出 所有 Dart 教程