Monday, August 21, 2017

Design Pattern example: Singleton and Observer (Part II)

Copyright © 2017, Steven E. Houchin. All rights reserved.


In my previous post on this subject, I introduced a class named Logger, which is used by my parser class to log parsing errors. It is implemented using the Singleton design pattern. The Logger class also uses the Observer design pattern where other interested objects can register with Logger to be notified when new messages are logged.

This article shows the notification logic used by Logger.  But first, a quick review of Logger:

public class Logger : object
{
    // The Singleton instance
    protected static Logger _instance;

    // List of Observer objects to be notified of
    // changes to the log
    protected List(lt)ILoggerObserver(gt) _observers;

    /// Hidden constructor
    private Logger()
    {
         _observers = new List(lt)ILoggerObserver(gt)();
    }

    ...

    /// Add an Observer object to the list of those
    /// to be notified of changes.
    public void Attach(ILoggerObserver observer)
    {
        if (null != observer)
        {
            // Add this observer to the list, but just once
            Detach(observer);
            _observers.Add(observer);
        }
    }

    /// Remove an Observer object from the list.
    public void Detach(ILoggerObserver observer)
    {
        if (null != observer)
            _observers.Remove(observer);
    }

    ...

    public void LogError(string msg)
    {
        // ... do something to log the error somewhere ...
    }
}

Objects wishing to be notified must implement the abstract interface Logger calls:

///
/// Interface to be implemented by objects who wish to
/// observe changes on the Logger object's data.
///
public interface ILoggerObserver
{
    void didAddLogMessage(string message);
}

Now, let's look at the implementation of the LogError method. It needs to notify the observers that a message has been logged, so:


    public void LogError(string msg)
    {
        if ((null == msg) || (0 == msg.Length))
           return;

        // Prepend an identifier prefix, then notify observers
        Notify(@"[Error] " + msg);
    }

    ///
    /// Notify all observers of the logged message.
    ///
    protected void Notify(string message)
    {
        foreach (ILoggerObserver observer in _observers)
        {
            // Notify the Observer of the logged message
            observer.didAddLogMessage(message);
        }
    }


Above, we have given substance to the LogError method, which in turn calls the Notify method to do the actual work of notifying the observer objects. The observer objects each implement the didAddLogMessage method, where the message reaches it's ultimate destination, such as a log file or a UI control. With the Notify method separated out like this, the class can easily be enhanced to add LogWarning and LogInfo methods, which prepend a different prefix to show the message's severity.

In a follow-up posting, I'll show an improved way to attach and detach an observer object from the Logger class.

No comments: