Home > Architecture > Application architecture with design patterns

Application architecture with design patterns

 

A design pattern can solve many problems by providing a framework for building an application. Design patterns, which make the design process cleaner and more efficient, are especially well-suited for use in C# development because it is an object-oriented language. Existing design patterns make good templates for your objects, allowing you to build software faster. This article describes several popular design patterns you can use in your own applications, including the singleton, the decorator, the composite, and the state classes, which can improve the extensibility of your applications and the reuse of your objects.

As any seasoned object-oriented software developer knows, it is unthinkable to discuss software design and architecture without at least a rudimentary understanding of design patterns. Most, if not all, software applications, tools, and systems incorporate one or more design patterns. A design pattern is a description of a set of interacting classes that provide a framework for a solution to a generalized problem in a specific context or environment. In other words, a pattern suggests a solution to a particular problem or issue in object-oriented software development. Additionally, patterns take into account design constraints and other factors that limit their applicability to the solution in general. Together, the classes, the communication and interconnections among those classes, and the contextual specifics define a pattern that provides a solution to any problem in object-oriented software design that presents characteristics and requirements matching those addressed by the pattern context.

Few developers have the luxury of writing only small programs. Modern software applications and systems are complex, comprising hundreds of thousands of lines of code, and I know of code bases that are even larger. Programming demands a lot more than simple mastery of tools and languages—corporate software development typically requires a great deal of flexibility in design and architecture to accommodate the ever-changing needs of clients and users at various stages of product development, and often after the product has been released. Such dynamics dictate that software design not be brittle. It should be able to accept changes without any undesirable ripple effect that would necessitate the reworking of other, potentially unrelated, subsystems. It is frustrating and counterproductive to add features and components to modules that were never designed for extensibility. Sooner or later, closed, inflexible designs break under the weight of changes. Design patterns assist in laying the foundation for a flexible architecture, which is the hallmark of every good object-oriented design.

Design patterns have been cataloged to address a variety of design problems, from small issues to large, architecture-level problems. In this article, I will describe some of the popular design patterns that I have found useful in my own projects. The article does not assume any prior knowledge of design patterns, although familiarity with concepts of object-oriented design will help. While any programming language that facilitates object-oriented development could be used to illustrate patterns.

C# and Design Patterns

C# is a modern programming language that promotes object-oriented software development by offering syntactic constructs and semantic support for concepts that map directly to notions in object-oriented design. This is in contrast to C++, which supports procedural as well as object-oriented (and generic) programming. Nonetheless, if you are a C++ programmer, getting up to speed with C# should be a snap—the learning curve for C++ programmers is flat. Even if you haven’t seen any C# code before, you should have no problem comprehending the example code in this article. In fact, I wouldn’t be surprised if you find the C# implementation of the design patterns cleaner, especially if you have used or coded the patterns before. Books and articles that discuss design patterns typically explain the problem and the context in great detail, followed by a formal description of the solution.

Let’s start with the simplest design pattern: Singleton.

Singleton

Anyone who has ever written an MFC application—no matter how small—knows what a singleton is. A singleton is the sole instance of some class. To use an MFC analogy, the global instance of the CWinApp-derived application class is the singleton. Of course, while it’s imperative that no additional instances of the application class be created, there really is nothing preventing you from creating additional instances. In situations like these, when you need to enforce singleton behavior for a specific class, a better alternative is to make the class itself responsible for ensuring that one and only one instance of the class can be created. Back in the MFC analogy, you see that the responsibility for keeping track of the solitary instance of the application class rests with the developers of the application. They must not inadvertently instantiate another application object.

It is most often the case that the singleton should also be globally accessible, and this is achieved by making the creation method public. However, unlike the scenario in which a global variable is instantiated as the singleton, this pattern prevents creation of any additional instances, while simultaneously allowing global access. Note that the class constructor is private—there is no way to circumvent the static method and directly create an instance of the class.

There are additional benefits, too. Specifically, this pattern can be extended to accommodate a variable number of instances of an object. For instance, let’s say you have an application with a dedicated worker thread that is dispatched whenever a particular task is required. In the interest of conserving system resources, you have implemented the thread as a singleton. At some point along the way, if you decide to scale up your application because the rate at which tasks arrive is too much for your singleton thread to handle, it will be fairly straightforward to increase the number of worker threads in the application because all the logic that creates the threads and grants access to them is confined to one class.

One other advantage to this pattern is that creation of the singleton can be delayed until it is actually needed.

