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: