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.
|