the generic state keeper and the observer

In my previous post I explored the state keepers for my robocode implementation and had a working generic state keeper.
However, there was a problem that this new design created and I'm going to discuss this by looking at my existing observer. Here's the original code for that observer before the change:

namespace TeamSlayer.Model.Decision
{
    public interface IObserveToMakeDecisions
    {
        void Observe(IKeepDecisionState decisionStateKeeper);
    }

    public class BattleReachedAReasonableTurnObserver : IObserveToMakeDecisions
    {
        readonly IManageStatistics statisticsManager;

        public BattleReachedAReasonableTurnObserver(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

    public void Observe(IKeepDecisionState decisionStateKeeper)
        {
            decisionStateKeeper.Situation.Where(s => s.EventArgs.TheNumberOfTurnsSoFar == 50)
                       .Subscribe(s =>
                       {
                           ReachedTurnFifty = true;
                           statisticsManager.WriteStatistics(ReachedTurnFifty);
                       });
        }

        public bool ReachedTurnFifty { get; private set; }
    }
}

To recap, this code is in the decision bounded context and namespace and the observer is presented with the decision state keeper when asked to observe; the observer then subscribes to the observable situation.

Here's the new code once the generic state keeper is in place:

using TeamSlayer.Model.World;

namespace TeamSlayer.Model.Decision
{
    public interface IObserveToMakeDecisions : IContextObserver<DecisionState>
    {
    }

    public class BattleReachedAReasonableTurnObserver : IObserveToMakeDecisions
    {
        readonly IManageStatistics statisticsManager;

        public BattleReachedAReasonableTurnObserver(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

        public void Observe(IKeepState<DecisionState> decisionStateKeeper)
        {
            decisionStateKeeper.Situation.Where(s => s.EventArgs.TheNumberOfTurnsSoFar == 50)
                .Subscribe(s =>
                               {
                                   ReachedTurnFifty = true;
                                   statisticsManager.WriteStatistics(ReachedTurnFifty);
                               });
        }

        public bool ReachedTurnFifty { get; private set; }
    }
}

The decision observer interface now has no implementation and simply declares the state type for the context observer. This is great as it effectively acts as a translator, abstracting the world namespace away from anything in the decision context - I'm very happy with this.

The observer is now presented with a generic state keeper via the IKeepState<State> interface and this I do not like - the using statement shown at the top clearly shows I'm violating my domain driven design concept of a bounded context that I originally wanted.
I now have to think about how to address this and after some tinkering I come to the conclusion that the Situation observable is effectively typed by the decision state keeper, so I should be able to use this.

To get this to work though, I have to change the generic state keeper around - I'm going to omit the specifications for brevity once again as this takes a bit of explaining. First, I change the contract of the context observer to use the observable situation instead of the state keeper itself and replace the Situation property in the observer call in place of sending "this" into the method call:

namespace TeamSlayer.Model.World
{
    public interface IContextObserver<State>
    {
        void Observe(IObservable<IEvent<State>> situation);
    }

    public abstract class StateKeeper<State, Observer> : IUpdateState<State>, IKeepState<State>
        where Observer : IContextObserver<State>
        where State : EventArgs
    {
       // rest of the class left out for this illustration

       protected abstract IObservable<IEvent<State>> EnsureThatTheObservableHasBeenCreated();

        public IObservable<IEvent<State>> Situation
        {
            get
            {
                return situation ?? ( situation = EnsureThatTheObservableHasBeenCreated());
            }
        }

        public StateKeeper(IEnumerable<Observer> observers)
        {
            foreach (var observer in observers)
            {
                observer.Observe(Situation);
            }
        }
    }
}

I've highlighted a problem in red that this change presents - my situation property calls out to a derived implementation of this class through an abstract method and this is not a good situation. This is the same problem I outlined in my last post where we would be calling into a derived class whilst in the base constructor and we will not be able to guarantee the derived class is initialised properly.

So to fix this, I'm going to have to pull the observer registration to when the update change is pushed to the state keeper:

    public abstract class StateKeeper<State, Observer> : IUpdateState<State>, IKeepState<State>
        where Observer : IContextObserver<State>
        where State : EventArgs
    {
        IEnumerable<Observer> observers;
        IObservable<IEvent<State>> situation;
        bool weHaveNotYetShownObserversOurSituation = true;
        State internalState;

        State InternalState
        {
            get
            {
                return internalState ?? (internalState = EnsureThatStateHasBeenCreated() );
            }
        }

        protected abstract State EnsureThatStateHasBeenCreated();

        protected abstract IObservable<IEvent<State>> EnsureThatTheObservableHasBeenCreated();

        protected event EventHandler<State> StateHasUpdated;

        public void UpdateState(Action<State> stateUpdate)
        {
            if (weHaveNotYetShownObserversOurSituation)
            {
                EnsureThatObserversHaveRegisteredObservations();
                weHaveNotYetShownObserversOurSituation = false;
            }

            stateUpdate(InternalState);
            StateHasUpdated(this, InternalState);
        }

        void EnsureThatObserversHaveRegisteredObservations()
        {
            foreach (var observer in observers)
            {
                observer.Observe(Situation);
            }
        }

        public IObservable<IEvent<State>> Situation 
        { 
            get
            {
                if (situation == null)
                    situation = EnsureThatTheObservableHasBeenCreated();

                return situation;
            } 
        }

        public StateKeeper(IEnumerable<Observer> observers)
        {
            this.observers = observers;
        }
    }

In the constructor, I'm simply storing the observers and when the update state happens I check if I have asked the observers to observe and if that has not already taken place, I do so.

The implementation of the original observer that had the bounded context leak issue now changes to:

namespace TeamSlayer.Model.World
{
    public interface IContextObserver<State>
    {
        void Observe(IObservable<IEvent<State>> situation);
    }
}

namespace TeamSlayer.Model.Decision
{
    public class BattleReachedAReasonableTurnObserver : IObserveToMakeDecisions
    {
        readonly IManageStatistics statisticsManager;

        public BattleReachedAReasonableTurnObserver(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

        public void Observe(IObservable<IEvent<DecisionState>> situation)
        {
            situation.Where(s => s.EventArgs.TheNumberOfTurnsSoFar == 50)
                .Subscribe(s =>
                               {
                                   ReachedTurnFifty = true;
                                   statisticsManager.WriteStatistics(ReachedTurnFifty);
                               });
        }

        public bool ReachedTurnFifty { get; private set; }
    }
}

I've now removed the using statement to the world context and this observer is now isolated in the decision context once again.

What is interesting is that as this observer is now passed the situation observable, typed to the state of this bounded context, this in fact makes much more sense than the previous implementation. Once again, a code change has moved me towards deeper insight about my code and cleared this area up - the bounded context concept has made me think about isolation in a new way.



0 comments: