【问题标题】:How to separate responsibilities when requirements evolve in this way?当需求以这种方式演变时,如何划分职责?
【发布时间】:2011-12-20 17:09:07
【问题描述】:

首先我的要求是

“我们可以创建一个账户并在上面存钱,当我们购买物品时,我们会减少账户”

所以我的 AccountController 看起来像

class AccountController
{
    private IAccountDataSource _accountDataSource;

    Create(Account anAccount)
    {
        _accountDataSource.Insert(anAccount);
         Render(anAccount.Id);
     }
}

但是有一个新的要求 "有些人可以拥有一个免费帐户(所有项目都是免费的),但如果我们创建一个真实帐户,我们就会删除该免费帐户"

所以我的 controller.Create 变成了

Create(Account anAccount)
{
    _accountDataSource.Insert(anAccount);
    RemoveFreeAccount(anAccount.Customer);
    Render(anAccount.Id);
}

RemoveFreeAccount(Customer aCustomer)
{
    _accountDataSource.Remove(new AccountFilter() { Type='Free', CustomerId=aCustomer.Id });
}

但对我来说,我觉得我应该把这个 RemoveFreeAccount 放在其他地方,但我不知道在哪里,因为 IAccountDataSource 只是假设处理数据存储。

【问题讨论】:

    标签: design-patterns solid-principles


    【解决方案1】:

    问题表明您正在破坏 SRP。您的控制器不应包含业务逻辑。直接在控制器中使用存储库会强制您将所有逻辑放入其中,因此需要承担两个职责(作为 MVC 中的 M + 处理业务逻辑之间的桥梁)。

    第一个重构部分应该是将业务逻辑移到模型中(在 MVC 中不要与实体模型或视图模型混淆)

    这使您的原始代码具有以下结构:

    public class AccountService
    {
        void CreateAccount(string accountName)
        {
           var account = new Account(accountName);
            _dataSource.Create(account);
            DomainEvents.Publish(new AccountCreated(account));
        }
    }
    
    public class AccountController
    {
        private AccountService _service;
    
        Create(AccountViewModel model)
        {
            var account = _accountDataSource.Create(model.Name);
            Render(account.Id);
         }
    }
    

    变化可能看起来很小,但很重要:

    1. 控制器现在只有一个更改原因(视图和模型之间的映射)
    2. 业务需求的任何更改都不会强制更改 UI 层。
    3. 仅在一处更改要求

    要添加对免费帐户的支持,我将使用事件驱动模型:

    public class FreeAccountService : ISubscriberOf<UserCreated>, ISubscriberOf<AccountCreated>
    {
        public FreeAccountService(AccountService)
        {
        }
    
        public void HandleEvent(UserCreated domainEvent)
        {
            accountService.Create(new FreeAccount());
        }
    
        public void HandleEvent(AccountCreated domainEvent)
        {
            var freeAccount = dbSource.GetFreeAccount();
            if (freeAccount != null)
                accountService.Delete(freeAccount)
        }
    }
    

    因为它不需要更改其他帐户服务。

    【讨论】:

    • 这是一个不错的解决方案,但你不觉得 CRUD 系统的抽象太多了吗?
    • 我的经验是,没有一个系统是简单的 CRUD 系统。你的问题就是一个很好的例子。免费帐户功能是业务规则,而不仅仅是 CRUD。从一开始就做,或者以后做维护噩梦。
    • 您使用哪个 API/框架来处理事件?有没有这样的内置工具?
    • 创建一个发布事件非常容易。我为 autofac 创建了一个,您可以使用它:github.com/sogeti-se/Sogeti.Pattern/wiki/Domain-events
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-11
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 2011-02-10
    相关资源
    最近更新 更多