Monday, August 28, 2017

Design Pattern example: Singleton and Observer (Part III)

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

In my last two postings on this subject, I outlined a Logger class used by my Parser class for logging errors.  In this article, I show a more sophisticated approach for the application to register and unregister as an interested Observer with Logger, so it is notified when an error message is posted to Logger.

First, the Observer class must derive from the ILoggerObserver interface:

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


In my original posting, I showed the following code snippet showing the Observer register and unregister sequence:


public class MyParser : ILoggerObserver
{
    ...

    public void MyParseLogic()
    {
        // Register to log any parsing errors
        Logger log = Logger.Instance;
        log.Attach(this);

        // Parse my data file
        Parser p = new Parser(my_file_path_to_be_parsed);
        p.Parse(); // will call the same Logger instance

        log.Detach(this); // resign as a Logger observer

        ...
    }

    /// Implement the ILoggerObserver interface
    public void didAddLogMessage(string message)
    {
        // Output the message to a TextBlock control or
        // pop up a Message Box.
        ...
    }

} // end class MyParser

Note that before parsing is done, the MyParser class calls Logger's Attach method to register as an observer, and after finishing with the parsing, calls Detach to unregister. But I later came up with a better design so Detach can be done automatically. I accomplish this by taking advantage of the .NET IDisposable interface.

I created a new container class named LoggerObserverDisposer, derived from IDisposable. Its only job is to call Logger's Detach for the Observer when it's being disposed by .NET. The target Observer object is provided to its constructor and remembered for when the Detach is done.

Here is that new Disposer class:


/// Container class for an observer, which terminates its observing
/// when out of scope.
public class LoggerObserverDisposer : IDisposable
{
    protected ILoggerObserver _observer = null;
    public LoggerObserverDisposer(ILoggerObserver observer)
    {
        // Remember the observer object to be disposed
        _observer = observer;
    }

    public void Dispose()
    {
        Dispose(true);  // call our Dispose method below
        // prevent redundant Dispose during garbage collection
        GC.SuppressFinalize(this); 
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Get the Logger object
            Logger logger = Logger.Instance;

            // Make operation atomic
            Monitor.Enter(logger);
            if (_observer != null)
            {
                // Remove the observer from Logger
                logger.Detach(_observer);
                _observer = null;
            }
            Monitor.Exit(logger);
        }
    }
}   // end class LoggerObserverDisposer


Previously, here is what Logger's Attach method looked like:


    /// 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);
        }
    }


Now, it is changed to make use of our new LoggerObserverDisposer class. Attach returns a new LoggerObserverDisposer instance for the Observer, but only if requested by the caller.


    /// Add an Observer object to the list of those to be notified
    /// of changes, and return a Disposer object for the observer,
    /// if requested.
    /// 
    /// Params:
    /// "observer" - The Observer object to add to the list.
    /// "doMakeDisposer" - true to return a Disposer object.
    /// Returns: A Disposer object, if requested, or null
    public LoggerObserverDisposer Attach(ILoggerObserver observer,
                                       bool doMakeDisposer = false)
    {
        if (null != observer)
        {
            // Add this observer to the list
            Detach(observer);
            _observers.Add(observer);
        }

        // Return an object the caller can include in a
        // 'using' statement so the observer is automatically
        // removed when the Disposer goes out of scope
        return (doMakeDisposer) ?
                new LoggerObserverDisposer(observer) : null;
    }


Finally, the MyParser class changes to use this new version of Logger's Attach, but never needs to explicitly call Detach:

    public void MyParseLogic()
    {
        // Register to log any parsing errors
        Logger log = Logger.Instance;
        using (logger.Attach(this, true))
        {
            // Parse my data file
            Parser p = new Parser(my_file_path_to_be_parsed);
            p.Parse(); // will call the same Logger instance
        }

        ...
    }

The changed code takes advantage of the C# "using" statement, which expects an IDisposable-derived object. When the "using" block goes out of scope, that object's Dispose method is called. In our case, the new Attach method returns a LoggerObserverDisposer object (rather than void as before), which remains in scope throughout the "using", even though I never assign it to a variable. Thus, LoggerObserverDisposer::Dispose is called when the "using" block loses scope, which in turn calls Logger's Detach for the Observer MyParser class.

This logic saves the programmer from having to remember to manually Detach, and possibly leave a dangling Observer that has since been destroyed.

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.