想象一下,我们有一个具有生命周期的聚合,以便它可以在其生命周期内改变其行为。在其生命的第一部分,它可以做一些事情,在第二部分,它可以做其他事情。
我想听听关于如何限制聚合在每个阶段可以做什么的意见。 为了使它更有形,让我们以金融交易作为一个共同的例子。
交易显然有3个不同的阶段,我称之为键入,验证和已提交
我的第一个想法是用InvalidOperationExceptions
污染聚合,我真的不喜欢这样:
public class Trade
{
private enum State { Typed, Validated, Submited }
private State _state = State.Typed;
public Guid Id { get; }
public Contract Contract { get; }
public decimal Price { get; }
public Trade (Guid id, Contract contract, decimal price) { ... }
private string _validationReason = null;
private AccountingInformation _accInfo = null;
public void Validate(string reason) {
if (_state != State.Typed)
throw new InvalidOperationException (..)
...
_validationReason = reason;
_state = State.Validated;
}
public string GetValidationReason() {
if (_state == State.Typed)
throw new InvalidOperationException (..)
return _validationReason;
}
public void SubmitToLedger(AccountingInformation info) {
if ((_state != State.Validated))
throw new InvalidOperationException (..)
...
}
public AccountingInfo GetAccountingInfo() { .. }
}
我可以执行类似Maybe pattern的操作,以避免Get...
方法上的例外情况。但这不适用于行为方法(Validate
,SubmitToLedger
等)
奇怪的是,如果我要使用函数式语言(例如F#),我可能会为每个州创建一个不同的类型。
type TypedTrade = { Id : Guid; Contract: Contract; Price : decimal }
type ValidatedTrade = { Id : Guid;
Contract: Contract;
Price : decimal;
ValidationReason : string}
type SubmittedTrade = { Id : Guid;
Contract: Contract;
Price : decimal;
ValidationReason : string;
AccInfo : AccountingInfo }
// TypedTrade -> string -> ValidatedTrade
let validateTrade typedTrade reason =
...
{ Id = typedTrade.Id; Contract = typedTrade.Contract;
Price = typedTrade.Price; Reason = reason }
// ValidatedTrade -> AccountingInfo -> SubmittedTrade
let submitTrade validatedTrade accInfo =
...
{ Id = validatedTrade.Id;
Contract = validatedTrade.Contract;
Price = validatedTrade.Price;
Reason = validatedTrad.Reason;
AccInfo = accInfo }
问题会优雅地消失。但要在OO中这样做,我必须使我的聚合不可变,并且可能创建一些o层次结构(我必须隐藏基本方法!?哎哟!)。
我只想就你们在这些情况下所做的事情发表意见,以及是否有更好的方法。
答案 0 :(得分:2)
我喜欢为每个州提供不同类型的想法。在我看来它是一个干净的设计。从逻辑上看,新创建的交易绝对不同于提交的交易。
public Interface ITrade
{
Guid Id { get; }
Contract Contract { get; }
decimal Price { get; }
}
public class Trade : ITrade
{
public Trade(Guid id, Contract contract, decimal price)
{
Id = id;
Contract = contract;
Price = price;
}
Guid Id { get; }
Contract Contract { get; }
decimal Price { get; }
public ValidatedTrade Validate(string reason)
{
return new ValidatedTrade(this, reason);
}
}
public class ValidatedTrade : ITrade
{
private ITrade trade;
private string validationReason;
public ValidatedTrade(Trade trade, string validationReason)
{
this.trade = trade;
this.validationReason = validationReason;
}
Guid Id { get { return trade.Id; } }
Contract Contract { get { return trade.Contract ; } }
decimal Price { get { return trade.Price ; } }
public string GetValidationReason()
{
return validationReason;
}
public SubmittedTrade SubmitToLedger(AccountingInfo accountingInfo)
{
return new SubmittedTrade(this, accountingInfo);
}
}
public class SubmittedTrade : ITrade
{
private ITrade trade;
private AccountingInfo accountingInfo;
public SubmittedTrade(ValidatedTrade trade, AccountingInfo accountingInfo)
{
this.trade = trade;
this.accountingInfo = accountingInfo;
}
Guid Id { get { return trade.Id; } }
Contract Contract { get { return trade.Contract ; } }
decimal Price { get { return trade.Price ; } }
public AccountingInfo GetAccountingInfo() { .. }
}
答案 1 :(得分:1)
每个州可以有一个班级,而不是一个班级。请参阅Greg Young撰写的这篇文章:http://codebetter.com/gregyoung/2010/03/09/state-pattern-misuse/
状态模式的常见问题是与持久性问题的摩擦,尤其是ORM。由您决定更好的稳健性和类型安全性是否值得给您带来麻烦。