【问题标题】:Getting rid of the ServiceLocator in the base class摆脱基类中的 ServiceLocator
【发布时间】:2017-07-06 16:57:48
【问题描述】:

我有一个用于命令/查询处理程序的抽象基类:

public abstract class AbstractBusiness<TRequest, TResult> : IBusiness<TRequest, TResult>
    where TResult : BaseResponse
{
    public TResult Send(TRequest request)
    {
        TResult response = Activator.CreateInstance<TResult>();
        response.Success = true;

        try
        {
            IConnectionProvider connectionProvider =
                ServiceLocator.Current.GetInstance<IConnectionProvider>();

            using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
                connectionProvider, 
                DatabaseScopeType.UseExistingOrNewTX,
                IsolationLevel.ReadCommitted))
            {
                response = this.Handle(request);
                scope.Complete();
            }
        }
        catch (Exception exc)
        {
            response.Success = false;
            response.Message = exc.Message;
        }

        return response;
    }

    protected abstract TResult Handle(TRequest request);
}

它是这样使用的:

public sealed class AccountCreateBusiness
    : AbstractBusiness<AccountCreateRequest, AccountCreateResponse>
{
    private readonly IMessageProvider messageProvider;

    public AccountCreateBusiness(IMessageProvider messageProvider)
    {
        this.messageProvider = messageProvider;
    }

    protected override AccountCreateResponse Handle(AccountCreateRequest request)
    {
        //handle request
    }
}

我正在尝试摆脱基类中的ServiceLocator。有没有办法注入IConnectionProvider 而不必更改所有派生类以在其构造函数中包含IConnectionProvider 并调用: base(connectionProvider)?我无法更改DatabaseScopeManager

