Introduction
In this article, I will explain how I solved a common problem I used to have regarding logging within my business layer. Normally, I would have a function call from my presentation layer to my business layer; however, once in the business layer, I had no access to the presentation layer anymore. There are many ways to solve this problem. In the MFC days, a Doc / View approach based on the Observer pattern was used. In .NET, we can use custom events to notify the presentation layer that something happened. But, in this article, I will show the Composite pattern in action, and some of its nice advantages.
The Requirements
Suppose we had a function in our business layer that performs a lot of updates. It would be nice that during all these updates, we log the exact action taking place. We want to easily log a string describing the activity to one of, all of, or some of the following loggers: TextBox
, ListBox
, text file and/or the EventLog.
Creating the ILogger Interface
To keep the example simple, I will implement a simple ILogger
interface that only requires a logger class to implement its own way of logging an activity.
public interface ILogger
{
void LogMessage(string strMessage);
}
Creating Logger Classes
Create the following logger classes:
- Text box logger - logs a message into a text box
- List box logger - adds a message to the list
- File logger - logs a message into a file
- EventLog logger - logs a message into the system event log
ListBox Logger
Notice that the ListBox
and TextBox
loggers are thread safe, by using the InvokeRequired
method.
class ListBoxLogger : ILogger
{
ListBox m_listBox;
public ListBoxLogger(ListBox listBox)
{
m_listBox = listBox;
}
public void LogMessage(string strMessage)
{
MethodInvoker logDelegate = delegate
{
m_listBox.Items.Add(strMessage);
};
if (m_listBox.InvokeRequired)
m_listBox.Invoke(logDelegate);
else
logDelegate();
}
}
TextBox Logger
class TextBoxLogger : ILogger
{
private TextBox m_textBox;
public TextBoxLogger(TextBox txtBox)
{
m_textBox = txtBox;
}
public void LogMessage(string strLogMessage)
{
MethodInvoker logDelegate = delegate { m_textBox.Text = strLogMessage; };
if (m_textBox.InvokeRequired)
m_textBox.Invoke(logDelegate);
else
logDelegate();
}
}
File Logger
class FileLogger : ILogger
{
private string m_strFileName;
private object m_sync = new object();
public FileLogger(string strFileName)
{
m_strFileName = strFileName;
}
public void LogMessage(string strMessage)
{
lock (m_sync)
{
using (StreamWriter writer = new StreamWriter(m_strFileName))
{
writer.WriteLine(strMessage);
}
}
}
}
Notice that the FileLogger
is thread-safe, and that it opens and closes the file before writing to it. This guarantees that the message is flushed after each call to LogMessage
.
EventLog Logger
class EventLogger : ILogger
{
public EventLogger()
{
}
public void LogMessage(string strMessage)
{
EventLog.WriteEntry("Logger", strMessage);
}
}
Using the ILogger Object
So far, there is nothing special here; we can have a function that takes a ILogger
object and allow us to log messages. For example:
private void DoSomthing(ILogger logger)
{
for(int i=0; i < 10; i++)
{
logger.LogMessage("Logging a message " + i.ToString());
}
}
The client code looks like this:
DoSomthing(new FileLogger("C://LogMessage.txt"));
DoSomthing(new TextBoxLogger(textBox));
Introducing the CompositeLogger
But what if I want to log to all my loggers at the same time, or what if I want to log to only some of my loggers? To solve this requirement, I create a new logger called a Composite Logger.
class CompositeLogger : ILogger
{
private ILogger[] m_loggerArray;
public CompositeLogger(params ILogger[] loggers)
{
m_loggerArray = loggers;
}
public void LogMessage(string strMessage)
{
foreach (ILogger logger in m_loggerArray)
logger.LogMessage(strMessage);
}
}
Let's Use Our Composite Logger to "Configure" Which Loggers to Use
Using all the loggers:
CompositeLogger compositeLogger =
new CompositeLogger(new TextBoxLogger(textBox),
new ListBoxLogger(listBox),
new FileLogger("C:\\LogPattern.txt"),
new EventLogger() );
Creating a composite logger with only TextBoxLogger
and ListBoxLogger
:
CompositeLogger compositeLogger =
new CompositeLogger(new TextBoxLogger(textBox),
new ListBoxLogger(listBox));
Because our composite logger implements ILogger
like all the other loggers, we can pass it to a function that expects an ILogger
object type.
DoSomthing(compositeLogger);
Testing the Composite Logger in a Thread
Let’s test our composite logger in a separate thread, just to make sure we can update the UI from a thread other than the UI-thread.
CompositeLogger compositeLogger =
new CompositeLogger(
new TextBoxLogger(textBox),
new ListBoxLogger(listBox),
new FileLogger("C:\\LogPattern.txt"),
new EventLogger());
ParameterizedThreadStart threadDelegate = delegate(object obj)
{
ILogger logger = (ILogger)obj;
DoSomthing(logger);
};
Thread t = new Thread(threadDelegate);
t.Start(compositeLogger);
Conclusion
Notice that I am now able to pass an ILogger
object to a method within the business layer or to the data layer, and provide an easy way to log a message. This is done by using the ILogger
interface, and therefore, your business layer or data layer requires no extra references to File.Io
, or Windows.Forms
, it only needs to have a reference to ILogger
. It is also nice that you can easily add and remove loggers by using your composite Logger. Thank you for reading, have a good day!
I am currently working as a team leader with a group of amazing .NET programmers. I love coding with .NET, and I love to apply design patterns into my work. Lately I had some free time, so I decided to write some articles, hoping I will spare someone frustration and anxiety.