Design Patterns


We have seen the basic principles for object construction and how to vary the use of the object creation process to encompass the use of operators, constructor overloading, and chaining and static constructors to initialize a class constructor. Let's focus on a more abstract problem area now that we have the necessary understanding of object creation principles.

A design pattern is a reusable design that can template an object-oriented approach for solving a problem. These patterns can be applied to create an abstraction layer for design, and these design patterns are normally learned concurrently with OO development techniques to evolve good verses bad development principles.

Design patterns have been proposed by numerous individuals but they were originated by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, the so called Gang of Four. The Gang of Four's patterns are so widespread in computing that they are often known by the abbreviation ‘Gof Patterns’. The Gang of Four created a series of reusable OO design patterns, which were adopted by the development community and used within most object-oriented projects. For more information refer to the Addision & Wesley book, Design Patterns: Elements of Reusable Object-Oriented Software, ISBN: 0- 201-63361-2. Written by the Gang of Four, you'll often see it referred to as the GoF book. As we delve into several design patterns, which we can use in our own code, it will become apparent to all well versed in either the .NET Framework class libraries or Java class libraries that these design patterns are being used throughout the library code. The patterns in this section are described as creational patterns as they represent designs based on the creation of objects, as opposed to structural or behavior patterns, which represent acts of design based on class models or communication between objects.

Singleton

The Singleton is a design pattern that is used throughout most class libraries and OO models. The Singleton design pattern ensures that only a single instance of a class is available, and this is defined by a method that can be controlled by the use of static helper methods on the class. Singleton design patterns are prolific throughout the design of operating systems ensuring that when we request the use of a hardware device we use the single instance of the hardware device object rather than a newly created one.

The following example has a class called HeadTeacher that cannot be created directly by using the new keyword. This is made possible by specifying a private constructor, so only a nested class or a static method can create an instance of the HeadTeacher class. We have seen the nested class approach but we use a static method when defining a Singleton.

The static field headTeacher stores the HeadTeacher object instance. We control the creation of this through the static GetInstance() method, which will check to see if this is null. If it is null then this method will create and return a new instance of HeadTeacher, if not then it will return the static field reference. In this way only a single object of HeadTeacher can ever exist at one time, and all clients will have a reference to this instance.

     using System;     public class HeadTeacher     {       private static HeadTeacher headTeacher;       private int grade;       private string name;       private HeadTeacher() {}       public static HeadTeacher GetInstance()       {         if(headTeacher == null)         {           Console.WriteLine("HeadTeacher instance created!");           headTeacher = new HeadTeacher();           return headTeacher;         }         else         {           Console.WriteLine("HeadTeacher instance already exists!");           return headTeacher;         }       }       public int Grade       {         get { return grade; }         set { grade = value; }       }       public string Name       {         get { return name; }         set { name = value; }       }     } 

A Main() method is used to check whether a single instance of the object is created. A single HeadTeacher object is created by invoking the GetInstance() method. We can set the Grade property to 10 and it will be identical for both object references because they reference the same object. The file containing all the source code is called singleton.cs:

     public class mainHeadTeacher     {       static void Main()       {         HeadTeacher ht1 = HeadTeacher.GetInstance();         HeadTeacher ht2 = HeadTeacher.GetInstance();         ht1.Grade = 10;         Console.WriteLine("ht1 Grade:{0}", ht1.Grade);         Console.WriteLine("ht2 Grade:{0}", ht2.Grade);       }     } 

The output from this will be.

     C:\ Class Design\Ch 05> Singleton     HeadTeacher instance created!     HeadTeacher instance already exists!     ht1 Grade:10     ht2 Grade:10 

By amending this, we can apply a second design pattern called the Double-Checked Locking Pattern to ensure that the Singleton class is threadsafe. To make anything threadsafe, we have to ensure that only one thread can enter the method at any one time. Consider the situation where a thread enters the GetInstance() method and another thread enters before the object is created, but after checking that the local variable containing the HeadTeacher object is null. Before control returns to the original thread, another thread has created the object. Assuming the calling code is multithreaded then h1 and h2 will reference different HeadTeacher objects!

We can use the native synchronization of .NET by using the following namespace.

     using System.Runtime.CompilerServices; 