【问题讨论】:

    标签: .net dependency-injection inversion-of-control service-locator


    【解决方案1】:

    这里问题的核心是你的基类的存在。 你应该摆脱基类。删除基类将彻底解决你的问题。

    基类有问题是因为:

    • 它使每个派生类型都依赖于此类。
    • 它使测试变得复杂,因为所有横切关注点总是被执行。
    • 基类将成为一个无限增长的大类;单一职责违规。

    虽然您可以使用属性注入来摆脱服务定位器,但这并没有消除上述问题,甚至引入了一个新问题,即:Temporal Coupling

    相反,您应该简单地摆脱基类并将这些横切关注点转移到装饰器中。在您的情况下,我建议创建两个装饰器。

    TransactionBusinessDecorator:

    public class TransactionBusinessDecorator<TRequest, TResult>
        : IBusiness<TRequest, TResult>
        where TResult : BaseResponse
    {
        private readonly IConnectionProvider connectionProvider;
        private readonly IBusiness<TRequest, TResult> decoratee;
    
        public TransactionBusinessDecorator(
            IConnectionProvider connectionProvider,
            IBusiness<TRequest, TResult> decoratee)
        {
            this.connectionProvider = connectionProvider;
            this.decoratee = decoratee;
        }
    
        public TResult Send(TRequest request)
        {
            TResult response;
    
            using (DatabaseScope scope = DatabaseScopeManager.Instance.GetScope(
                this.connectionProvider, 
                DatabaseScopeType.UseExistingOrNewTX,
                IsolationLevel.ReadCommitted))
            {
                response = this.decoratee.Handle(request);
                scope.Complete();
            }
    
            return response;        
        }
    }
    

    还有一个ExceptionWrapperBusinessDecorator

    public class ExceptionWrapperBusinessDecorator<TRequest, TResult>
        : IBusiness<TRequest, TResult>
        where TResult : BaseResponse
    {
        private readonly IBusiness<TRequest, TResult> decoratee;
    
        public ExceptionWrapperBusinessDecorator(
            IBusiness<TRequest, TResult> decoratee)
        {
            this.decoratee = decoratee;
        }
    
        public TResult Send(TRequest request)
        {
            TResult response = Activator.CreateInstance<TResult>();
    
            try
            {
                var response = this.decoratee.Handle(request);
                response.Success = true;
            }
            catch (Exception ex)
            {
                response.Success = false;
                response.Message = exc.Message;
            }
    
            return response
        }
    }
    

    这使得AccountCreateBusiness 可以写成如下:

    public sealed class AccountCreateBusiness 
        : IBusiness<AccountCreateRequest, AccountCreateResponse>
    {
        private readonly IMessageProvider messageProvider;
    
        public AccountCreateBusiness(IMessageProvider messageProvider)
        {
            this.messageProvider = messageProvider;
        }
    
        public AccountCreateResponse Handle(AccountCreateRequest request)
        {
            //handle request
        }
    }
    

    用装饰器包装每个处理程序对于大多数 DI 容器来说是微不足道的,当手动执行此操作时(也称为纯 DI)。

    是最后的设计建议。捕获每个异常并将此信息作为消息的一部分返回通常是一个坏主意,因为:

    • 这些错误代码泄漏到与它无关的系统基础(您的业务层)中。不要在基本响应中提供此信息,而是使用包含此信息的信封。
    • 您将向客户提供技术信息。客户通常对这种详细程度不感兴趣。您只想返回函数消息。
    • 返回安全敏感信息;黑客喜欢这类信息。
    • 如果您不记录此信息,则会丢失许多重要的错误详细信息。
    • 基本上返回错误代码,而不是处理异常。错误代码很容易出错;客户端需要始终检查结果以查看响应是否成功。

    【讨论】:

      【解决方案2】:

      还有一个替代方案:属性注入

      顺便说一句,属性注入没有定义强制依赖,而构造函数依赖则定义了。

      【讨论】:

      • 财产注入然而导致Temporal Coupling
      • @steven BTW 当您使用容器时,很有可能最终会出现未在属性上设置依赖项的情况......如果您没有配置某些组件,可能会发生这种情况,对吧?
      • 正确。构造函数注入器为我们提供保证对象在构造后正确设置。我们通过属性注入失去了这种能力。
      • @Steven 是的,当然。就我而言,我从不在我的项目中使用属性注入。感觉就像是为 OP 暴露的情况而设计的解决方法......
      • @Steven 我看到您已尝试解决根本问题。可能你的答案是正确的。如果您在其他问答中遵循了我的回答,通常我不会使用 OP 的预期解决方案来回答,但我会尝试像您一样解决潜在问题......也许我需要放弃我的回答t 混淆 OP,以便 OP 找到正确的解决方案。
      【解决方案3】:

      正如 Matías Fidemraizer 所提到的,您可以使用属性注入,这是一种避免破坏性更改的非常安全的方法。缺点是可选中的属性依赖,但在你的代码中IConnectionProvider必须的

      从长远来看,我会选择两个构造函数:

      1. 默认的仍然使用服务定位器(为了向后兼容),但标记为[Obsolete],表示它将在下一个主要版本中被删除。
      2. IConnectionProvider 作为必备依赖项的构造函数。这个应该被推荐使用,并且一旦默认构造函数的所有用法都被消除,就应该成为唯一的一个。

      【讨论】:

      • 不幸的是,这导致了Multiple Constructors anti-pattern
      • 这就是为什么我建议将 newtemporary 默认构造函数标记为过时的原因。这只是偿还技术债务的一种方式(作者说现在引入重大更改不是一种选择)。
      • 我同意您的解决方案作为临时解决方案,因为在处理遗留代码时,允许每个中间步骤进入更好的位置。但是,从“长期运行的角度”来看,我不同意您的解决方案,因为最终这个构造函数应该消失了。
      • 没错!这正是我想说的。构造函数应该在下一个主要版本中消失。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-31
      • 2023-04-03
      • 1970-01-01
      • 2012-02-17
      相关资源
      最近更新 更多