方法参数设计考虑

时间:2013-07-13 23:12:35

标签: c# design-patterns

我的AddCustomer()有四个参数(firName, lastName, email, companyId),如下所示。

public class CustomerService
    {

            public bool AddCustomer(
                    string firName, string lastName, 
                    string email, int companyId)
            {

           //logic: create company object based on companId

           //other logic including validation

           var customer = //create customer based on argument and company object 

           //save the customer

        }

    }

     public class Customer
    {
        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public Company Company { get; set; }

        public string EmailAddress { get; set; }

        //Other five primitive properties

    }

        public class Company
    {
        public int Id { get; set; }

        public string Name { get; set; }

    }

我的问题是,如果考虑到SOLID原则,AddCustomer's参数应该更改为Customer对象,如下所示。请注意,该方法仅使用上面显示的四个字段。

 public bool AddCustomer(Customer customer){         
         }

更新

如果使用以下内容:

public bool AddCustomer(Customer customer)

问题:其中一个参数是CompanyId。因此,使用CompanyId作为参数创建Customer构造函数可能不适用于所有情况。但是,如果没有构造函数,AdCustomer()的客户端会分配哪些属性会让人感到困惑。

更新2

理想情况下,我希望通过限制财产制定者来保护实体客户和公司的不变量。

3 个答案:

答案 0 :(得分:1)

答案很大程度上取决于CustomerService班级和Customer班级的目的和责任,以及他们打算实现的目标。

从你的问题看来(“其他逻辑包括验证”),CustomerService有责任确定什么构成有效的新客户注册,而客户类本身只不过是一个DTO没有任何行为。

因此,请考虑以下假设用例:客户的电子邮件更改;客户为变更而工作的公司;如果公司破产,应拒绝新的客户注册;如果公司为我们产生大量销售,则客户应被视为高级客户。如何处理此类案件以及涉及哪些责任?

您可能希望以不同的方式处理此问题,因为您将intent和行为都显式化,而不是使用“AddCustomer”,“UpdateCustomer”,“DeleteCustomer”和“GetCustomer(Id)”。客户服务可以负责服务协调和基础架构方面,而Customer类确实关注所需的域行为和客户相关的业务规则。

我将概述一种(CQRS类型方法)的几种可能的方法来更好地分解责任,以说明这一点:

分别将行为意图和决定编码为命令和事件。

namespace CustomerDomain.Commands
{
    public class RegisterNewCustomer : ICommand
    {
        public RegisterNewCustomer(Guid registrationId, string firstName, string lastName, string email, int worksForCompanyId)
        {
            this.RegistrationId = registrationId;
            this.FirstName = firstName;
            // ... more fields
        }
        public readonly Guid RegistrationId;
        public readonly string FirstName;
        // ... more fields
    }
    public class ChangeCustomerEmail : ICommand
    {
        public ChangeCustomerEmail(int customerId, string newEmail)
        // ...
    }
    public class ChangeCustomerCompany : ICommand
    {
        public ChangeCustomerCompany(int customerId, int newCompanyId)
        // ...
    }
    // ... more commands
}

namespace CustomerDomain.Events
{
    public class NewCustomerWasRegistered : IEvent
    {
        public NewCustomerWasRegistered(Guid registrationId, int assignedId, bool isPremiumCustomer, string firstName /* ... other fields */)
        {
            this.RegistrationId = registrationId;
            // ...
        }
        public readonly Guid RegistrationId;
        public readonly int AssignedCustomerId;
        public readonly bool IsPremiumCustomer;
        public readonly string FirstName;
        // ...
    }
    public class CustomerRegistrationWasRefused : IEvent
    {
        public CustomerRegistrationWasRefused(Guid registrationId, string reason)
        // ...
    }
    public class CustomerEmailWasChanged : IEvent
    public class CustomerCompanyWasChanged : IEvent
    public class CustomerWasAwardedPremiumStatus : IEvent
    public class CustomerPremiumStatusWasRevoked : IEvent
}

这允许非常清楚地表达意图,并且仅包括完成特定任务所需的实际信息。

