Click here to Skip to main content
15,885,537 members
Articles / Programming Languages / C#
Article

Illustrated GOF Design Patterns in C# Part III: Structural II

Rate me:
Please Sign up or sign in to vote.
4.90/5 (18 votes)
11 Nov 2002CPOL10 min read 119.6K   578   190   3
Part III of a series of articles illustrating GOF Design Patterns in C#

Abstract

Design Patterns, Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides [also known as the Gang of Four (GOF)] has been a de facto reference for any Object-Oriented software developer. This article is Part III of a series of articles illustrating the GOF Design Patterns in C#. We will discuss the Facade, Flyweight, and Proxy patterns. It is assumed the reader is familiar with basic C# syntax and conventions, and not necessarily details of the .NET Framework.

Background

In Design Patterns, each pattern is described with its name (and other well-known names); the motivation behind the pattern; its applicability; the structure of the pattern; class/object participants; participant collaborations; pattern consequences; implementation; sample code; known uses; and related patterns. This article will only give a brief overview of the pattern, and an illustration of the pattern in C#.

A design pattern is not code, per se, but a "plan of attack" for solving a common software development problem. The GOF had distilled the design patterns in their book into three main subject areas: Creational, Structural, and Behavioral. This article deals with the Structural design patterns, or how objects are composed. The first two articles in this series dealt with the Creational patterns and the first half of the Structural patterns. This article finishes off the Structural patterns as described by the Gang of Four.

This article is meant to illustrate the design patterns as a supplement to their material. It is recommended that you are familiar with the various terms and object diagram methods used to describe the design patterns as used by the GOF. If you're not familiar with the diagrams, they should be somewhat self-explanatory once viewed. The most important terms to get your head around are abstract and concrete. The former is a description and not an implementation, while the latter is the actual implementation. In C#, this means an abstract class is an interface, and the concrete class implements that interface.

Structural Patterns

To quote the GOF, "Structural patterns are concerned with how classes and objects are composed to form larger structures. Structural class patterns use inheritance to compose interfaces or implementations. As a simple example, consider how multiple inheritance mixes two or more classes into one. The result is a class that combines the properties of its parent classes. This pattern is particularly useful for making independently developed class libraries work together."

Facade

Often in software development, there are time where a complex system exists, and there is a need to simplify its use. As software architects, we've often had to provide a contextually meaningful "interface" to existing systems in such a way as to simplify their use. The facade pattern solves our problem:

Facade structure

In C#, as it is in many object-oriented languages, this is one of the easiest patterns to use (refer to the sample code facade.cs.) Suppose we have an aircraft, composed of various subsystems: The engine and its controls, the avionics, and the flight control surfaces. Flying the aircraft entails knowledge and use of the various subsystems, and after a while, experience melds these together. Flying an airplane becomes second nature, working the various subsystems together to achieve a flight. Our subsystems are encapsulated in several classes, with a CFly class providing a facade to flying:

C#
public class CFly
{
    private CEngine m_oEngine;
    private CAvionics m_oRadios;
    private CControls m_oControls;

    public CFly()
    {
        m_oEngine = new CEngine();
        m_oRadios = new CAvionics();
        m_oControls = new CControls();
    }

    public void Takeoff()
    {
        // use encapsulated subsystems to takeoff
    }
}

A Facade certainly means less work for the client, while still allowing direct use of the subsystems.

Flyweight

A large issue in software design is the reuse of commonly used objects. For example, a word processor allows you to type letters and symbols and format them to create a document. If one were to use objects for each letter and its formatting, even a moderately sized document will eat up quite a bit of memory. Given the fact that the characters are independent of the formatting, it would be more efficient to "pool" the limited subset of characters and reuse them as necessary within a formatting state. This is the basis for the Flyweight structural design pattern, to "Use sharing to support large numbers of fine-grained objects efficiently." (GOF).

The GOF suggest that you use the Flyweight pattern when all the following are true:

  • An application uses a large number of objects.
  • Storage costs are high because of the sheer quantity of objects.
  • Most object state can be made extrinsic [that is, the object state that can be defined outside the object itself.]
  • Many groups of objects may be replaced by relatively few shared objects once extrinsic state is removed.
  • The application doesn't depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects.