A variable declared at global scope will be created on startup regardless of whether it is needed—it may very well be that the object isn’t always needed. C# doesn’t allow variables at global scope anyway, but it is possible to create an object on the heap at the outset of a method and not use it until much later, if at all. The Singleton pattern offers an elegant solution in such cases.
      Additionally, as an implementation vehicle, C# is superior to C++ for this design pattern in a subtle but important way. A C++-based implementation has to take into account some sticky issues related to lifetime management that are automatically taken care of by the C# runtime. This is a significant benefit, as all you need to do in the C# version is make sure you have a live reference to the singleton object for as long as it’s needed.

Strategy

Applications are often written so that the way they perform a particular task varies, depending on user input, the platform it’s running on, the environment it’s been deployed in, and so on. An example is asynchronous I/O on disk files: Win32® APIs under Windows NT® and Windows® 2000 support asynchronous I/O natively. However, that’s not the case with Windows 95 or Windows 98. An application that relies on asynchronous file I/O, therefore, has to execute two different algorithms, depending on the deployment platform—one that uses native Win32 APIs, and another that is built from scratch, perhaps using multiple threads. Clients of such a service will be oblivious to the fact that different algorithms are being executed; as far as they are concerned, the end result is the same and that’s all they care about.
Another example is downloading a file from some remote server on the Internet. An application that offers a file download service that accepts a URL as input needs to examine the URL, identify the protocol (FTP or HTTP, for example), and then create an object that can communicate with the remote server using that protocol. Note that depending on user input, a different algorithm (protocol) will be used. However, again, the end result is the same—a file is downloaded.

An interface is like a contract. It is a specification that inheriting classes must follow. More specifically, it defines method signatures but no implementations—the latter must be provided by the concrete classes that implement the interface. C# is clearly superior to C++ in this regard because C++ lacks native language support for interfaces. C++ programmers typically create interfaces by defining abstract classes with pure virtual methods. In C#, all interface members are public, and classes adhering to an interface must implement all methods in the interface.

Decorator

A client application often needs to augment the services provided by methods of some class, perhaps by inserting some preprocessing and post-processing tasks before and after the method calls, respectively. One way to accomplish this is to bracket each method invocation with calls to functions that achieve the desired effect. However, this approach is not only cumbersome, it also limits the framework’s extensibility. For instance, if distinct pre- and post-processing tasks were to be carried out for different clients, the application logic would be obscured by conditional statements, leading to a maintenance nightmare. The question, then, is how to enhance the functionality offered by a class in a manner that does not cause repercussions in client code. The Decorator pattern is just what’s needed.

The Decorator pattern thus allows dynamic and transparent addition and removal of responsibilities without affecting client code. It is particularly useful when a range of extensions or responsibilities can be applied to existing classes, and when defining subclasses to accommodate all those extensions is impractical.

Composite

The Composite pattern is useful when individual objects as well as aggregates of those objects are to be treated uniformly. An everyday example is enumerating the contents of a file folder. A folder may contain not only files, but subfolders as well. An application designed to recursively display the list of all files in some top-level folder can use a conditional statement to distinguish between files and directories, and traverse down the directory tree to display the names of files in the subfolders. A better approach is suggested by the Composite pattern. In this approach, every folder item, be it a file, a subfolder, a network printer, or a moniker for any directory element, is an instance of a class that conforms to an interface offering a method to display a user-friendly name of the element. In this case, the client application does not have to treat each element differently, thereby reducing the complexity of the application logic.

The implementation of Drawing.Draw uses the collection classes available in the System.Collections library. For more information on these and other libraries, check out the documentation in the .NET Framework SDK.

State

Every developer has implemented a finite state machine at least once. You can’t avoid them—they are everywhere, and not just limited to the world of software development. It’s no wonder that literature on the design and implementation of deterministic finite automata is also readily available. A popular design for finite state machines is based on table lookup. A table maps all possible inputs for each state to transitions that would lead the machine to perhaps a different state. Needless to say, while this design is simpler, it is unable to accommodate changes without significant modifications to the existing implementation. A better alternative is the solution offered by the State design pattern.

To summarize, the State design pattern helps localize state-specific behavior to classes that implement concrete states, which promotes reuse and extensibility. This removes the need for conditional statements that would otherwise be scattered throughout the code, making life difficult for maintenance programmers, who vastly outnumber implementers in the real world.

– Pull – MSDN

Advertisements
Categories: Architecture
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: