架构问题:使用dependency injection导致垃圾API
我想创建一个类来执行各种与数据库相关的低级操作,但是它提供了一个非常简单的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(new { caseNumber, user});
从API设计的角度来看,这是垃圾。 UI层开发人员无法知道IAssetRegister需要整数和用户。 他们需要知道类的实现才能使用它。
我知道我必须在这里遇到某种设计问题。 任何人都可以给我一些指示吗?
正如Morten所指出的那样,将非注入式依赖从构造函数调用移动到实际需要使用它的方法,
如果你有不能(或很难)注入的构造函数参数,你将无法自动将IAssetRegister
注入任何需要它的类。
当然,您总是可以创建一个IUserProvider
接口,其中包含以下IUserProvider
行的具体实现:
public class UserProvider : IUserProvider { // interface method public User GetUser() { // you obviously don't want a page dependency here but ok... return GetUserFromPage(); } }
因此创建另一个可注射的依赖项,其中没有。 现在,您无需将用户传递给可能需要它的每个方法。
尝试将消息与行为分开。 创建一个包含操作数据的类,并创建一个包含该操作的业务逻辑的不同类。 例如,创建此命令:
public class RegisterAssetCommand { [Required] public int CaseNumber { get; set; } [Required] public User Operator { get; set; } }
现在定义一个用于处理业务命令的接口:
public interface ICommandHandler { void Handle(TCommand command); }
您的演示文稿代码现在将如下所示:
var command = new RegisterAssetCommand { CaseNumber = 1000, Operator = GetUserFromPage(), }; var commandHandler = WindsorContainer .Resolve); commandHandler.Handle(command);
注意: 如果可能的话,将使得commandHandler
的责任移出表示类并将其注入该类的构造函数(构造函数再次注入)。
不,您可以像这样创建ICommandHandler
的实现:
public class RegisterAssetCommandHandler : ICommandHandler { 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 } }
或者,您甚至可以通过在RegisterAssetCommand
注入IUserProvider
来使User
退出RegisterAssetCommandHandler
。 IUserProvider
接口可以具有处理程序可以调用的GetUserForCurrentContext
。
我希望这是有道理的。