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
相关产品推荐
相关产品推荐

