ZetCode

Dart UnmodifiableMapBase

最后修改于 2025 年 4 月 4 日

在 Dart 中,UnmodifiableMapBase 是一个抽象基类,用于创建不可变 Map 视图。它提供对 Map 数据的只读访问,同时阻止修改。

UnmodifiableMapBase 实现 Map 接口,但会对所有修改操作抛出 UnsupportedError。它对于安全地公开 Map 数据而不允许更改非常有用。

UnmodifiableMapBase 基本用法

下面是如何通过继承 UnmodifiableMapBase 来创建一个简单的不可变 Map。

main.dart
import 'dart:collection';

class UnmodifiableMapView<K, V> extends UnmodifiableMapBase<K, V> {
  final Map<K, V> _source;

  UnmodifiableMapView(this._source);

  @override
  V? operator [](Object? key) => _source[key];

  @override
  Iterable<K> get keys => _source.keys;
}

void main() {

  final source = {'a': 1, 'b': 2, 'c': 3};
  final unmodifiable = UnmodifiableMapView(source);

  print(unmodifiable['a']); // 1
  print(unmodifiable.length); // 3

  try {
    unmodifiable['d'] = 4; // Throws
  } catch (e) {
    print('Error: $e');
  }
}

我们创建一个 UnmodifiableMapView 类来包装一个源 Map。必须实现 [] 操作符和 keys getter。尝试修改将抛出异常。

$ dart main.dart
1
3
Error: Unsupported operation: Cannot modify unmodifiable map

使用工厂创建不可变 Map

一种更实用的方法是使用工厂方法来创建不可变 Map。

main.dart
import 'dart:collection';

class UnmodifiableMapFactory {
  static Map<K, V> create<K, V>(Map<K, V> source) {
    return _UnmodifiableMapWrapper(source);
  }
}

class _UnmodifiableMapWrapper<K, V> extends UnmodifiableMapBase<K, V> {
  final Map<K, V> _source;

  _UnmodifiableMapWrapper(this._source);

  @override
  V? operator [](Object? key) => _source[key];

  @override
  Iterable<K> get keys => _source.keys;
}

void main() {

  final source = {'x': 10, 'y': 20, 'z': 30};
  final unmodifiable = UnmodifiableMapFactory.create(source);

  print(unmodifiable['y']); // 20
  print(unmodifiable.containsKey('x')); // true

  try {
    unmodifiable.clear(); // Throws
  } catch (e) {
    print('Error: $e');
  }
}

此示例展示了创建不可变 Map 的工厂模式。私有的 _UnmodifiableMapWrapper 类实现了不可变行为。

$ dart main.dart
20
true
Error: Unsupported operation: Cannot modify unmodifiable map

具有自定义键值类型的不可变 Map

UnmodifiableMapBase 可与自定义类型一起使用,同时保持不可变性。

main.dart
import 'dart:collection';

class Product {
  final String id;
  final String name;
  
  Product(this.id, this.name);
  
  @override
  String toString() => 'Product($id, $name)';
}

class UnmodifiableProductMap extends UnmodifiableMapBase<String, Product> {
  final Map<String, Product> _products;
  
  UnmodifiableProductMap(this._products);
  
  @override
  Product? operator [](Object? key) => _products[key];
  
  @override
  Iterable<String> get keys => _products.keys;
}

void main() {

  final products = {
    'p1': Product('p1', 'Laptop'),
    'p2': Product('p2', 'Phone'),
    'p3': Product('p3', 'Tablet')
  };
  
  final unmodifiable = UnmodifiableProductMap(products);
  
  print(unmodifiable['p2']); // Product(p2, Phone)
  print(unmodifiable.values.where((p) => p.name.length > 5).toList());
  
  try {
    unmodifiable['p4'] = Product('p4', 'Monitor'); // Throws
  } catch (e) {
    print('Modification error: $e');
  }
}

我们为 Product 对象创建了一个类型安全的不可变 Map。该 Map 提供只读访问,同时阻止对产品集合的任何修改。

$ dart main.dart
Product(p2, Phone)
[Product(p1, Laptop), Product(p3, Tablet)]
Modification error: Unsupported operation: Cannot modify unmodifiable map

将 UnmodifiableMapBase 与其他集合结合使用

UnmodifiableMapBase 可以与其他集合类型结合使用,以创建复杂的不可变数据结构。

main.dart
import 'dart:collection';

class ImmutableConfig extends UnmodifiableMapBase<String, dynamic> {
  final Map<String, dynamic> _config;
  final List<String> _log;
  
  ImmutableConfig(this._config, this._log);
  
  @override
  dynamic operator [](Object? key) {
    _log.add('Accessed: $key');
    return _config[key];
  }
  
  @override
  Iterable<String> get keys => _config.keys;
}

void main() {

  final config = {
    'timeout': 30,
    'retries': 3,
    'debug': false
  };
  
  final accessLog = <String>[];
  final immutableConfig = ImmutableConfig(config, accessLog);
  
  print(immutableConfig['timeout']); // 30
  print(immutableConfig['debug']);   // false
  
  try {
    immutableConfig['timeout'] = 60; // Throws
  } catch (e) {
    print('Error: $e');
  }
  
  print('Access log: $accessLog');
}

此示例将一个不可变 Map 与一个日志列表结合使用。Map 保持不可变,同时跟踪访问尝试。日志列表可以单独访问。

$ dart main.dart
30
false
Error: Unsupported operation: Cannot modify unmodifiable map
Access log: [Accessed: timeout, Accessed: debug]

带懒加载的高级 UnmodifiableMapBase

我们可以通过重写访问器来实现不可变 Map 中的懒加载。

main.dart
import 'dart:collection';

class LazyUnmodifiableMap extends UnmodifiableMapBase<String, String> {
  final Map<String, String> _source;
  final Map<String, String> _cache = {};
  
  LazyUnmodifiableMap(this._source);
  
  @override
  String? operator [](Object? key) {
    if (key is! String) return null;
    
    if (!_cache.containsKey(key)) {
      print('Loading $key from source...');
      _cache[key] = _source[key] ?? 'DEFAULT';
    }
    
    return _cache[key];
  }
  
  @override
  Iterable<String> get keys => _source.keys;
}

void main() {

  final source = {
    'config1': 'Value1',
    'config2': 'Value2',
    'config3': 'Value3'
  };
  
  final lazyMap = LazyUnmodifiableMap(source);
  
  print('First access:');
  print(lazyMap['config2']); // Loads and caches
  
  print('\nSecond access:');
  print(lazyMap['config2']); // Uses cache
  
  print('\nAll keys:');
  print(lazyMap.keys.join(', '));
  
  try {
    lazyMap['new'] = 'value'; // Throws
  } catch (e) {
    print('\nError: $e');
  }
}

这个高级示例使用缓存实现了懒加载。值在第一次访问时加载,并在后续请求中缓存。Map 保持不可变。

$ dart main.dart
First access:
Loading config2 from source...
Value2

Second access:
Value2

All keys:
config1, config2, config3

Error: Unsupported operation: Cannot modify unmodifiable map

最佳实践

来源

Dart UnmodifiableMapBase 文档

本教程通过实际示例介绍了 Dart 的 UnmodifiableMapBase,演示了不可变 Map 的实现和模式。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出 所有 Dart 教程