Let's look at the structure of the Flyweight pattern:

Flyweight structure

As you can see, there are several things going on here: the use of a Factory for creation of flyweight objects or their reuse; an interface to the Flyweight; concrete implementations of the Flyweight interface; and the client. Because our Flyweights are shared, clients should not create them directly, thus the use of a Factory to create them. The UnsharedConcreteFlyweight illustrates that though the "Flyweight interface enables sharing; it doesn't enforce it. It's common for UnsharedConcreteFlyweight objects to have ConcreteFlyweight objects as children at some level in the flyweight object structure." (GOF)

For our illustration (flyweight.cs,) we shall use the following scenario: An airplane manufacturer's assembly control software has a limited number of robots it uses to put an aircraft together. The aircraft is made up of many parts, and these robots each assemble them together sequentially, according to the current state of assembly. There is concern that there could be a huge memory issue because of the sheer number of instantiated objects needed to represent the robots for any given assembly "run." This is a candidate for the Flyweight because of the aforementioned reasons.

Attacking the problem, we know the limited set of robot objects each do their operation according to a particular state, but that most of the state can be separated from the object. We still have to inform the object of this external state whenever we ask to do an operation, but we can store that state context outside the set of objects.

We begin, for simplicity, by using a string as the external state. Our robot interface, IAssemblyRobot would be defined as follows:

C#
public interface IAssemblyRobot
{
   void DoWork(string state);
}

We would then create concrete implementations of the robot objects, one for each particular robot and how it performs the assembly. Remember, we do not know the state ahead of time, but each concrete robot object "knows" how to perform its duties given a particular state.

We furthermore enforce our pattern by creating a Factory to manage use and instantiation of the robot objects:

C#
public class RobotFactory
{
    public enum RobotType { PAINTING, WELDING, MOLDING }

    private static Hashtable m_Robots = new Hashtable();

    public static IAssemblyRobot UseRobot(RobotType t)
    {
        // does one exist?
        IAssemblyRobot r = (IAssemblyRobot)m_Robots[t];

        if (null == r)
        {
            // nope, create the new robot
            switch (t)
            {
            case RobotType.PAINTING: 
                r = new PaintingRobot(); 
                break;
            case RobotType.WELDING: 
                r = new WeldingRobot(); 
                break;
            case RobotType.MOLDING: 
                r = new MoldingRobot(); 
                break;
            }

            m_Robots[t] = r;
        }

        return r;
    }
}

We now need a mapping of what the current state is during the run, and what robot object is used at the point in the run:

C#
public class RunNode
{
    public IAssemblyRobot robot;
    public string state;

    public RunNode(IAssemblyRobot r, string s)
    {
        robot = r;
        state = s;
    }
}

The client will use an ArrayList composed of RunNode objects to "build" the sequential operations necessary to build an aircraft:

C#
ArrayList aRun = new ArrayList();

//   construct the run
aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.MOLDING), 
        "prefab airframe parts"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.WELDING), 
        "prefab airframe parts"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.WELDING), 
        "airframe subassembly"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.PAINTING), 
        "airframe subassembly"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.MOLDING), 
        "airframe subframe collation"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.WELDING), 
        "airframe subframe collation"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.WELDING), 
        "airframe final collation"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.WELDING), 
        "airframe final assembly"));

aRun.Add(
    new RunNode(
        RobotFactory.UseRobot(RobotFactory.RobotType.PAINTING), 
        "airframe final assembly"));

//   perform the run
Console.WriteLine("Running aircraft assembly...");

foreach (RunNode r in aRun)   
    r.robot.DoWork(r.state);

This illustration is very simplified. ArrayLists are fun and easy, but as seen in the illustration, there's the problem of state reuse and management. In a real-world implementation, the mapping of state to the flyweight would probably be done using a binary tree or some other efficient storage mechanism, with a pool, perhaps, of state.

Proxy

The GOF define a Proxy as a pattern to "Provide a surrogate or placeholder for another object to control access to it." Sometimes, objects are expensive to create and initialize. It can be a good design decision to defer the expensive operations until a time when they are actually needed, to use a lightweight placeholder in lieu of the expensive object. You see this in Microsoft Word® where an object has been inserted into a document, but doesn't need to be fully loaded until edited or rendered.

