重构C#Save命令处理程序

Gra*_*ham 4 .net c# simple-injector

我有以下命令处理程序.处理程序接受命令对象并使用其属性来创建或更新实体.

它由Id可以为空的命令对象上的属性决定.如果为null,则创建,如果不是,则更新.

public class SaveCategoryCommandHandler : ICommandHandler<SaveCategoryCommand>
{
    public SaveCategoryCommandHandler(
        ICategoryRepository<Category> categoryRepository,
        ITracker<User> tracker,
        IMapProcessor mapProcessor,
        IUnitOfWork unitOfWork,
        IPostCommitRegistrator registrator)
    {
         // Private fields are set up. The definitions for the fields have been removed for brevity.
    }

    public override void Handle(SaveCategoryCommand command)
    {
        // The only thing here that is important to the question is the below ternary operator.

            var category = command.Id.HasValue ? GetForUpdate(command) : Create(command);

 // Below code is not important to the question. It is common to both create and update operations though.

            MapProcessor.Map(command, category);

            UnitOfWork.Commit();

            Registrator.Committed += () =>
            {
                command.Id = category.Id;
            };

    }

    private Category GetForUpdate(SaveCategoryCommand command)
    {
        // Category is retrieved and tracking information added
    }

    private Category Create(SaveCategoryCommand command)
    {
        // Category is created via the ICategoryRepository and some other stuff happens too.
    }
}
Run Code Online (Sandbox Code Playgroud)

我曾经有两个处理程序,一个用于创建,一个用于更新,还有两个用于创建和更新的命令.一切都是使用IoC连线的.

在重构为一个类以减少代码重复的数量后,我最终得到了上面的处理程序类.重构的另一个动机是避免使用两个命令(UpdateCategoryCommand和CreateCategoryCommand),这导致更多的重复验证和类似.

这方面的一个例子是必须有两个验证装饰器,它们实际上是相同的命令(因为它们只有Id属性而不同).装饰器确实实现了继承,但是当有很多命令需要处理时,它仍然很痛苦.

关于重构的处理程序有一些让我烦恼的事情.

一个是注入的依赖项数量.另一个是课堂上有很多东西.在if三元困扰我-这似乎有点像一个代码味道的.

一种选择是在处理程序中注入某种辅助类.这可以实现某种ICategoryHelper具体CreateUpdate实现的接口.这将意味着ICategoryRepositoryITracker依赖关系可以用在一个单一的依赖所取代ICategoryHelper.

唯一可能的问题是,这需要根据命令上的Id字段是否为空来从IoC容器中进行某种条件注入.

我正在使用SimpleInjector,并且不确定如何执行此操作的语法,或者即使它可以完成.

这是通过IoC这样做也是一种气味,还是处理者有责任这样做?

有没有其他模式或方法来解决这个问题?我原本以为装饰器可能会被使用,但我真的不能想到如何这样做.

Ste*_*ven 6

我的经验是,使用两个单独的命令(SaveCategoryCommandUpdateCategoryCommand)和一个命令处理程序可以获得最佳结果(尽管两个单独的命令处理程序有时也可以正常).

这些命令不应该从CategoryCommandBase基类继承,而应该将两个命令共享的数据提取到DTO类,该类作为两个类的属性公开(组合而不是继承).命令处理程序应该实现两个接口,这允许它包含共享功能.

[Permission(Permissions.CreateCategories)]
class SaveCategory {
    [Required, ValidateObject]
    public CategoryData Data;

    // Assuming name can't be changed after creation
    [Required, StringLength(50)]
    public string Name;
}

[Permission(Permissions.ManageCategories)]
class UpdateCategory {
    [NonEmptyGuid]
    public Guid CategoryId;

    [Required, ValidateObject]
    public CategoryData Data;
}

class CategoryData {
    [NonEmptyGuid]
    public Guid CategoryTypeId;
    [Required, StringLength(250)]
    public string Description;
}
Run Code Online (Sandbox Code Playgroud)

拥有两个命令效果最好,因为当每个操作都有自己的命令时,它可以更容易地记录它们,并允许它们提供不同的权限(例如,使用属性,如上所示).共享数据对象最有效,因为它允许您在命令处理程序中传递它并允许视图绑定到它.继承几乎总是丑陋的.

class CategoryCommandHandler :
    ICommandHandler<SaveCategory>,
    ICommandHandler<UpdateCategory> {
    public CategoryCommandHandler() { }

    public void Handle(SaveCategory command) {
        var c = new Category { Name = command.Name };
        UpdateCategory(c, command.Data);
    }

    public void Handle(UpdateCategory command) {
        var c = this.repository.GetById(command.CategoryId);
        UpdateCategory(c, command.Data);
    }

    private void UpdateCategory(Category cat, CategoryData data) {
        cat.CategoryTypeId = data.CategoryDataId;
        cat.Description = data.Description;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,CRUDy操作将始终导致解决方案看起来不像基于任务的操作那样干净.这是我推动开发人员并要求工程师考虑他们想要执行的任务的众多原因之一.这样可以获得更好的用户界面,更高的用户体验,更具表现力的审计线索,更舒适的设计以及更好的整体软件 但是你的应用程序的某些部分将始终是CRUDy; 无论你做什么.