|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionA lot of information about GoF design patterns has been published, however most of the examples use separate illustration for each pattern, and the sample code is not an application with a real function. The Storage Explorer is a small application that is designed to use several design patterns together. While the main purpose of this submission is to share the code than to teach the reader about design patterns, the discussion about the application design itself is explained using design patterns. This article discusses the structure of the application, the design patterns that I use, how it works and the advantages of using them. In my opinion design pattern is worth learning. Developers who have design pattern skill can recognize the intent of a program and the purpose by identifying the patterns. The classes with complex relations then can be understood better and faster than if the developers only understand basic OO design. The design patterns can be used as a thinking model for the designer and the code reader. The application is used to explore file composition inside a computer storage. The design patterns that are used are: Strategy, Observer, Adapter, Template Method, Singleton and Wrapper Façade. The first five are known as GoF design patterns and the last one is a POSA pattern (POSA book volume-2). BackgroundThe idea of this application came when I desperately needed an application similar to Windows Explorer that can show me the file size composition inside my storage. I want to know, for example, under the Program Files folder, which folder uses what size of space, including the numbers in percentage. I want also to know how big my mp3 total size in drive D compare to my jpg files. The information I want to see should look like this:
And this
I need the information is presented in a chart to help me visualize the numbers as well. The FeaturesThe application features are:
The Design Patterns inside the ApplicationThe design patterns used in this application is shown in the following figure.
The discussion for each design pattern is given below. StrategyIntent: “Define a family of algorithms, encapsulate each one, and make them interchangeable” (GoF).
The application explores the file system using two different algorithms, which is implemented in two strategy classes: Now suppose we want to add a new algorithm. It can be done by creating a new subclass under public abstract class ExplorationStrategy
{
public virtual void Explore (...){}
}
public class NewAlgorithm : ExplorationStrategy
{
public override void Explore (...)
{
// the new algorithm
}
}
public class StorageExplorerForm : System.Windows.Forms.Form
{
// Somewhere in the initialization section
ExplorationStrategy explorer;
ExplorationStrategy folderStrategy = new FolderStrategy ();
ExplorationStrategy fileTypeStrategy = new FileTypeStrategy ();
ExplorationStrategy newStrategy = new NewAlgorithm ();
// Somewhere else, an algorithm is selected
switch (...)
{
case 0: explorer = folderStrategy; break;
case 1: explorer = fileTypeStrategy; break;
case 2: explorer = newStrategy; break;
}
// Execute the algorithm
explorer.Explore (...);
}
ObserverIntent: “Define a one-to-many dependency between object so that when one object changes state, all its dependent are notified and updated automatically” (GoF).
In the application, this pattern creates a relationship between the subjects (concrete The object and observer relationship is established by registering the observer public abstract class ExplorationObserver
{
public void SubscribeToExplorationEvent (ExplorationStrategy obj)
{
obj.Finish += new ExplorationFinishEventHandler (UpdateDisplay);
}
}
And during application initialization in the client (the application form), the concrete observer object call // Initialization in the client:
// Create the concrete subject
ExplorationStrategy folderStrategy = new FolderStrategy ();
ExplorationStrategy fileTypeStrategy = new FileTypeStrategy ();
// Create the concrete observer
ExplorationObserver pieChart = new PieChartAdapter ();
// Subscribe the concrete observer object to the concrete strategy object
pieChart.SubscribeToExplorationEvent (folderStrategy);
pieChart.SubscribeToExplorationEvent (fileTypeStrategy);
Now let see the beauty of this pattern. Suppose we want to change the application to accommodate a new requirement. We want to save the exploration result into a file in addition to displaying it on the screen. For that purpose, inherits a new class from public class NewConcreteObserver : ExplorationObserver
{
public override void UpdateDisplay (object o, ExplorationFinishEventArgs e)
{
Hashtable result = e.ExplorationResult;
// Write the result to a file
}
}
// Somewhere, during the initialization in the client
...
ExplorationObserver fileSaver = new NewConcreteObserver ();
fileSaver.SubscribeToExplorationEvent (folderStrategy);
fileSaver.SubscribeToExplorationEvent (fileTypeStrategy);
Observer design pattern is actually always used in the event driven programming. The idea is often used without being realized. However knowing the conceptes is still important if we want to collaborate it with another pattern like Adapter, as explained in the next section. AdapterIntent: “Convert the interface of a class into another interface client expects. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces” (Gof). Now here is the case: I want the exploration result to be displayed in a .NET The adapter pattern has two forms: class adapter and object adapter. The class adapter is implemented by creating The second form, the object adapter, which is used in this application, uses object composition (see Figure below).
The public class ListViewAdapter : ExplorationObserver
{
protected ListView listView;
public ListViewAdapter()
{
// Create a ListView object and initialze it
listView = new ListView();
...
}
}
The same approach is applied to the
Template MethodIntent: “Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain step of an algorithm without changing the algorithm structure” (GoF). The template method itself is a method in a class that calls other primitive method(s), so that when the subclass overrides the primitive method(s), the template method in the descendant class produces different results. Now let see the template method implementation in this application (see Figure below).
The template method inside the Chart is the public abstract class Chart : Control
{
// OnPaint is a template method. It contains steps
// necessary to draw a complete chart
public OnPaint ()
{
...
DrawLegend (...);
DrawChart (...);
}
protected abstract void DrawChart (...)
protected virtual void DrawLegend (...) { // Draw legend }
}
public abstract class PieChart : Chart
{
protected virtual void DrawChart (...)
{
// Draw the pie chart image
}
}
public abstract class BarChart : Chart
{
protected virtual void DrawChart (...)
{
// Draw the bar chart image
}
}
I use the same approach with the
The key to use the Template Method is to define a skeleton of an algorithm as general as possible in the base class and defer the steps that could vary to the descendant class. Also in the base class, we need to create some default implementation of the deferred step in case the descendant classes do not want to override the steps. Once we have a good template and default implementation, then we can enjoy creating variety of descendant classes with less effort. SingletonIntent: “Ensure a class only has one instance, and provide a global point of access to it” [GoF].
In this application there are two classes in which each of them needs only one instance to run: MSDN provides an example on how to implement the singleton in .NET using C#. In this application the .NET singleton is implemented as follows: sealed class FileIcons
{
...
// By making it private, the class cannot be explicitly created
private FileIcons () {}
// This is the statement that ensures only one instance exists
public static readonly FileIcons Instance = new FileIcons();
public int GetIconIndex (...)
{
...
}
}
To use the int iconIndex = FileIcons.Instance.GetIconIndex (...);
Wrapper FacadeIntent: “ Encapsulate the functions and data provided by existing non-object oriented APIs within more concise, robust, portable, maintainable, and cohesive object-oriented class interfaces” (POSA volume 2).
To access the shell icons from the windows operating system we must call the Shell API and Win API functions. The function call involves strict usage of data structure and complex parameter. To encapsulate that complexity I created an | ||||||||||||||||||||