状态模式:解耦MoveNext()方法

时间:2014-02-19 16:25:55

标签: c# design-patterns

我已经从以下StackOverflow帖子中获得了一些帮助来实现状态模式:

state pattern

到目前为止很好。 我现在可以移动对象(文档,到下一个状态,即ACKNOWLEDGED。)

 public override void MoveNext(Document currDoc, IProcessor currProcessor)
    {                               
        TransitionTo<ACKNOWLEDGED>(() => new ACKNOWLEDGED(_factory, _context));
        currProcessor.LogTheChange(currDoc);  
        currProcessor.DoSomethingElse(currDoc)
    }

现在,在更改状态的过程中,我想在不同的类中执行其他操作。 我怀疑结合状态和IProcessor是个好主意。

我认为国家应该只关注两个谨慎状态之间的转换(在这种情况下是UNACKNOWLEDGED和ACKNOWLLEDGED)。

如何对底层对象进行实际更新?据我所见,我已将doc对象传递给moveNext()方法,只是为了可以将其传递到别处。如果这不正确,那我的状态对象如何以解耦的方式与IProcessor通信呢?它应该引发一个IProcessor将处理的事件吗?或者,我应该在MoveNext()方法中将接口作为参数传递吗?我怀疑不是。

2 个答案:

答案 0 :(得分:0)

嗯。我会说这个有趣的问题。如果状态转换只有一个效果(使IProcessor做某事或某些事情),那么这种方法可能很好。

即使状态发生变化可能会发生很多事情,但MoveNext()函数是煽动此变化的唯一方法,那么为方法添加更多处理器和操作可能不会太可怕。如果您的某个处理器抛出异常,您最终可能会担心会发生什么。

但是,如果状态更改可以从许多地方启动(许多函数,如MoveNext(),或者状态可以根据条件自行更改),那么您将需要拥有监视状态更改的实体。您可以使用某种发布和订阅机制来使用它,或者只是按惯例使用,因为某些对象会监视您的州实体发出的消息。

