我的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
理想情况下,我希望通过限制财产制定者来保护实体客户和公司的不变量。
答案 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();