正确建模表示状态突变的不可变数据结构

时间:2013-03-06 10:22:56

标签: c# design-patterns immutability

我特意使用 immutable mutation 这些词来突出显示问题。

我想对属性设计进行抽象,模拟状态转换并收集相关数据(可能这两个要求违反单一责任?)。

我画了类似的东西:

enum StateTypeEnum
{
    State1,
    State2,
    State3
}

class State
{
    private readonly StateTypeEnum _stateType;
    private readonly IEnumerable<string> _stateData;

    public State(StateTypeEnum stateType)
    {
        _stateType = stateType;
        _stateData = new List<string>(new string[] { });
    }

    public State(StateTypeEnum stateType, IEnumerable<string> stateData)
    {
        _stateType = stateType;
        _stateData = new List<string>(stateData);
    }


    public State ChangeState(StateTypeEnum stateType)
    {
        return new State(stateType, _stateData);
    }

    public State ChangeState(StateTypeEnum stateType, IEnumerable<string> stateData)
    {
        return new State(stateType, _stateData.Concat(stateData));
    }

    public State ChangeState(IEnumerable<string> stateData)
    {
        return new State(_stateType, _stateData.Concat(stateData));
    }

    public IReadOnlyList<string> StateData
    {
        get
        {
            return new ReadOnlyCollection<string>(_stateData.ToList());
        }
    }

    public StateTypeEnum CurrentStateType
    {
        get
        {
            return _stateType;
        }
    }
}

并将其用作:

var state1 = new State(StateTypeEnum.State1);
var state2 = state1.ChangeState(StateTypeEnum.State3);
var state3 = state2.ChangeState(new[] { "a", "b", "c" });
var state4 = state3.ChangeState(StateTypeEnum.State1, new[] { "d", "e" });
var state5 = state4.ChangeState(StateTypeEnum.State3);

Debug.Assert(
    state5.CurrentStateType == StateTypeEnum.State3);
Debug.Assert(
    state5.StateData.SequenceEqual(new[] { "a", "b", "c", "d", "e" }));

previous question我知道我可以使用.NET immutable collections

但这不是我的问题的中心,我想知道这样的设计是否导致了一个不可变数据结构的正确(或可接受)实现,该数据结构模拟状态转换与相关数据。

替代实施:

我可以删除枚举并定义State的子类型吗?

class State {

  public StateTransition1 ChangeToTransition1() {
    return new StateTransition1( ... );
  }

  public StateTransition2 ChangeToTransition2(IEnumerable data) {
    return new StateTransition2( ... );
  }
}

通过这种方式,我明确指出transition1作为一个明确定义的特定含义,transition2作为它自己的含义(例如它包含特定的相关数据)。

然后,消费者代码可以查询子类型而不是枚举来控制流。

正确分解状态变更方法:

由于状态是使用构造函数重新创建的,我想知道是否因子分析(``ChangeToXXXX`)会改变状态方法,因为扩展方法可以导致更可维护和正式的设计。

2 个答案:

答案 0 :(得分:1)

如果你需要保证State是不可变的,你应该考虑密封 State类,它只允许状态转换(进入新的不可变状态)这个独一无二的类暴露的方法,保持enum就像你到目前为止一样。在这种情况下,使用扩展方法在功能上是等效的,因为您保持实现密封。

如果你想重构它的“OOP风格”并且不关心这是否会让你暴露于可能的可变实现,那么你可以使用"Replace Typecode with Subclass"重构来结束类似的事情:

interface IState
{
    IEnumerable<string> Data { get; }
}

class State1 : IState 
{  ...  }

class State2 : IState 
{  ...  }

class State3 : IState 
{  ...  }

在这种情况下,您可以在IState接口上使用扩展方法,但这意味着转换逻辑必须与实际状态完全分离。

或者,您可以让IState接口实现GetNextState(someInput)方法,然后让每个州决定下一个状态。

或者,您可以拥有一个带有internal abstract成员的抽象基类,以防止其他程序集从中派生。

如果没有其他背景知识,很难确切地说明哪种情况最适合您的应用。例如,目前尚不清楚这些枚举的目的是什么,因为它们似乎没有参与有关转换的决定。

答案 1 :(得分:-2)

也许这样的模式会有所帮助State