Note: This post is part of a series and you can find the rest of the parts in the series index.
The pub/sub pattern is a simple concept, you have a provider which has data which it pushes to subscribers and .NET 4 brings in two new interfaces IObservable<T> and IObserver<T> which make implementing this pattern very easy.
To see the usage of these interfaces, I would like to use the example of a device which has a GPS sensor which every second checks it’s position and if the position of the GPS leaves a specified area we then want to notify the cops.
GPSPosition
First we need a small class which tells us where we are, i.e. it is just a data class. Some key points of this class are
- I have implemented IComparable so I can check when the boundary is exceeded.
- I’ve overridden ToString so it prints nicely.
- I have two constructors, a default and one which takes a latitude and longitude so that I can copy the values from one object to another.
- Note that my parameter is named @long – because long is a reserved keyword in C# you can append @ to use it.
class GPSPosition : IComparable<GPSPosition>
{
public int Lat { get; set; }
public int Long { get; set; }
public GPSPosition() { }
public GPSPosition(int lat, int @long)
{
this.Lat = lat;
this.Long = @long;
}
public override string ToString()
{
return string.Format("Latitude = {0:N4}, Longitude = {1:N4}", Lat, Long);
}
public int CompareTo(GPSPosition other)
{
if (this.Lat > other.Lat)
{
return 1;
}
if (this.Lat < other.Lat)
{
return -1;
}
// latitude is the same
if (this.Long > other.Long)
{
return 1;
}
if (this.Long < other.Long)
{
return -1;
}
// long is the same
return 0;
}
}
GPSSensor
Now we need to create our fake sensor, which when asked tells us where in the world we are. Key points here are:
- It implements IObservable<T> so it is a provider of GPSPosition data. That means we needed to implement the subscribe method so other objects can tell this class to send them the data.
- We keep a list of the observers in a List<T>
- We call the observer.OnNext to send data to it.
- We call observer.OnCompleted when we are done with monitoring, which in our example is when we exceed the boundary.
- Note that in the GetPosition method we are responsible for sending data to all the observers.
class GPSSensor : IObservable<GPSPosition>
{
List<IObserver<GPSPosition>> observers = new List<IObserver<GPSPosition>>();
Random random = new Random();
public GPSPosition Position { get; set; }
private GPSPosition boundry;
public GPSSensor()
{
this.Position = new GPSPosition();
this.Position.Lat = random.Next(0, 181);
this.Position.Lat = random.Next(0, 181);
boundry = new GPSPosition(this.Position.Lat + 10, this.Position.Long + 10);
}
public void GetPosition()
{
GPSPosition current = new GPSPosition(this.Position.Lat, this.Position.Long);
Position.Lat += random.Next(0, 5);
Position.Long += random.Next(0, 5);
if (current.CompareTo(this.Position) != 0)
{
foreach (IObserver<GPSPosition> observer in observers)
{
observer.OnNext(this.Position);
if (current.CompareTo(boundry) > 0)
{
observer.OnCompleted();
}
}
}
}
public IDisposable Subscribe(IObserver<GPSPosition> observer)
{
observers.Add(observer);
observer.OnNext(this.Position);
return observer as IDisposable;
}
}
Map
Our third class, Map is a subscriber of data and it handles the outputting to the screen and notification of the cops when we move past the boundary. Key notes here:
- It implements IObserver<GPSPosition> so it is a subscriber of GPS position data.
- We implement the three methods from the interface:
- OnCompleted for when we are done.
- OnError in case something goes wrong.
- OnNext for when new data is available.
class Map : IObserver<GPSPosition>
{
private GPSPosition lastKnown;
public bool StillTracking { get; private set; }
public void OnCompleted()
{
Console.WriteLine("The device has moved beyond the boundsof our checking, notify the cops it was last seen at: {0}", lastKnown);
StillTracking = false;
}
public void OnError(Exception error)
{
Console.WriteLine("SkyNet has taken over and shut down the GPS");
}
public void OnNext(GPSPosition value)
{
lastKnown = value;
Console.WriteLine("At {0} we have moved to {1}", DateTime.Now, value);
StillTracking = true;
}
}
Main
We have created our data structure, our provider and and our subscriber - now we just need to bring them together in our main method, which is very easy:
public static void Main()
{
GPSSensor sensor = new GPSSensor();
Map map = new Map();
sensor.Subscribe(map);
do
{
sensor.GetPosition();
Thread.Sleep(1000);
} while (map.StillTracking);
}
This produces something that looks like: