聚合根应使用接口还是类?附代码示例及选型困惑
选择聚合根实现方案:抽象类 vs 标记接口
我在做DDD项目的时候也纠结过类似的问题,这俩方案各有适用场景,得结合你的项目需求来选,下面给你拆解一下:
一、带领域事件的抽象类 AggregateRoot
优点
- 开箱即用的一致性:所有继承它的聚合根直接拥有领域事件的管理能力,不用重复写
_domainEvents集合、AddDomainEvent、ClearEvents这些基础代码,能保证所有聚合根的事件处理逻辑统一,避免出现有的聚合根忘了实现ClearEvents导致事件重复发布的坑。 - 符合DDD实践:领域事件本来就是聚合根的核心能力之一,大多数DDD项目里,几乎所有聚合根都会产生或处理领域事件,这种抽象类能帮你省掉大量重复劳动。
缺点
- 单继承限制:C#是单继承语言,如果你的某个聚合根因为特殊业务需求必须继承另一个类(虽然在DDD的规范里这种情况很少见,但不排除例外),这个抽象类就会让你陷入两难。
- 冗余代码:如果有部分聚合根完全不需要处理领域事件,继承这个抽象类就会带上无用的事件管理代码,有点多余。
二、标记接口 IAggregateRoot
优点
- 极致灵活:只是一个标记,没有任何继承限制,你可以给任何类加上这个接口来标识它是聚合根,不管它有没有领域事件,也不管它是否继承了其他类。
- 架构层面的约定:可以在仓储层、领域服务层用这个接口做统一的筛选或处理,比如仓储只处理实现了
IAggregateRoot的对象,方便架构扩展。
缺点
- 重复代码风险:每个需要领域事件的聚合根都得自己实现一遍事件管理逻辑,很容易出现不一致的实现——比如有的用
List,有的用HashSet,或者有的聚合根漏写了ClearEvents方法,后期维护成本高。
三、怎么选?
- 优先选抽象类(大多数场景):如果你的项目里所有/绝大多数聚合根都需要处理领域事件,而且没有多继承的需求,直接用那个带领域事件的抽象类就好。这是DDD项目里最常见的做法,能帮你减少重复工作,保证事件处理的一致性。
- 选标记接口(特殊场景):如果你的聚合根需求差异很大,有些完全不需要领域事件,或者有聚合根必须继承其他类,那标记接口更合适。另外,如果你用的是C# 8及以上版本,还可以给接口加默认实现,或者结合扩展方法来补上事件管理的逻辑,兼顾灵活性和代码复用:
public interface IAggregateRoot { IReadOnlyList<IDomainEvent> DomainEvents { get; } void AddDomainEvent(IDomainEvent newEvent); void ClearEvents(); } // 用扩展方法实现默认逻辑 public static class AggregateRootExtensions { private static readonly ConditionalWeakTable<IAggregateRoot, List<IDomainEvent>> _eventStore = new(); public static IReadOnlyList<IDomainEvent> DomainEvents(this IAggregateRoot root) { return _eventStore.TryGetValue(root, out var events) ? events.AsReadOnly() : Array.Empty<IDomainEvent>(); } public static void AddDomainEvent(this IAggregateRoot root, IDomainEvent newEvent) { var events = _eventStore.GetOrCreateValue(root); events.Add(newEvent); } public static void ClearEvents(this IAggregateRoot root) { if (_eventStore.TryGetValue(root, out var events)) { events.Clear(); } } }
这种方式既保留了接口的灵活性,又避免了重复写事件管理代码,适合需要兼顾多种场景的项目。
内容的提问来源于stack exchange,提问作者w0051977
相关产品推荐
相关产品推荐