There are variants of proxies: remote, where you represent a remote object through a local object; virtual, which provides on demand creation of expensive objects; protection, which controls access to the original object; and a smart reference, also known as a smart pointer, which provides "decorated" functionality to the proxied object (such as a smart pointer, persisted object loader, or wrapper object for multithreaded operations to a single-threaded object.)

The Proxy structural design pattern's structure looks like this:

Proxy structure

For our example (proxy.cs) we'll use the following scenario: An airport Fixed Base of Operations [FBO] manager needs software that will help him manage hundreds of airplanes. His database of aircraft only contains basic aircraft information; however, he has partnered with other operators at his airport and others to use several web services and databases for more details about each aircraft such as fees, incidentals, and maintenance. Using a Proxy, we gain an advantage by using basic aircraft information, and only loading the details from these varied sources when actually needed [just in time.]

In our illustration we highly simplify things and won't actually query databases or web services for brevity, but it should illustrate the basic idea. We begin by defining the classes for the full aircraft details, and its proxy:

C#
public class AircraftDetails
{
    public string MaintenanceRecords
    {
        get { return "--> List of maintenance records."; }
    }

    // ...
}

public class AircraftDetailsProxy
{
    // ...

    public AircraftDetailsProxy(string strName,
        string strTailNo, string strOwner)
    {
        m_oReal = null;

        // ...
    }

    public AircraftDetails Details
    {
        get
        {
            if (m_oReal != null) return m_oReal;
            
            // and load data
            m_oReal = new AircraftDetails(); 

            return m_oReal;
        }
    }
}

Notice that the Details method actually performs the expensive operation of creating and initializing the details. In a real world implementation, it would go out and fetch information at that point. Furthermore, the AircraftDetails class itself may use proxies for on-demand information, rather than loading everything at once.

Conclusions

Structural design patterns allow for great flexibility in how your software's objects are composed:

A Facade allows you to shield clients from the dirty work of directly using subsystems. It also allows you to eliminate complex or circular dependencies, allowing independent development of subsystems and clients, and it still allows for direct use of subsystems as necessary.

The Flyweight pattern is useful where a limited set of objects are used a large number of times performing stateful operations. One must consider the states that can be externalized for the operations. The Flyweight pattern will not help if there are as many external (extrinsic) states as there are objects that need to be shared. One must also be aware of the data storage costs involved. "Ideally, extrinsic state can be computed from a separate object structure, one with far smaller storage requirements." (GOF) The sample code was a poor example of real-world efficient storage of extrinsic state, even though it illustrated the Flyweight pattern. The data structure algorithms studied in computer science courses come in quite handy for this pattern because efficient data storage and retrieval for the mappings is extremely important in order to maximize any benefits.

The Proxy pattern allows for additional mechanisms for dealing with objects, such as on demand creation and initialization of expensive objects. It also allows reference counting (much like AddRef and Release in old-school COM, where if a reference goes to zero, the object is removed; or perhaps for a network connection Proxy where if no one is using it, it closes.) A Proxy can also provide additional housekeeping mechanisms during object access. This pattern may have the additional side-effect of making it appear that a system is more "snappy" than it actually is. The use of proxies, caching and other mechanisms can work together to increase the overall robustness of a system, as well as increase scalability through smart resource usage and allocation.

Stay tuned for future articles...

Building the Samples

Unzip the source files to the folder of your choice. Start a shell (cmd.exe) and type nmake. You may have to alter the Makefile to point to the correct folder where your .NET Framework libraries exist.

History

2002-11-11 Initial Revision

References

  • Design Patterns, Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Addison Wesley Longman, Inc. 1988. ISBN 0-201-63498-8.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
United States United States
20+ years as a strategist at the intersection of business, design and technology.

Comments and Discussions

 
GeneralFlyweight objects and collections Pin
supercat98-Feb-10 13:17
supercat98-Feb-10 13:17 
GeneralRe: Flyweight objects and collections Pin
David Kemp7-Apr-11 5:45
David Kemp7-Apr-11 5:45 
GeneralRe: Flyweight objects and collections Pin
supercat920-Apr-11 6:06
supercat920-Apr-11 6:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.