如果是我,我可能会将我的状态对象挂钩到spring.net事件系统(http://springframework.net/doc-latest/reference/html/quickstarts.html)之类的东西。但是,在.net中可能有其他方法可以执行此操作,而不涉及spring。

答案 1 :(得分:0)

我不认为在内部使用State模式的类的调用者应该知道状态机存在。因此,我不是调用Documemt.MoveNext的客户端代码的粉丝。它暴露了很多实现细节。

这是一个隐藏Document类中的状态模式的替代实现。请注意,我使用私有内部类来完全隐藏状态机的详细信息,同时提供对每个状态子类的Document成员的完全访问权限。我仍然将这些内部类保存在自己的代码文件中,以避免Document类文件中的代码混乱。

Document.cs

partial class Document
{

    public Document()
    {
        // default/starting state
        this.TransitionToState<EmptyState>();
    }

    // misc data for example
    public int? caseNumber { get; private set;}
    public DateTime? WhenSubmitted { get; private set; }
    public DateTime? WhenAcknowlegded { get; private set; }
    public int? CompletionStatus { get; private set; }

    // transitions:  EMPTY -> ASSIGNED -> UNACKNOWLEDGED -> ACKNOWLEDGED -> COMPLETED
    private DocumentState State { get; set; }


    // state-related methods are forwarded to the current DocumentState instance

    public void AssignCase(int caseNumber)
    {
        State.AssignCase(caseNumber);
    }

    public void SubmitTo(object clientInfo)
    {
        State.SubmitTo(clientInfo);
    }

    public void Acknowledged(object ackInfo)
    {
        State.Acknowledged(ackInfo);
    }

    public void Complete(int statusCode)        
    {
        State.Complete(statusCode);
    }

    // events could be used for this callback as well, but using private inner 
    //  classes calling a private member is probably the simplest.

    private void TransitionToState<T>() where T : DocumentState, new()
    {
        // save prior for a moment
        DocumentState priorState = State;

        // this can be lookup from map instead of new() if you need to keep them 
        //  alive for some reason.  I personally like flyweight states.
        DocumentState nextState = new T();

        // activate the new state.  it will get notified so it can do any one-
        //  time setup
        State = nextState;
        State.EnterState(this);

        // let the prior state know as well, so it can cleanup if needed
        if (priorState != null)
            priorState.ExitState();

    }        

}

DocumentState.cs

partial class Document
{

    abstract class DocumentState
    {

        //--------------------------------------------
        // state machine infrastructure 
        //--------------------------------------------

        public void EnterState(Document context)
        {
            this.Context = context;
            Console.WriteLine("Entering state: " + this.GetType().Name); // debug only
            OnEnterState();
        }

        public void ExitState()
        {
            this.Context = null;
            OnExitState();
            Console.WriteLine("State that was exited: " + this.GetType().Name); // debug only
        }

        protected Document Context { get; private set; }

        //--------------------------------------------
        // a mirror of the document-manipulation methods that concerns states
        //--------------------------------------------

        public void AssignCase(int caseNumber)
        {
            OnAssignCase(caseNumber);
        }

        public void SubmitTo(object clientInfo)
        {
            OnSubmitTo(clientInfo);
        }

        public void Acknowledged(object ackInfo)
        {
            OnAcknowledged(ackInfo);
        }

        public void Complete(int statusCode)
        {
            OnComplete(statusCode);
        }

        //--------------------------------------------
        // state subclasses override the methods they need.  Typically not 
        //  all are needed by all states.  Default implementation is to
        //  throw an exception if a state receives and "unexpected" invocation.
        //--------------------------------------------

        protected virtual void OnAssignCase(int caseNumber)
        {
            throw new InvalidOperationException();
        }

        protected virtual void OnSubmitTo(object clientInfo)
        {
            throw new InvalidOperationException();
        }

        protected virtual void OnAcknowledged(object ackInfo)
        {
            throw new InvalidOperationException();
        }

        protected virtual void OnComplete(int statusCode)
        {
            throw new InvalidOperationException();
        }

        //--------------------------------------------
        // additional hooks that can be override if needed that signal the
        //  enter and exit of the state.
        //--------------------------------------------

        protected virtual void OnEnterState()
        {
        }

        protected virtual void OnExitState()
        {
        }

    }

}

州级课程(为了说明目的,我添加了其他课程):

partial class Document
{

    // Represents an empty document waiting to get assigned a case #.  Once 
    //  that is satisfied, it performs its logic and triggers a state 
    //  transition to the next state.
    class EmptyState : DocumentState
    {
        protected override void OnAssignCase(int caseNumber)
        {
            // business logic
            Context.caseNumber = caseNumber;
            // write to log
            // etc, etc

            // goto next state
            Context.TransitionToState<AssignedState>();
        }
    }

}

partial class Document
{

    // Represents an document assigned a ase number but not submitted to a
    //  client yet.  Once that happens it performs its logic and the triggers a state 
    //  transition.
    class AssignedState : DocumentState
    {
        protected override void OnSubmitTo(object clientInfo)
        {
            // business logic
            Context.WhenSubmitted = DateTime.Now;
            // etc
            // etc

            // goto next state
            Context.TransitionToState<UnacknowledgedState>();
        }
    }
}

partial class Document
{        
    // you get the idea by now...
    class UnacknowledgedState : DocumentState
    {
        protected override void OnAcknowledged(object ackInfo)
        {
            // business logic
            Context.WhenAcknowlegded = DateTime.Now;

            // goto next state
            Context.TransitionToState<AcknowledgedState>();

        }
    }
}

partial class Document
{
    class AcknowledgedState : DocumentState
    {
        protected override void OnComplete(int statusCode)
        {
            Context.CompletionStatus = statusCode;

            Context.TransitionToState<CompletedState>();
        }

    }
}

partial class Document
{
    class CompletedState : DocumentState
    {
        // note there are no methods overriden.  this is the last state.
    }
}

最后,Program.cs:

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        Document doc = new Document();

        Console.ReadLine();

        doc.AssignCase(123456);

        Console.ReadLine();

        doc.SubmitTo("clientAddress");

        Console.ReadLine();

        doc.Acknowledged("responseFromClient");

        Console.ReadLine();

        const int TERMS_REJECTED = 123;
        doc.Complete(TERMS_REJECTED);

        Console.ReadLine();

    }
}

如果您有任何问题,请与我们联系。