【问题标题】:Architecture problem: use of dependency injection resulting in rubbish API架构问题:使用依赖注入导致垃圾 API
【发布时间】:2011-03-11 11:04:02
【问题描述】:

我正在尝试创建一个类,该类执行各种与数据库相关的低级操作,但为 UI 层提供了一个非常简单的接口。

这个类代表了一堆数据,它们都在一个特定的聚合根中,由单个 ID int 检索。

构造函数有四个参数:

public AssetRegister(int caseNumber, ILawbaseAssetRepository lawbaseAssetRepository, IAssetChecklistKctcPartRepository assetChecklistKctcPartRepository, User user)
{
  _caseNumber = caseNumber;
  _lawbaseAssetRepository = lawbaseAssetRepository;
  _assetChecklistKctcPartRepository = assetChecklistKctcPartRepository;
  _user = user;
  LoadChecklists();
}

UI层通过接口IAssetRegister访问这个类。 Castle Windsor 可以自己提供 ILawbaseAssetRepository 和 IAssetChecklistKctcPartRepository 参数,但 UI 代码需要使用匿名类型提供另外两个,如下所示:

int caseNumber = 1000;
User user = GetUserFromPage();
IAssetRegister assetRegister = Moose.Application.WindsorContainer.Resolve<IAssetRegister>(new { caseNumber, user});

从 API 设计的角度来看,这是垃圾。 UI 层开发人员无法知道 IAssetRegister 需要一个整数和一个用户。他们需要知道类的实现才能使用它。

我知道我这里一定有某种设计问题。谁能指点一下?

【问题讨论】:

  • 仅对可注入的依赖项使用构造函数注入。将手动赋值的值移动为方法参数或在构造后提供设置方法。
  • 哈哈@thephpdeveloper 的编辑!
  • 是的,那个编辑也让我发笑。不知道你是否真的应该以这种方式“与作者的风格个性混为一谈”:)
  • 导致垃圾 API 的不是 DI。你用的是SL,这是常见的气味。不要做SL。
  • 我从来没有说 DI 本身会导致垃圾 API,只是我(误)使用它。

标签: c# api architecture dependency-injection castle-windsor


【解决方案1】:

尝试将消息与行为分开。创建一个包含操作数据的类,并创建一个包含该操作的业务逻辑的不同类。例如,创建这个命令:

public class RegisterAssetCommand
{
    [Required]
    public int CaseNumber { get; set; }

    [Required]
    public User Operator { get; set; }
}

现在定义一个处理业务命令的接口:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

您的演示文稿代码现在将如下所示:

var command = new RegisterAssetCommand
{
    CaseNumber = 1000,
    Operator = GetUserFromPage(),
};

var commandHandler = WindsorContainer
    .Resolve<ICommandHandler<RegisterAssetCommand>);

commandHandler.Handle(command);

注意: 如果可能,将获取commandHandler 的职责移出表示类并将其注入该类的构造函数(再次构造函数注入)。

不,您可以像这样创建ICommandHandler&lt;RegisterAssetCommand&gt; 的实现:

public class RegisterAssetCommandHandler
    : ICommandHandler<RegisterAssetCommand>
{
    private ILawbaseAssetRepository lawbaseAssetRepository;
    private IAssetChecklistKctcPartRepository assetRepository;

    public RegisterAssetCommandHandler(
        ILawbaseAssetRepository lawbaseAssetRepository,
        IAssetChecklistKctcPartRepository assetRepository)
    {
        this.lawbaseAssetRepository = lawbaseAssetRepository;
        this.assetRepository = assetRepository;
    }

    public void Handle(RegisterAssetCommand command)
    {
        // Optionally validate the command

        // Execute the command
    }
}

或者,您甚至可以通过在RegisterAssetCommandHandler 中注入IUserProviderUser 排除在RegisterAssetCommand 之外。 IUserProvider 接口可以有一个处理程序可以调用的GetUserForCurrentContext

我希望这是有道理的。

【讨论】:

  • 对不起,我看不懂这段代码。我需要更多的勺子喂养。如何获取 IAssetRegister 实例?
  • 你的IAssetRegister 不见了。它分为两类:RegisterAssetCommandICommandHandler&lt;RegisterAssetCommand&gt;。您在IAssetRegister 的实现中所做的代码应该放入RegisterAssetCommandHandler 类的Handle 方法中。
  • 哦,我想我明白了。您认为 IAssetRegister 只是执行一些命令。事实上,它基本上就像一个实体——它包含状态。话虽如此,我认为问题在于设计 - 我需要一个存储库来为我提供资产登记册。然后我可以将案例编号和用户提供给存储库的 Load 方法。
  • 啊,我明白了。让您的实体依赖于服务通常是个坏主意,但我想您已经意识到这一点,因为这就是您的问题所在。
  • 顺便说一句,你的博客看起来很棒。喝了一杯咖啡后,我会尝试一下! :)
【解决方案2】:

正如 Morten 指出的,将不可注入的依赖项从构造函数调用移至实际需要使用它的方法,

如果您有无法(或难以)注入的构造函数参数,您将无法自动将 IAssetRegister 注入到任何需要它的类中。

当然,您总是可以创建一个 IUserProvider 接口,并按照以下思路进行具体实现:

public class UserProvider : IUserProvider 
{
    // interface method
    public User GetUser() 
    {
        // you obviously don't want a page dependency here but ok...
        return GetUserFromPage();
    }
}

因此创建了另一个没有的可注入依赖项。现在,您无需将用户传递给可能需要它的每个方法。

【讨论】:

  • UserProvider 的想法可以为我解决几个问题。谢谢。
  • 好的,我解决了这个问题。我正在使用 IUserProvider 类型接口来获取当前用户(当然是注入的),并通过方法调用而不是构造函数提供案例编号。现在我的 API 不需要开发人员的任何猜测。谢谢大家!
  • 哦,IUserProvider 的概念也为我解决了这个问题:stackoverflow.com/questions/5192529/…
  • 这超出了我的预期;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-31
  • 2023-04-04
  • 1970-01-01
  • 2015-11-21
  • 1970-01-01
  • 2020-02-01
  • 1970-01-01
相关资源
最近更新 更多