DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 6.14 Enable/Disable Complex Tracing Code

Problem

You have an object that contains complex tracing/debugging code. In fact, there is so much tracing/debugging code that to turn it all on would create an extremely large amount of output. You want to be able to generate objects at runtime that contain all of the tracing/debugging code, only a specific portion of this tracing/debugging code, or that contain no tracing/debugging code. The amount of tracing code generated could depend on the state of the application or the environment where it is running. The tracing code needs to be generated during object creation.

Solution

Use the TraceFactory class, which implements the Simple Factory design pattern to allow creation of an object that either generates tracing information or does not:

#define TRACE
#define TRACE_INSTANTIATION
#define TRACE_BEHAVIOR

using System.Diagnostics;

public class TraceFactory
{
    public TraceFactory( ) {}

    public Foo CreateObj( )
    {
        Foo obj = null;

        #if (TRACE)
            #if (TRACE_INSTANTIATION)
                obj = new BarTraceInst( );
            #elif (TRACE_BEHAVIOR)
                obj = new BarTraceBehavior( );
            #else
                obj = new Bar( );
            #endif
        #else
            obj = new Bar( );
        #endif

        return (obj);
    }
}

The class hierarchy for the Bar, BarTraceInst, and BarTraceBehavior classes is shown next. The BarTraceInst class would contain only the constructor tracing code, the BarTraceBehavior class contains only tracing code within specific methods, and the Bar class contains no tracing code:

public abstract class Foo
{
    public virtual void SomeBehavior( )
    {
        //...
    }
}

public class Bar : Foo
{
    public Bar( ) {}

    public override void SomeBehavior( )
    {
        base.SomeBehavior( );
    }
}

public class BarTraceInst : Foo
{
    public BarTraceInst( ) 
    {
        Trace.WriteLine("BarTraceInst object instantiated");
    }

    public override void SomeBehavior( )
    {
        base.SomeBehavior( );
    }
}

public class BarTraceBehavior : Foo
{
    public BarTraceBehavior( ) {}

    public override void SomeBehavior( )
    {
        Trace.WriteLine("SomeBehavior called");
        base.SomeBehavior( );
    }
}

Discussion

The factory design pattern is designed to abstract away the creation of objects within a system. This pattern allows code to create objects of a particular type by using an intermediate object called a factory. In its simplest form, a factory pattern consists of some client code that uses a factory object to create and return a specific type of object. The factory pattern allows changes to be made in the way objects are created, independent of the client code. This design prevents code changes to the way an object is constructed from permeating throughout the client code.

Consider that you could have a class that contained numerous lines of tracing code. If you ran this code to obtain the trace output, you would be inundated with reams of information. This setup is hard to manage and even harder to read to pinpoint problems in your code. One solution to this problem is to use a factory to create an object based on the type of tracing code you wish to output.

To do this, create an abstract base class called Foo that contains all of the base behavior. The Foo class is subclassed to create the Bar, BarTraceInst, and BarTraceBehavior classes. The Bar class contains no tracing code, the BarTraceInst class only contains tracing code in its constructor (and potentially in its destructor), and the BarTraceBehavior class only contains tracing code in specific methods. (The class hierarchy provided in the Solution section is much simpler than classes that you would create; this allows you to focus more on the design pattern and less on the class hierarchy from which the factory creates classes.)

A TraceFactory class is created that will act as our factory to create objects inheriting from the abstract Foo class. The TraceFactory class contains a single public method called CreateObj. This method attempts to instantiate an object that inherits from Foo based on the preprocessor symbols defined in your application. If the following line of code exists:

#define TRACE_BEHAVIOR

the BarTraceBehavior class is created. If this line exists:

#define TRACE_INSTANTIATION

the BarTraceInst class is created. If neither of these exists, the Bar class is created. Once the correct class is created, it is returned to the caller. The caller never needs to know which exact object is instantiated, only that it is of type Foo. This allows us to add even more classes to handle varying types and amounts of tracing code.

To instantiate a TraceFactory class, use the following code:

TraceFactory factory = new TraceFactory( );

Using this factory object, we can create a new object of type Foo:

Foo obj = factory.CreateObj( );
Console.WriteLine(obj.ToString( ));
obj.SomeBehavior( );

Now we can use the Foo object without regard to the trace output that it will produce. To create and use a different Foo object, all we have to do is define a different preprocessor symbol that controls which subclass of Foo is created.

See Also

See the "C# Preprocessor Directives" and "ConditionalAttribute Class" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section