We can now use the MethodImplOptions.Synchronized enumeration member on the MethodImpl attribute to ensure that this method is only ever called by one thread at a time. The new file is called locking_pattern.cs. Locks, Monitors, or Mutexes can also be used instead of this, but such threading topics are beyond the scope of this book.

     [MethodImpl(MethodImplOptions.Synchronized)]     public static HeadTeacher getInstance()     {       ...     } 

The Abstract Factory Pattern

The Abstract Factory pattern, like the Singleton, is used throughout the .NET Framework class libraries. The Factory name should be familiar to Java developers where Factory() methods are present throughout the Java class libraries, and also COM developers may recognize the term from the ClassFactory objects used to create instances of COM classes.

To use the Abstract Factory pattern we don't need to create the class directly through a constructor; instead we simply expose a Factory, which will create the class on our behalf. We don't actually need to stipulate the concrete class; since the Factory knows the class it should return for us this can be also be extended to return a default class. Whenever the family of classes needs extending, the code can simply be changed in the Factory classes (which act as an abstraction) rather than changing any code in the HospitalWorker directly.

The best definition of an abstract factory is actually the definition given in the GoF book:

Note

‘The purpose of an abstract factory is to provide an interface for creating families of related objects without specifying their concrete classes.’

In this example we create two types of hospital workers – doctors and nurses. The nurses and the doctors have certain traits in common, for example they are either qualified or students. To model this we create abstract base classes, Doctor and Nurse, which will contain a basic implementation of the underlying type of HopitalWorkers – the implementation does not matter to us in this case since we must use a separate class to create the doctor or nurse objects. As their properties are the same, however, we first create an abstract MedicalStaff class for the two abstract Doctor and Nurse classes to derive from.

     public abstract class MedicalStaff     {       protected int salary;       protected int grade;       protected string hospital;       public int Pay       {         get { return this.salary; }         set { this.salary = value; }       }       public int Grade       {         get { return this.grade; }         set { this.grade = value; }       public string Hospital       {         get { return this.hospital; }         set { this.hospital = value; }       }     }     public abstract class Doctor : MedicalStaff {}     public abstract class Nurse : MedicalStaff {} 

As there are two different types of nurses and doctors, both either qualified or students, we can derive new classes as a student or qualified representation of a nurse or a doctor. The implementation here is unimportant; the principle is all we need to understand the design pattern:

     public class StudentNurse : Nurse {}     public class QualifiedNurse : Nurse {}     public class QualifiedDoctor : Doctor {}     public class StudentDoctor : Doctor {} 

To continue with the design pattern we must create a base class to create the nurse or doctor, student or qualified. The base class is called HospitalWorker and has two methods that will return either a nurse or a doctor, which should be implemented in the derived classes.

     public abstract class HospitalWorkerFactory     {      public abstract Nurse GetNurse() {}      public abstract Doctor GetDoctor() {}     } 

The two derived classes are StudentFactory and QualifiedFactory respectively, which will return either a student doctor or nurse, or a qualified doctor or nurse depending on the method called.

     public class StudentFactory : HospitalWorkerFactory     {       public Nurse GetNurse()       {         return new StudentNurse();       }       public Doctor GetDoctor()       {         return new StudentDoctor();       }     }     public class QualifiedFactory : HospitalWorkerFactory     {       public Nurse GetNurse()       {         return new QualifiedNurse();       }       public Doctor GetDoctor()       {         return new QualifiedDoctor();       }     } 

The base class HospitalWorkerFactory is known as an Abstract Factory class after the pattern name simply because it is the interface that the creation code uses it is aFactory in the sense that it specifically creates and returns the object to us with a concrete name used. Each type of category, student or qualified, must have its own concrete implementation derived from the abstract factory.

In our creation code we can add another class called HospitalWorker, which will take the appropriate factory as a method parameter and return either a nurse or doctor. The fact that the HospitalWorkerFactory class is either of type StudentFactory or QualifiedFactory, thus enabling it to determine the appropriate class, determines the choice.

     public class HospitalWorker     {       public static Nurse GetNurse(HospitalWorkerFactory hwf)       {         return hwf.GetNurse();       }       public static Doctor GetDoctor(HospitalWorkerFactory hwf)       {         return hwf.GetDoctor();       }     } 

There are other types of Factory methods (the abstract factory method is only one of them), though they all intuitively use another class (a Factory class) to control the creation of a particular object. Here we made all types have the same properties, but in this example, we could have provided completely different properties and methods for the Doctor and Nurse abstract classes. The .NET Framework provides many examples of factories, like the WebRequest class, or the SymmetricAlgorithm class, rather than taking an abstract factory class argument, they normally use a string parameter that represents the literal name of the class for the static Create() method to return the appropriate class instance.

Lazy Initialization

The last two patterns are based on the abstraction of creation of objects, and they avoid the creation code directly. In the case of an Abstract Factory pattern we create the object we need without specifying the name of the concrete class directly, but deferring the creation through a factory method, and in the case of a Singleton we use a class to broker a single instance of the object, which either exists or needs to be created, using a static method. Lazy initialization ensures that the field values will only be set when they are first accessed by a piece of code; after this the same value is returned. Lazy Initialization is classed as a design pattern but can be conceived to be an optimization technique to avoid consumption of resources.

With the Doctors and Nurses in the previous section we could return a field value, which would currently be uninitialized. The lazy initialization model populates field values only when necessary, as they are seldom used by the implementation. For greater performance, do not allow this value to be set at construction in case it is time consuming. Lazy initialization specifies that get property accessors contain all the code necessary to retrieve information from a resource, and only when they are invoked is the underlying field value populated.

In the Lazy class below there are two fields, id and name. The number parameter is an integer value that is passed into the constructor when the object is first created and assigned to the id field. The name field is unassigned at object creation time and will use the id value when the Name get accessor is requested to populate the name field from the file so that it will effectively be initialized only if it is used.

     public class Lazy     {       private int id;       private string name;       public Lazy(int number)       {         this.id = number;       }       public int ID       {         get { return id; }       }       public string Name       {         get         {           if(this.name==null)           {             StreamReader sr;             try             {               sr = new                   StreamReader("C:\\name"+id.ToString("0000")+".txt");               name = sr.ReadToEnd();             }             finally             {               if(sr!=null)                 sr.Close();             }             return this.name;           }         }       }     } 

Copy-On-Write

Copy-on-write is an application of a general design pattern like the proxy pattern. It is relevant to the discussion of object construction since it relates directly to object construction through optimization in much the same way as Lazy Initialization does. Earlier in the chapter we discussed the notion that a deep copy was more expensive than a shallow copy so for the purposes of efficiency we may need to use a shallow copy in places. The problem here is that shallow copying relies on shared references to objects, which are fine for as long as the objects don't need to contain different field values; in practice they always will have to at some stage within an application.

Copy-on-write addresses this issue by determining when an object value is being written to, and if it is, it will take a copy of the referenced object and make the appropriate changes. In this way it is not as expensive as a deep copy since the second object reference may only need to apply read operations throughout the application and not a near identical copy. There are issues such as reference counting, and synchronization associated with copy-on-write, which make its implementation a little tougher in practice.

Copy-on-write can be built into classes and is inherent in some .NET objects. For example, if we look at the following code, we would say that the second variable world is a reference copy of the first hello. The world variable should change the underlying string so that both references when referenced and written to the screen should produce the expression Hello World!

This is not, however, what happens in this case as the string uses a copy-on-write technique of actually creating a by value copy of the string since the overloaded operator checks to see what the reference count on the string is. If the count is above 1 and the object value is being modified then a new copy will be created and a reference to the new copy altered stored in the second variable.

     string hello = "Hello";     string world = hello;     world += " World!";     Console.WriteLine("{0}\{1}", hello, world); 

This optimization can be done in objects using similar techniques of operator overloading. In the case of the string example above, every reference to the string could invoke an implicit conversion operator, which would return a reference to the current string and increment the reference count by one; when we use the += operator this would check to see if the reference count is greater than one, and if so copy the string by value and return a number string with the additional string argument appended, also setting the reference count to one. There is ample debate on the usefulness of the copy-on-write pattern since it requires synchronization and makes the reference counting and additional checks take more resources than an initial by-value copy.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net