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.
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( ); } }
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.