Home
Index Chapter 3
Chapter 5
A Twisted Look at Object Oriented Programming in C#
By Jeff Louie
11/19/2002
I must admit that my first exposure to object oriented programming (OOP) was
frustrating and difficult. As a hobbyist I have struggled through Z80 assembly
and EPROM burners, BASIC, Turbo Pascal, Java, C++ COM and now C#. The move to
event driven programming and then to object oriented programming presented major
conceptual hurdles to my function driven sequential programming mindset. The “aha”
moment when OOP made sense was most gratifying, but did not come quickly or
easily. It has been a few years since I “got” the OOP mindset and I feel
comfortable enough now to try to help fellow travelers with this journey. If OOP
comes easily to you, feel free to skip this tutorial. If you are having problems
getting your mind around objects and inheritance I hope this tutorial can help
you. This tutorial does not represent a conventional teaching method. It assumes
a passing knowledge of the C# language and familiarity with the Visual Studio
.NET IDE. This is a work in progress and may require correction or
revisions.
Comments are actively requested (email: [email protected]).
Useful Texts
I highly recommend the following books. Much of my understanding of OOP has
been gleamed from these “classic” texts and then reinforced from coding
database projects in Java, C++ and C#. At all times I willfully try to avoid
plagiarizing these authors, but my understanding of OOP is so closely tied to
these texts that I must cite them as sources of knowledge right from the
start!
Object-Oriented Analysis and Design with Applications Grady
Booch, Second Edition, Addison-Wesley, 1994, 589pp.
Design Patterns Elements of Reusable Object-Oriented Software
Gamma Helm, Johnson and Vlissides, Addison-Wesley, 1994, 395pp.
Object-Oriented Software Construction Second Edition Bertrand
Meyer, Prentice Hall, 1997, 1254pp.
Of course, some of this material is a descendent of my writing from our now
out of print book:
Visual Café for Java Explorer Database Development Edition
Brogden Louie and Tittle, Coriolis, 1998, 595pp.
Chapter 4 "Static Methods, Factories & Constructors"
Well, I’ve tried as long as possible to avoid the the "nuts and
bolts" of object oriented programming. It’s sort of like going in to the
dentist for a
root canal. You know it needs to be done, but it is going to be painful and you
want to put it off. The good news is that once you have the root canal the pain goes away! So
just dive in. In this chapter you will learn about static methods,
factory methods and constructors. You will be introduced to the
creational patterns "Class Factory" and "Singleton".
What Is a Static Field or Method?
Let’s change the question. When is a field or method not part of an object?
Answer: when it is part of the class! Remember, an object is an instance of a
class and each object exists in a separate space in memory. It is possible to access
class fields and class methods without creating an instance of a class using the
"static" key word. Declaring a field or method with the static key
word, tells the compiler that the field or method is associated with the class
itself, not with instances of the class. In a sense, static or "class"
fields and methods are global variables and methods that you can touch using the
class name. If you think of a class as a blueprint used to create objects, then
you can think of static fields and methods are being part of the blueprint
itself. There is only one copy of the static fields and methods in memory, shared by all instances of the class.
Static fields are useful when you want to store state related to all
instances of a class. A counter is a good example of a static field. The classic
use of a static counter is to generate a unique ID or serial number for each
instance of a class.
Static methods are useful when you have behavior that is global to the class
and not specific to an instance of a class.
In contrast, instance methods are useful when the method needs to know about the state
of an object. Since data and behavior are intertwined in an object, instance
methods have access to the instance fields and can exhibit behavior that is specific to the
state of an object.
A Static Counter
Here is the classic example of a static counter that is zero based. It
contains a static field "uniqueID" and a thread safe static method
"GetUniqueID" that increments the unique ID:
/// <summary>
/// Summary description for TestStatic.
/// </summary>
class TestStatic
{
// static stuff
private static int uniqueID= 0;
private static int GetUniqueID()
{
lock(typeof(TestStatic))
{
return uniqueID++; // returns zero at start
}
}
// member stuff
private int identity;
public TestStatic()
{
this.identity= TestStatic.GetUniqueID();
}
public int Identity
{
get
{
return identity;
}
}
}
public class Test
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
TestStatic ts1= new TestStatic();
TestStatic ts2= new TestStatic();
Console.WriteLine(ts1.Identity.ToString());
Console.WriteLine(ts2.Identity.ToString());
Console.ReadLine();
}
}
If you compile and run this code the output is: 0 and 1. The static
field "uniqueID" is global to the application and stores the value of
the next unique ID. Each call to the constructor returns the unique ID and then increments the counter using the "postfix" operator ++.
Notice how you use the class name to touch a static field or method:
ClassName.fieldName;
ClassName.MethodName();
Note: In Java you can touch a static field or method using the
class name or a reference variable to an instance of a class. Not so in C#.
Managing Concurrency Conflicts
The curious coder will note the call to "lock" which causes callers
of the static method "GetUniqueID" to queue up to this method. (Lock
is basically a shortcut to "Monitor.Enter" and "Monitor.Exit".)
Locking inside the method insures that the method is thread safe. The problem is
that the increment operator (++) is not an atomic operation, but performs a read
and then a write. If you don’t force callers to queue up to the increment
operation it is possible for two callers to "almost simultaneously"
enter the method. Both callers could read the uniqueID value before either
caller can write the incremented value. In this case, both callers will receive
the same ID. Not very unique! Be careful. If your locking code is poorly written, it
is possible for two callers to queue up in a "catch-22" conflict where
neither call can proceed, an example of "deadlock." The topic of
locking, deadlock, and concurrency is an advanced topic not covered by this
tutorial.
Let’s Get Constructed
When you create an instance of an object using the key word "new", you call
a class constructor. In fact, if you don’t explicitly declare a class
constructors,
the compiler creates a hidden no argument constructor for you. Here is an
example of explicitly declaring a no-arg do nothing constructor:
class Toaster
{
public Toaster() {} // this is a do nothing constructor
}
The compiler will create this constructor, if and only if,
you do not declare any constructors for the class. The syntax for
a public constructor is:
public NameOfClass(parameterList)
{
... stuff here
}
Let’s Get Initialized
Constructors are often
used to initialize the state of an object. Alternatively, you can initialize the
instance fields when they are declared. Finally, you can break out the initialization
code into a separate "Init" method. The following code demonstrates all three idioms
for initializing an object:
/// <summary>
/// Summary description for Idioms
./// </summary>
class Idioms
{
private Hashtable idiom1= new Hashtable();
private Hashtable idiom2;
private Hashtable idiom3;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Idioms c= new Idioms();}
public Idioms()
{
Init();
idiom2= new Hashtable();
}
private void Init()
{
idiom3= new Hashtable();
}
}
Assigning an instance variable a value when you declare the variable is
an example of defensive programming. It minimizes the chance of forgetting to
initialize a variable in the constructor or Init method.
Creating Multiple Constructors
A common programming task is to create
multiple constructors that differ only in their parameter list. The C# language
supports this concept by allowing you to "overload" a method name. As
long as the parameter list is sufficiently unique, you can create multiple
methods or constructors with the same name.
Note: Be careful not to confuse overloading with overriding.
Overriding a virtual method is quite different than overloading a method or
constructor. Overriding is a subject of a future tutorial. I promise.
It’s time to resurrect the Toaster class. Here is a new version of the
Toaster class with two new instance fields that contain information about the
color of the toaster and the model name of the toaster:
class Toaster
{
public const string DEFAULT_NAME= "Generic";
public enum ColorType {Black, Red, Yellow, White};
private static ColorType DEFAULT_COLOR= ColorType.Black;
private ColorType color= DEFAULT_COLOR;
private string modelName= DEFAULT_NAME;
}
Note the use of an enum "ColorType" to limit the domain of valid toaster
colors. Here again is the default no-args constructor:
public Toaster(){} // black toaster with default name
The no-arg constructor simply leaves the default field values unaltered. You can now create a constructor that takes two parameters,
the color type and the model name. Note that the constructor does validity
checking to insure that the state of the object remains valid.
public Toaster(ColorType color, string modelName)
{
this.color= color;
if (modelName != null)
{
this.modelName= modelName;
}
}
You can now create a constructor that only takes one parameter, the model
name:
public Toaster(string modelName)
{
if (modelName != null)
{
this.modelName= modelName
}
}
Now this looks like redundant code, just begging to be refactored. Happily, C# allows
you to chain constructor calls, eliminating the redundant null checking code. You can
chain the two constructors like this:
public Toaster(string modelName) : this(DEFAULT_COLOR, modelName) {}
The syntax is:
public ClassName(someParameters) : this(someParameters) {}
Pretty cool! By using C#’s built in support for constructor overloading
and constructor chaining you can write a series of constructors. Here is the final
version of the Toaster class using multiple overloaded constructors:
class Toaster
{
public const string DEFAULT_NAME= "Generic";
public enum ColorType {Black, Red, Yellow, White};
private static ColorType DEFAULT_COLOR= ColorType.Black;
private ColorType color= DEFAULT_COLOR;
private string modelName= DEFAULT_NAME;
public Toaster(){} // black toaster with default name
public Toaster(ColorType color, string modelName)
{
this.color= color;
if (modelName != null)
{
this.modelName= modelName;
}
}
public Toaster(ColorType color) : this(color,DEFAULT_NAME){}
public Toaster(string modelName) : this(DEFAULT_COLOR,modelName) {}
public string Color
{
get
{
return Enum.Format(typeof(ColorType), color,"G");
}
}
public string ModelName
{
get
{
return modelName; // danger, return ModelName --> stack overflow!
}
}
}
What Is a Destructor?
C++ programmers are familiar with the concept of a destructor. I only briefly
mention
this topic here in self defense. In C++, a destructor is a method that is called when an object
goes out of scope, is deleted, or the application closes. In C++, a destructor
is often used to release valuable system resources. This works in C++ since
memory reuse in C++ is deterministic. When an object in C++ goes out of scope, the destructor
is called and resources can be released immediately.
Things are quite different in C#. First,
memory reuse in C# is based on garbage collection. In a nutshell, when application memory
becomes limited, the garbage collector executes and attempts to reclaim memory by
reclaiming objects that are not "reachable". As a result, in C#, you
cannot depend on a destructor
to reclaim system resources in a timely manner.
Second, although C# supports
the syntax of destructors,
destructors in C# simply map to finalize. According to the IDE documentation:
~ MyClass()
{
// Cleanup statements.
}
… is converted by the compiler to:
protected override void Finalize()
{
try
{
// Cleanup statements.
}
finally
{
base.Finalize();
}
}
If your object uses valuable external resources, you may want your class to inherit from the IDisposable interface and implement the
Dispose method, calling GC.SuppressFinalize. Alternatively, you want to rely on
C#’s support for try, catch, finally to release external resources. For
instance, you might want to open a connection in try and close any open
connections in finally.
The concept of garbage collection and reclaiming external resources is
definitely beyond
the scope of this tutorial.
Using Static Factory Methods Instead of Multiple Constructors
You might wonder why I have chosen to combine the topics of static methods and
constructors into a single chapter. The answer is "static
factory methods." Instead of writing multiple public constructors, you can
write multiple static factory methods and private constructors that return objects. First, here is an example of a static factory method. The
method simply constructs an object with default values and then returns a
reference to the object.
public static Toaster GetInstance()
{
return new Toaster(ColorType.Black, DEFAULT_NAME);
}
In a sense, this static method is analogous to the no-arg constructor.
public Toaster() {}
Here is the version of the Toaster class that uses static
methods and a single private constructor to return toaster objects:
/// <summary>
/// Summary description for Toaster
/// </summary>
class Toaster
{
// static factory methods
public static Toaster GetInstance()
{
return new Toaster(ColorType.Black, DEFAULT_NAME);
}
public static Toaster GetInstance(string modelName)
{
return new Toaster(ColorType.Black, modelName);
}
public static Toaster GetInstance(ColorType color)
{
return new Toaster(color, DEFAULT_NAME);
}
public static Toaster GetInstance(ColorType color, string modelName)
{
return new Toaster(color, modelName);
}
public const string DEFAULT_NAME= "Generic";
public enum ColorType {Black, Red, Yellow, White}; // black is the enum default value!
private static ColorType DEFAULT_COLOR= ColorType.Black;
private ColorType color= DEFAULT_COLOR;
private string modelName= DEFAULT_NAME;
// the single private constructor
private Toaster(ColorType color, string modelName)
{
this.color= color; // ColorType cannot be null --> compile time error or defaults to ColorType.Black!
if (modelName != null)
{
this.modelName= modelName;
}
}
// the getters
public string Color
{
get
{
return Enum.Format(typeof(ColorType), color,"G");
}
}
public string ModelName
{
get
{
return modelName; // danger, return ModelName --> stack overflow!
}
}
}
Declaring the only constructor private, prevents any outside caller from directly
instantiating the class. The only path to a Toaster object is through a static
factory method. So, you can use multiple overloaded public constructors or multiple static factory
methods and private constructors to create toaster objects. If you are
interested in learning more about using static factory methods instead of
multiple constructers check out Effective Java Programming Language Guide
by Joshua Bloch, Addison-Wessley, 2001, 252 pp.
Note: The behavior of enum is quite complicated. You cannot set
an enum to null and if you fail to explicitly initialize an enum variable, it
defaults to the first member of the enumeration. For example:
public static ColorType c; // c –> ColorType.Black
Creational Patterns — Class Factory and Singleton
I am going to finish off this chapter by introducing two common design
patterns: the "Class Factory" and "Singleton" patterns. The
class factory is useful when you want to return concrete objects that share a
base class, at runtime. The singleton pattern is useful when you only want to
allow the creation of one instance of an object in a application. These patterns
are both considered "Creational" patterns since they abstract the
creation of objects.
Using a Static Method to Return Concrete Classes — The Class Factory.
The concept of using static methods to return objects is a useful one. In the
previous code, you learned how to replace multiple constructors with multiple static
factory methods. Another
useful design pattern is the "Class Factory." A class factory can be used to return concrete implementations of a common base type.
In Chapter 2, you
learned about polymorphism using the Drawable abstract class. Concrete
implementations of the Drawable class such as square or circle provide a
concrete implementation of the abstract "DrawYourself" method. Let’s
resurrect our Drawable class.
abstract class Drawable
{
public abstract String DrawYourself();
}
class Circle : Drawable
{
public override String DrawYourself()
{
return "Circle";
}
}
class Square : Drawable
{
public override String DrawYourself()
{
return "Square";
}
}
In this example of the class factory pattern,
you
pass a parameter to a static factory method that then returns the appropriate concrete
implementation of the Drawable abstract class. To insure that the method is
passed valid parameters at compile time, you can define a type safe enum "DrawableType":
public enum DrawableType {CIRCLE,SQUARE};
Here is our class factory:
/// <summary>
/// Summary description for class Factory.
/// </summary>
class Factory
{
public enum DrawableType {CIRCLE,SQUARE};
public static Drawable GetInstance(DrawableEnum e)
{
if (e == DrawableType.CIRCLE)
{
return new Circle();
}
else if (e == DrawableType.SQUARE)
{
return new Square();
}
else
{
throw new IndexOutOfRangeException(); // should never get here
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
Drawable d1= Factory.GetInstance(Factory.DrawableType.CIRCLE);
Console.WriteLine(d1.DrawYourself());
Drawable d2= Factory.GetInstance(Factory.DrawableType.SQUARE);
Console.WriteLine(d2.DrawYourself());Console.ReadLine();
}
}
Note that d1 and d1 are reference variables of the Type Drawable, yet the code outputs: Circle, Square.
Polymorphism at work! The class factory design pattern
allows your application to create concrete implementations of a base class
or interface dynamically at runtime in response to user or system input.
The Singleton Pattern
The "Singleton" pattern is a special version of the class factory that only returns a single instance
of a class. The singleton pattern is useful when there should only be one
instance of an object. As an example, there may be many soldiers that derive from
person, but there should only be one reigning King of England that derives from
person! Here is a sample that uses a static factory method to insure that only
one instance is created. The factory method "GetInstance" returns
a reference to the single instance of the class. Note that you must declare
any constructors private, so that the constructors are not visible outside
of the class. This insures that. there will be
one and only one instance of MyClass.
/// <summary>
/// Summary description for MyClass.
/// </summary>
class MyClass
{
private static MyClass theOnlyOne= new MyClass(); // create one and only one instance of the class
public static MyClass GetInstance()
{
return theOnlyOne;
}
public readonly string description= "The One and Only.";
private MyClass(){}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
MyClass mc= MyClass.GetInstance();
Console.WriteLine(mc.description);
Console.ReadLine();
}
}
One of the advantages of the factory method is that you can modify the singleton
behavior of the class without affecting the caller of the class. If you decide
that your application should now support Kings present and past, then you can
modify MyClass to return a new instance for each call to GetInstance. Here is
the modified multi-instance version of MyClass:
/// <summary>
/// Summary description for MyClass
/// </summary>
class MyClass
{
public static MyClass GetInstance()
{
return new MyClass();
}
public readonly string description= "OneOfMany";
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
MyClass mc= MyClass.GetInstance();
Console.WriteLine(mc.description);
Console.ReadLine();
}
}
That’s enough pain! I suggest the you jog around the block, clear your head and re-read this
chapter later. In the next chapter, you will learn the top ten "gumption
traps" for C++ and Java programmers.
All Rights Reserved Jeff Louie 2002
|