You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clean Architecture中多态场景下的实体与仓储设计疑问

整洁架构下多态实体的仓储设计方案

针对你遇到的兽医宠物存储场景,核心矛盾是既要遵循整洁架构的依赖倒置原则(实体不依赖仓储),又要避免大量类型判断破坏多态性。以下是几种合理的解决方案:

方案1:用访问者模式封装仓储操作

访问者模式能在不修改实体类的前提下,为不同类型的实体添加特定操作,完美适配你的场景:

步骤1:定义访问者抽象

abstract class AnimalVisitor {
  Future<void> visitDog(Dog dog);
  Future<void> visitCat(Cat cat);
  Future<void> visitSnake(Snake snake);
  // 新增宠物类型时扩展这里
}

步骤2:让Animal抽象类接受访问者

abstract class Animal {
  String name;
  // 实体只依赖抽象访问者,不依赖具体仓储
  Future<void> accept(AnimalVisitor visitor);
}

步骤3:实现具体实体的accept方法

class Dog extends Animal {
  String breed; // Dog专属属性

  @override
  Future<void> accept(AnimalVisitor visitor) {
    return visitor.visitDog(this);
  }
}

class Cat extends Animal {
  bool hasClaws; // Cat专属属性

  @override
  Future<void> accept(AnimalVisitor visitor) {
    return visitor.visitCat(this);
  }
}

步骤4:实现仓储访问者,封装具体存储逻辑

class AnimalStorageVisitor implements AnimalVisitor {
  // 持有具体仓储(仓储属于外层,访问者也属于外层,符合依赖方向)
  final DogRepository _dogRepo;
  final CatRepository _catRepo;
  final SnakeRepository _snakeRepo;

  AnimalStorageVisitor(this._dogRepo, this._catRepo, this._snakeRepo);

  @override
  Future<void> visitDog(Dog dog) => _dogRepo.save(dog);

  @override
  Future<void> visitCat(Cat cat) => _catRepo.save(cat);

  @override
  Future<void> visitSnake(Snake snake) => _snakeRepo.save(snake);
}

调用方式

// 初始化访问者
final storageVisitor = AnimalStorageVisitor(DogRepository(), CatRepository(), SnakeRepository());

// 保存任意Animal,无需类型判断
Future<void> saveAnimal(Animal animal) async {
  await animal.accept(storageVisitor);
}

这种方式完全符合整洁架构:实体(Animal子类)只依赖抽象的访问者,不直接依赖仓储;所有存储逻辑都放在外层的访问者和仓储中,同时彻底避免了类型判断,新增宠物类型时只需扩展访问者接口和实现,符合开闭原则。

方案2:通用仓储+类型映射(轻量替代方案)

如果觉得访问者模式稍显复杂,也可以用类型映射的方式简化,同时减少条件判断:

步骤1:定义通用仓储抽象

abstract class AnimalRepository {
  Future<void> save(Animal animal);
}

步骤2:实现通用仓储,内部维护类型-仓储映射

class AnimalRepositoryImpl implements AnimalRepository {
  final Map<Type, dynamic> _repoMap = {};

  // 初始化时注册各类型对应的仓储
  AnimalRepositoryImpl() {
    _repoMap[Dog] = DogRepository();
    _repoMap[Cat] = CatRepository();
    _repoMap[Snake] = SnakeRepository();
  }

  @override
  Future<void> save(Animal animal) async {
    final repo = _repoMap[animal.runtimeType];
    if (repo == null) {
      throw Exception('Unsupported animal type');
    }
    await repo.save(animal);
  }
}

调用方式

final animalRepo = AnimalRepositoryImpl();
await animalRepo.save(anyAnimal);

这种方式比一堆if判断更优雅,新增类型时只需在映射中添加对应仓储即可,缺点是需要用runtimeType做类型匹配,不如访问者模式类型安全,但胜在简单易实现。

关键原则回顾

  • 永远不要让实体依赖仓储:实体属于整洁架构的最内层,只能依赖同层的抽象,不能依赖外层的仓储、UI等组件。
  • 用多态/设计模式替代条件判断:这正是Uncle Bob强调的,避免代码变成“条件判断地狱”,同时提升扩展性。

内容的提问来源于stack exchange,提问作者salim.elkh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:23:10