在其生命周期中改变聚合行为

时间:2018-05-25 14:14:30

标签: oop design-patterns domain-driven-design

想象一下,我们有一个具有生命周期的聚合,以便它可以在其生命周期内改变其行为。在其生命的第一部分,它可以做一些事情,在第二部分,它可以做其他事情。

我想听听关于如何限制聚合在每个阶段可以做什么的意见。 为了使它更有形,让我们以金融交易作为一个共同的例子。

  • 交易者创建交易,告知合约及其价格。
  • 风险经理验证交易,给出了相应的理由。
  • BackOffice可以将交易提交给分类账,提供会计信息。
  • 交易提交后,会计信息永远不会改变。

交易显然有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...方法上的例外情况。但这不适用于行为方法(ValidateSubmitToLedger等)

奇怪的是,如果我要使用函数式语言(例如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层次结构(我必须隐藏基本方法!?哎哟!)。

我只想就你们在这些情况下所做的事情发表意见,以及是否有更好的方法。

2 个答案:

答案 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。由您决定更好的稳健性和类型安全性是否值得给您带来麻烦。