Observer Design Pattern
Observer Design Pattern
In today’s episode of Software Design Patterns you will learn everything about the Observer Design Pattern. The Observer Pattern is pretty common and widely used. Compared to the already covered creational design patterns like Singleton and Factory Method the Observer is a behavioral pattern.
All the examples, source code and unit tests are in this GitHub repository.
This Blog post and the code example is strongly inspired by the Microsoft's definition and implementation.
What’s the purpose of the Observer Design Pattern?
The Gang-of-Four [1] reference book ("Design Patterns. Elements of Reusable Object-Oriented Software") describes the intent of the Observer Pattern as the following.
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Basically you have an observable object (also often named subject) which holds a list of observers. This observable notifies all observers when a state changes. This is archived by calling one of their methods which is often called Notify() or OnNext(). In my example I have a list of newspaper which are subscribed to a news agency - called news handler. After the news handler puts out new news every subscribed newspaper will get notified. In addition to that every new newspaper will receive all existing news as well. This behavior depends on the use case - it could also be useful that you only want to receive brand-new news and not old ones.
How does the Observer look like?
In the picture below you find the Unified Modeling Language (UML) diagram.
In the linked GitHub repository you will find the News class which represents the data model for news.
public class News
{
public int Id { get; set; }
public string Headline { get; set; }
public string Description { get; set; }
public override string ToString()
{
return string.Join(";", new[] { Id.ToString(), Headline, Description });
}
}
The NewsPaper class implements the IObserver interface, which provides all the structure for all observers.
public class NewsPaper : IObserver
{
public IList newsInfo = new List();
public string Name { get; }
private IDisposable cancellation;
public NewsPaper(string name)
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentNullException("The observer must be assigned a name.");
}
Name = name;
}
public void Subscribe(NewsHandler provider)
{
cancellation = provider.Subscribe(this);
}
public void Unsubscribe()
{
cancellation.Dispose();
newsInfo.Clear();
}
public void OnCompleted()
{
newsInfo.Clear();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(News value)
{
newsInfo.Add(value.ToString());
}
}
Those interfaces are from directly the System name space in .Net.
In the test project you'll find test cases for subscribing, notifying and unsubscribing from observers.
public class ObserverPatternTest
{
[Fact]
public void ShouldPublishToAllSubscribers()
{
var newsHandler = new NewsHandler();
var newsPaperOne = new NewsPaper("New York Times");
var newsPaperTwo = new NewsPaper("Washington Post");
var news = GenerateTestNews();
newsHandler.PostNews(news[0]);
newsPaperOne.Subscribe(newsHandler);
newsHandler.PostNews(news[1]);
newsPaperTwo.Subscribe(newsHandler);
newsHandler.PostNews(news[2]);
newsPaperOne.newsInfo.Count.Should().Be(3);
newsPaperTwo.newsInfo.Count.Should().Be(3);
newsPaperOne.newsInfo[0].Should().Be(news[0].ToString());
newsPaperOne.newsInfo[1].Should().Be(news[1].ToString());
newsPaperOne.newsInfo[2].Should().Be(news[2].ToString());
newsPaperTwo.newsInfo[0].Should().Be(news[0].ToString());
newsPaperTwo.newsInfo[1].Should().Be(news[1].ToString());
newsPaperTwo.newsInfo[2].Should().Be(news[2].ToString());
}
[Fact]
public void ShouldResetAllNewsWhenUnsubscribed()
{
var newsHandler = new NewsHandler();
var newsPaperOne = new NewsPaper("New York Times");
var news = GenerateTestNews();
news.ToList().ForEach(newsHandler.PostNews);
newsPaperOne.Subscribe(newsHandler);
newsPaperOne.newsInfo.Count.Should().Be(3);
newsPaperOne.Unsubscribe();
newsPaperOne.newsInfo.Should().BeEmpty();
}
private static IList GenerateTestNews()
{
var ids = 1;
return new Faker()
.StrictMode(true)
.RuleFor(n => n.Id, f => ids++)
.RuleFor(n => n.Headline, f => f.Lorem.Sentence())
.RuleFor(n => n.Description, f => f.Lorem.Paragraph())
.Generate(3);
}
}
To make the tests more readable I am using FluentAsserstions. For generating test content I highly recommend using Bogus.
If you have any questions or comments feel free to leave them below.
That’s it. Happy coding as always :).
References
[1] –Design patterns, software engineering, object-oriented programming