Observer Pattern with C# 4

By | October 27, 2010

all source available at git@github.com:jeffvee/ObserverPatternC-4.git

A common situation when building systems is the case where an object contains a list of child objects that need to be aware of the containing object’s state. Based on this state, the child objects must adjust themselves. In essence, the children must “observe” the parent so as to remain consistent. This post is a bare bones demonstration of how to implement this observer pattern using C# 4.

 

Practical Example

To help visualize the problem space, let’s imagine that we are building a system that manages contracts. A contract can have one or more signees. A contract is associated with a pricing plan, which dictates what the initial monthly dues for its signees are. Finally, a signee’s dues can change after the initial contract signing.

An object model that captures this might look like the one below.

initialObjectLayout

 

Remember that the dues that a signee pays at the start of a contract is defined by the pricing plan that is associated with said contract. So as we add signees to and vary the pricing on a contract, the dues must stay in sync. A couple of tests will make our goal abundantly clear.

[Test]
public void SigneeGetsPricingInfoWhenItChangesTest()
{
    Contract c = new Contract();
    Signee s = new Signee();
    c.AddSignee(s);

    c.InitialPricingPlan = new PricingPlan { Dues = 42M };

    Assert.That(s.CurrentDues, Is.EqualTo(42));
}

 

The above test verifies one portion of our goal for us; namely, that when we change pricing on a contract after we have added a signee, the signee’s dues are also updated.

[Test]
public void ValidPricingInfoTransferredToSigneesAfterOneIsAddedTest()
{
    Contract c = new Contract
    {
        InitialPricingPlan = new PricingPlan
        {
            Dues = 42M
        }
    };

    Signee s = new Signee();
    c.AddSignee(s);

    Assert.That(s.CurrentDues, Is.EqualTo(42M));
}

 

The above test verifies the final portion of our goal; namely, that when signees are added after pricing has already been set on a contract, said signee has its pricing set correctly.

Since we’ve written no code yet, obviously the two tests above fail. Lets fix that.

 

The Observer Pattern With C# 4

C# 4 has introduced two new interfaces, IObserver<T> and IObservable<T>, for situations like the above. In our case, we want signees to observe their contracts so that they can update their dues values based on pricing plan changes. Our first step to achieve this is to have our Signee class implement the IObserver<T> interface.

using System;

namespace ObserverPattern.BizObjects
{
    public class Signee : IObserver<Contract>
    {
        public decimal? CurrentDues { get; set; }

        #region IObserver<Contract> implementation

        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(Contract value)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

 

We have three new methods that we need now that we are implementing IObserver<T>: OnCompleted, OnError, and OnNext. In this post we will only be implementing OnNext so that we can get our test to pass; future posts will make this code more robust.

Since our goal is to have update signee’s dues when the associated contract’s pricing has changed, we update our OnNext method as below.

public void OnNext(Contract value)
{
    if (value != null
        && value.InitialPricingPlan != null)
    {
        CurrentDues = value.InitialPricingPlan.Dues;
    }
}

 

Hopefully Demeter will forgive me. We are finished setting our observer up.

 

Observable<T>

Now we have to allow our defined observers to actually observe a contract. The cleanest way I’ve found so far to do this is to introduce a new class to help “track” our object of interest. This class appears below.

using System;
using System.Collections.Generic;

namespace ObserverPattern.BizObjects
{
    public class ContractTracker : IObservable<Contract>
    {
        public IDisposable Subscribe(IObserver<Contract> observer)
        {
            throw new NotImplementedException();
        }
    }
}

 

Our new ContractTracker class implements IObservable<Contract>, and will contain the necessary plumbing that stitches together IObserver<Contract> with an actual contract. The full implementation appears below.

using System;
using System.Collections.Generic;

namespace ObserverPattern.BizObjects
{
    public class ContractTracker : IObservable<Contract>
    {
        private List<IObserver<Contract>> _observers;

        public ContractTracker()
        {
            _observers = new List<IObserver<Contract>>();
        }

        public IDisposable Subscribe(IObserver<Contract> observer)
        {
            _observers.Add(observer);

            return null;
        }

        public void UpdateSubscribers(Contract c)
        {
            _observers.ForEach(x => x.OnNext(c));
        }
    }
}

 

As we can see, we are maintaining a list of observers which are added via the Subscribe method. We also added a method called UpdateSubscribers which our object under observation will use to push updates to any subscribers.

One final thing to note here is that we are returning a null object from Subscribe. When using these interfaces, we should return an object that allows a subscriber to unsubscribe itself. This also will be shown in future posts.

 

Putting It Together

The last part to getting this to work is to have our Contract class use our ContractTracker to actually propagate needed changes. The implementation appears below.

using System;
using System.Collections.Generic;

namespace ObserverPattern.BizObjects
{
    public class Contract
    {
        private List<Signee> _signees;
        private ContractTracker _tracker;

        public Contract()
        {
            _signees = new List<Signee>();
            _tracker = new ContractTracker();
        }

        public void AddSignee(Signee s)
        {
            _signees.Add(s);

            _tracker.Subscribe(s);
            _tracker.UpdateSubscribers(this);
        }

        private PricingPlan _initialPricingPlan;
        public PricingPlan InitialPricingPlan {
            get { return _initialPricingPlan; }
            set
            {
                _initialPricingPlan = value;
                _tracker.UpdateSubscribers(this);
            }
        }
    }
}

 

And with that, our tests pass. As stated above, future posts will improve on the robustness of the solution by actually implementing exception handling and the ability to unsubscribe from updates.