使用小型专用服务来处理应用程序域的决策需求:

namespace CompanyIntelligenceServices
{
    public interface ICompanyIntelligenceService
    {
        CompanyIntelligenceReport GetIntelligenceReport(int companyId); 
        // ... other relevant methods.
    }

    public class CompanyIntelligenceReport
    {
        public readonly string CompanyName;
        public readonly double AccumulatedSales;
        public readonly double LastQuarterSales;
        public readonly bool IsBankrupt;
        // etc.
    }
}

让CustomerService实施处理基础架构/协调问题:

public class CustomerDomainService : IDomainService
{
    private readonly Func<int> _customerIdGenerator;
    private readonly Dictionary<Type, Func<ICommand, IEnumerable<IEvent>>> _commandHandlers;
    private readonly Dictionary<int, List<IEvent>> _dataBase;
    private readonly IEventChannel _eventsChannel;
    private readonly ICompanyIntelligenceService _companyIntelligenceService;

    public CustomerDomainService(ICompanyIntelligenceService companyIntelligenceService, IEventChannel eventsChannel)
    {
        // mock database.
        var id = 1;
        _customerIdGenerator = () => id++;
        _dataBase = new Dictionary<int, List<IEvent>>(); 

        // external services and infrastructure.
        _companyIntelligenceService = companyIntelligenceService;
        _eventsChannel = eventsChannel;

        // command handler wiring.
        _commandHandlers = new Dictionary<Type,Func<ICommand,IEnumerable<IEvent>>>();
        SetHandlerFor<RegisterNewCustomerCommand>(cmd => HandleCommandFor(-1,
            (id, cust) => cust.Register(id, cmd, ReportFor(cmd.WorksForCompanyId))));
        SetHandlerFor<ChangeCustomerEmail>(cmd => HandleCommandFor(cmd.CustomerId, 
            (id, cust) => cust.ChangeEmail(cmd.NewEmail)));
        SetHandlerFor<ChangeCustomerCompany>(cmd => HandleCommandFor(cmd.CustomerId,
            (id, cust) => cust.ChangeCompany(cmd.NewCompanyId, ReportFor(cmd.NewCompanyId))));
    }
    public void PerformCommand(ICommand cmd)
    {
        var commandHandler = _commandHandlers[cmd.GetType()]; 
        var resultingEvents = commandHandler(cmd);
        foreach (var evt in resultingEvents)
            _eventsChannel.Publish(evt);
    }
    private IEnumerable<IEvent> HandleCommandFor(int customerId, Func<int, Customer, IEnumerable<IEvent>> handler)
    {
        if (customerId <= 0)
            customerId = _customerIdGenerator();
        var events = handler(LoadCustomer(customerId));
        SaveCustomer(customerId, events);
        return events;
    }
    private void SetHandlerFor<TCommand>(Func<TCommand, IEnumerable<IEvent>> handler)
    {
        _commandHandlers[typeof(TCommand)] = cmd => handler((TCommand)cmd);
    }
    private CompanyIntelligenceReport ReportFor(int companyId)
    {
        return _companyIntelligenceService.GetIntelligenceReport(companyId);
    }
    private Customer LoadCustomer(int customerId)
    { 
        var currentHistoricalEvents = new List<IEvent>();
        _dataBase.TryGetValue(customerId, out currentHistoricalEvents);
        return new Customer(currentHistoricalEvents);
    }
    private void SaveCustomer(int customerId, IEnumerable<IEvent> newEvents)
    {
        List<IEvent> currentEventHistory;
        if (!_dataBase.TryGetValue(customerId, out currentEventHistory))
            _dataBase[customerId] = currentEventHistory = new List<IEvent>();
        currentEventHistory.AddRange(newEvents);
    }
}

然后,这使您可以真正专注于Customer类所需的行为,业务规则和决策,仅维护执行决策所需的状态。

internal class Customer
{
    private int _id; 
    private bool _isRegistered;
    private bool _isPremium;
    private bool _canOrderProducts;

    public Customer(IEnumerable<IEvent> eventHistory)
    {
        foreach (var evt in eventHistory)
            ApplyEvent(evt);
    }

    public IEnumerable<IEvent> Register(int id, RegisterNewCustomerCommand cmd, CompanyIntelligenceReport report)
    {
        if (report.IsBankrupt)
            yield return ApplyEvent(new CustomerRegistrationWasRefused(cmd.RegistrationId, "Customer's company is bankrupt"));
        var isPremium = IsPremiumCompany(report);
        yield return ApplyEvent(new NewCustomerWasRegistered(cmd.RegistrationId, id, isPremium, cmd.FirstName, cmd.LastName, cmd.Email, cmd.WorksForCompanyID));
    }
    public IEnumerable<IEvent> ChangeEmail(string newEmailAddress)
    {
        EnsureIsRegistered("change email");
        yield return ApplyEvent(new CustomerEmailWasChanged(_id, newEmailAddress));
    }
    public IEnumerable<IEvent> ChangeCompany(int newCompanyId, CompanyIntelligenceReport report)
    {
        EnsureIsRegistered("change company");
        var isPremiumCompany = IsPremiumCompany(report);
        if (!_isPremium && isPremiumCompany)
            yield return ApplyEvent(new CustomerWasAwardedPremiumStatus(_id));
        else 
        {
            if (_isPremium && !isPremiumCompany)
                yield return ApplyEvent(new CustomerPremiumStatusRevoked(_id, "Customer changed workplace to a non-premium company"));
            if (report.IsBankrupt)
                yield return ApplyEvent(new CustomerLostBuyingCapability(_id, "Customer changed workplace to a bankrupt company"));
        }
    }
    // ... handlers for other commands
    private bool IsPremiumCompany(CompanyIntelligenceReport report)
    {
        return !report.IsBankrupt && 
            (report.AccumulatedSales > 1000000 || report.LastQuarterSales > 10000);
    }
    private void EnsureIsRegistered(string forAction)
    {
        if (_isRegistered)
            throw new DomainException(string.Format("Cannot {0} for an unregistered customer", forAction));
    }
    private IEvent ApplyEvent(IEvent evt)
    {
        // build up only the status needed to take domain/business decisions.
        // instead of if/then/else, event hander wiring could be used.
        if (evt is NewCustomerWasRegistered)
        {
            _isPremium = evt.IsPremiumCustomer;
            _isRegistered = true;
            _canOrderProducts = true;
        }
        else if (evt is CustomerRegistrationWasRefused)
            _isRegistered = false;
        else if (evt is CustomerWasAwardedPremiumStatus)
            _isPremium = true;
        else if (evt is CustomerPremiumStatusRevoked)
            _isPremium = false;
        else if (evt is CustomerLostBuyingCapability)
            _canOrderProducts = false;
        return evt;
    }
}

另一个好处是,在这种情况下,Customer类与任何基础架构问题完全隔离,可以轻松测试其正确行为,并且可以轻松更改或扩展客户域模块,以满足新要求,而不会破坏现有客户端。

答案 1 :(得分:0)

是....如果它有效创建一个具有这4个属性的客户....理想情况下你有一个构造函数与那些4.那样创建责任与客户对象和客户服务不一致需要了解它,它只是处理“客户”。

答案 2 :(得分:0)

如何使用构建器模式导致代码有点像这样:

var customer = new CustomerBuilder()
                     .firstName("John")
                     .lastName("Doe")
                     .email("john.doe@example.com")
                     .companyId(6)
                     .createCustomer();

customerService.AddCustomer(customer);

然后,当调用createCustomer时,您可以让构建器类处理查找公司对象,并且参数的顺序不再重要,并且您有一个方便的位置来放置逻辑以选择合理的默认值。

这也为验证逻辑提供了方便的位置,因此您无法在第一时间获得无效的Customer实例。

或者另一种可能的方法是让AddCustomer返回一个命令对象,这样你的客户端代码就可以这样做:

customerService.AddCustomer()
    .firstName("John")
    .lastName("Doe")
    .email("john.doe@example.com")
    .companyId(6)
    .execute();