使用模板模式(继承)实现依赖注入的优劣及强制依赖可行性分析
刚好对这个场景有过实践,来跟你聊聊我的看法:
基于继承的模板模式实现依赖注入的优缺点
优点
- 强制依赖统一:所有子类必须遵循父类定义的依赖注入规则,像你提到的
IBusinessContext,能确保每个业务服务都能拿到这个依赖,不会出现漏加的情况。 - 减少重复代码:父类可以把依赖注入的通用逻辑(比如构造函数注入的模板、依赖校验)统一实现,子类只需要专注自己的业务逻辑,不用重复写相同的注入代码。
- 标准化流程:可以在父类中定义固定的初始化或执行流程(比如拿到上下文后做统一的合法性校验),子类继承后自动遵循这套流程,避免各子类逻辑混乱。
缺点
- 强耦合陷阱:子类和父类绑定得太死,一旦父类的依赖或逻辑变更,所有子类都会受影响,违反了开闭原则,后期维护成本会很高。
- 单继承限制:如果你的业务服务类已经需要继承其他类(比如框架提供的基类),就没法再继承这个模板基类了,灵活性大打折扣。
- 依赖隐藏:子类的依赖是通过父类注入的,从子类的构造函数或接口上看不到对
IBusinessContext的依赖,可读性差,新人接手可能会疑惑上下文是从哪来的。 - 测试复杂度上升:测试子类时,必须先初始化父类的所有依赖,哪怕子类没用到父类的某些逻辑,也得处理这些依赖,增加了测试的工作量。
用模板模式(继承)强制业务服务依赖IBusinessContext的可行方案
完全可以通过这种方式实现强制依赖,核心思路是用一个抽象基类封装IBusinessContext的注入逻辑,所有业务服务类都继承这个基类,通过构造函数的强制调用实现依赖绑定。
代码示例
首先定义上下文接口和模板基类:
// 业务上下文接口 public interface IBusinessContext { string CurrentUserId { get; } // 其他上下文属性,比如当前租户、请求ID等 } // 业务服务抽象基类,强制依赖IBusinessContext public abstract class BaseBusinessService { protected readonly IBusinessContext _businessContext; // 构造函数注入IBusinessContext,子类必须调用此构造函数 protected BaseBusinessService(IBusinessContext businessContext) { _businessContext = businessContext ?? throw new ArgumentNullException(nameof(businessContext)); } // 可选:定义模板方法,统一业务服务的执行流程 public virtual void Execute() { PreExecute(); DoBusinessLogic(); PostExecute(); } // 统一前置处理,比如上下文校验 protected virtual void PreExecute() { if (string.IsNullOrEmpty(_businessContext.CurrentUserId)) { throw new InvalidOperationException("用户上下文未初始化,请先登录"); } } // 子类必须实现的核心业务逻辑 protected abstract void DoBusinessLogic(); // 统一后置处理,比如日志记录 protected virtual void PostExecute() { // 可以在这里统一记录业务执行日志 } }
然后实现具体的业务服务:
// 安全服务接口 public interface ISecurityService { bool HasPermission(string permissionCode); } // 安全服务实现,继承模板基类,强制依赖IBusinessContext public class SecurityService : BaseBusinessService, ISecurityService { // 子类构造函数必须调用父类构造函数,从而强制注入IBusinessContext public SecurityService(IBusinessContext businessContext) : base(businessContext) { } public bool HasPermission(string permissionCode) { // 使用父类注入的上下文获取当前用户ID,执行权限校验 var currentUserId = _businessContext.CurrentUserId; // 具体权限校验逻辑,比如查询数据库判断权限 return true; } // 如果需要使用模板方法Execute,重写核心逻辑 protected override void DoBusinessLogic() { // 这里可以实现对应业务的执行逻辑 } }
方案关键点
- 强制依赖的保障:因为
BaseBusinessService的构造函数是protected,且必须传入IBusinessContext,子类如果不调用base(businessContext)会直接编译报错,从语法层面强制了所有业务服务类都必须依赖IBusinessContext。 - 流程管控:通过模板方法(比如
Execute)可以统一所有业务服务的执行流程,前置校验、后置处理都在父类统一实现,子类只需要关注核心业务逻辑。
注意事项
- 要警惕继承带来的耦合问题,如果后续业务服务需要继承其他类,这个方案就会因为单继承限制而失效,此时可以考虑用组合模式替代继承来实现类似的效果。
- 如果使用依赖注入容器(比如Autofac、Microsoft.Extensions.DependencyInjection),需要确保容器已正确注册
IBusinessContext的实现类,这样所有子类在被容器解析时都会自动注入IBusinessContext。
内容的提问来源于stack exchange,提问作者Param
相关产品推荐
相关产品推荐

