JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

Chapter 7 "To Inherit or Contain? That is the Question." 

In Chapter 2, you visited the design conundrum of using inheritance or containment. In this chapter you will use both. First you will use implementation inheritance to create a custom, type safe, null safe collection. You will then use containment to wrap the custom collection and "adapt" the read/write collection interface to a read only interface.

Here again is the discussion from Chapter 2: One difficult design decision is to decide if a class should inherit from a parent class or hold a reference to a new object. Inheritance represents an IS_A relationship from a generalization to a specialization. Containment represents a HAS_A relationship between the whole and a part. So a car IS_A motorized vehicle, but HAS_A radio. The two relationships can be expressed in code thusly:

	class Radio 
	{
		...
	}
	class Vehicle
	{
		...
	}
	class Car : Vehicle 
	{
		Radio r= new Radio();
	}

An instance of Car contains an instance of a Radio so that the lifetimes of the radio and car are intertwined. This is "containment by ownership" making Car an example of a composite class. 

Containment can also be accomplished using references to external objects. "Containment by reference" can be expressed in code thusly:

	class Radio 
	{
		...
	}
	class Vehicle
	{
		...
	}
	class Car : Vehicle 
	{
		private Radio r;
		public Car(Radio r)  {
			...
			this.r= r;
		}
	}

You can then create an instance of Car like this:

	class Class1
	{
		[STAThread]
		static void Main(string[] args)
		{
			Radio r= new Radio();
			Car c= new Car(r);
		}
	}

Here is a graphical view of containment by reference.

  What's this?

Let's Get Your Inheritance

One common real world task is to create a type safe, null safe collection. For instance, you might want to create a collection that can only store elements that are non null references of type Drawable. This allows you to iterate over the collection casting each element and then calling a public method on every element without fear of a NullReferenceException or a InvalidCastException.

Here again is the Drawable type implemented as an interface. By convention, this should have been named IDrawable.

	// an interface version of Drawable
	interface Drawable 
	{
		void DrawYourself();
	}
	class Circle : Drawable 
	{
		public void DrawYourself() 
		{
			System.Console.WriteLine("Circle");
		}
	}
	class Square : Drawable 
	{
		public void DrawYourself() 
		{
			System.Console.WriteLine("Square");
		}
	}

You can now create a type safe, null safe collection by extending the abstract base class System.Collections.CollectionBase. CollectionBase was designed for use as a base class of a custom type safe collection. Extending CollectionBase automatically exposes all of the public methods of CollectionBase such as Count and GetEnumerator()

Here is a graphical view of a DrawableCollection that inherits from CollectionBase.

 

Note: You could also create a type safe collection by creating a class that contains an ArrayList and provides pass through getter and setter methods that take and return only references of type Drawable. This would require that you provide a do nothing pass through method for every public type safe method in ArrayList that you want to expose in the containing class. The graphical view of a class that contains an ArrayList by ownership would look like this.

 

Note: v2.0 of the .NET CLR (Command Language Runtime) supports generics which can be used to create type safe collections as in List<Drawable> dl= new List<Drawable>();

Here is an example of using inheritance to create a type safe, null safe collection of Drawable elements. The set indexer calls the type and null safe Insert method.

	/// <summary>
	/// DrawableCollection
	/// A type safe, null safe collection of Drawable objects
	/// Demonstrates the use of Inheritance
	/// A DrawableCollection IS_A Collection
	/// Extends CollectionBase to create a specialization
	/// </summary>
	class DrawableCollection : System.Collections.CollectionBase
	{
		// Custom implementations of the protected members of IList
		// returns -1 if parameter is null
		public int Add(Drawable value) 
		{
			if (value != null) 
			{
				// throws NotSupportedException
				return List.Add(value); 
			}
			else 
			{
				return -1;
			}
		}
		public void Insert(int index, Drawable value) 
		{
			if (value != null) 
			{
				//throws ArgumentOutOfRangeException
				List.Insert(index, value); 
			}
			// else do nothing
		}
		public void CopyTo(Array array, int start) 
		{
			//throws ArgumentOutOfRangeException
			List.CopyTo(array, start); 
		}
		// provide an indexer
		public Drawable this[int index] 
		{
			get 
			{
				// ArgumentOutOfRangeException
				return (Drawable)List[index];  
			}
			set 
			{
				//throws ArgumentOutOfRangeException
				Insert(index,value); 
			}
		}
	}

The key here is that all of the setter methods (Add, Insert, set) validate for non null and take a reference of type Drawable. Any attempt to pass a null reference will be ignored. Any attempt to pass a reference to an object that does not support the Drawable interface will fail. This guarantees that all elements in the collection are of the type Drawable and are not null. This allows you to iterate over the collection without fear of a NullReferenceException or a InvalidCastException like this:

	foreach(Drawable d in drawableCollection) 
	{
		System.Console.WriteLine(d.ToString());
	} 

Note: Using foreach hides the call to GetEnumerator(). Here is the explicit call using IEnumerator:

	System.Collections.IEnumerator enumerator= dc.GetEnumerator();
	while (enumerator.MoveNext()) 
	{
		System.Console.WriteLine(((Drawable)(enumerator.Current)).ToString());
	}

C# supports the concept of an indexer which supports random access to a collection using the index operator ([]). A custom indexer does not add support for a Length property. Here again is the get and set code that creates an indexer:

	// provide an indexer
	public Drawable this[int index] 
	{
		get 
		{
			// throws ArgumentOutOfRangeException
			return (Drawable)List[index];  
		}
		set 
		{
			//throws ArgumentOutOfRangeException
			Insert(index,value); 
		}
	}

You can then use the indexer like this:

	// create a DrawableCollection
	DrawableCollection dc= new DrawableCollection();
	dc.Add(new Circle());
	// test indexer
	Drawable draw= dc[0];

A Better Class Hierarchy

Although the type safe, null safe collection above works, you could improve the class design by first creating a null safe collection. The following class simply insures that null objects cannot be inserted into the collection. Note that all of the setters are declared protected as this class was designed to be extended, not instantiated.

	// A null safe collection. This class is meant to be
	// extended by a type safe class so that the setter
	// methods are protected.
	class NullSafeCollection : System.Collections.CollectionBase 
	{
		// class is not meant to be instantiated, only inherited
		protected NullSafeCollection(){}
		// Custom implementations of the protected members of IList
		// These methods are for internal use by a type safe subclass
		// returns -1 if parameter is null
		protected int Add(object value) 
		{
			if (value != null) 
			{
				// throws NotSupportedException
				return List.Add(value); 
			}
			else 
			{
				return -1;
			}
		}
		protected void Insert(int index, object value) 
		{
			if (value != null) 
			{
				//throws ArgumentOutOfRangeException
				List.Insert(index, value); 
			}
			// else do nothing
		}
		// provide an indexer
		protected object this[int index] 
		{
			get 
			{
				//throws ArgumentOutOfRangeException
				return List[index];  
			}
			set 
			{
				//throws ArgumentOutOfRangeException
				Insert(index,value); 
			}
		}
		
		// expose single public method, get only CopyTo
		public void CopyTo(Array array, int start) 
		{
			//throws ArgumentOutOfRangeException
			List.CopyTo(array, start); 
		}
	}

You can now extend from this null safe collection, creating a type safe and null safe collection of Drawable elements. The advantage of this design hierarchy is that you can now reuse the NullSafeCollection class to create a different type safe collection class. Here is the final type safe, null safe class:

	class DrawableCollection : NullSafeCollection 
	{
		// Custom implementations of the protected members of IList
		// returns -1 if parameter is null
		public int Add(Drawable value) 
		{
			return base.Add(value);
		}
		public void Insert(int index, Drawable value) 
		{
			base.Insert(index, value);
		}
		// provide an indexer
		public new Drawable this[int index] 
		{
			get 
			{
				//throws ArgumentOutOfRangeException
				return (Drawable)base[index];  
			}
			set 
			{
				//throws ArgumentOutOfRangeException
				base.Insert(index,value); 
			}
		}
	}

Note the key word new which tells the the compiler that you are explicitly shadowing or hiding the indexer in the base class. You cannot override the base indexer since the subclass has a different return type than the base class.

Here is the graphical view of the improved class hierarchy.

 

Wrap It Up Please (Using Containment)

Another common task is to pass a "read only" reference to a caller. (This restriction is available in C++ using the key word const on a pointer in the method parameter list declaration. Using const on a pointer prevents corruption of any data that can be touched with the pointer within the method.) One way to pass a "read only" reference to a collection in C# is to "wrap" a reference in another object. This is an example of using containment by reference. As I view it, the read/write interface of the DrawableCollection is "adapted" to a read only interface. When you adapt an existing interface to a new interface, you are using the Adapter Design Pattern. Wrapping or adapting a class is a common idiom. (For instance, you might want to wrap an unmanaged legacy Win32 dll function in a managed C# class.) 

Here is a read only class WrapCollection that contains a private reference to a DrawableCollection. 

	/// <summary>
	/// WrapCollection
	/// Demonstrates wrapping our DrawableCollection to limit access
	/// Demonstrates the use of Containment
	/// WrapCollection contains, HAS_A, DrawableCollection
	/// Adapts the read write interface to a read only interface
	/// Demonstrates the Adapter Design Pattern
	/// </summary>
	class WrapCollection 
	{
		private DrawableCollection collection;
		// constructor
		public WrapCollection(DrawableCollection collection)
		{
			if (collection != null) 
			{
				// reference to an existing collection
				this.collection= collection; 
			}
			else 
			{
				// new empty collection
				this.collection= new DrawableCollection(); 
			} 
		}
		// provide a get only indexer
		// throws ArgumentOutOfRange if collection is empty
		public Drawable this[int index] 
		{
			get 
			{
				// throws ArgumentOutOfRangeException
				return (Drawable)collection[index];  
			}
		}
		public System.Collections.IEnumerator GetEnumerator() 
		{
				return collection.GetEnumerator();
		}
		public int Count 
		{
			get 
			{
					return collection.Count;
			}
		}
	}

The key here is to declare the reference variable collection as private. Declaring collection private, prevents the public user of the WrapCollection object from accessing any of the setters in the contained DrawableCollection.

private DrawableCollection collection;

Note the design decision to create a new empty DrawableCollection if the caller passes null to the WrapCollection constructor. This design allows the caller to blissfully iterate over the contained collection using IEnumerator or foreach without throwing a runtime exception.

	// constructor
	public WrapCollection(DrawableCollection collection)
	{
		if (collection != null) 
		{
			this.collection= collection;
		}
		else 
		{
			this.collection= new DrawableCollection(); 
		} 
	}

If the user passes null to the WrapCollection constructor, the calls to GetEnumerator() and Count will still be valid, returning an empty enumeration and a count of zero.

Here is the graphical view of the WrapCollection class.

 
Test It!

Go ahead. Compile the DrawableCollection and WrapCollection. Then use the following code to test the type safe, null safe collection. Passing a WrapCollection prevents the caller from modifying the contained collection since the collection is not visible outside of the class.

	class Test 
	{
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] args)
		{
			//
			// TODO: Add code to start application here
			//
			// create a DrawableCollection
			DrawableCollection dc= new DrawableCollection();
			dc.Add(new Circle());
			dc.Add(new Circle());
			dc.Add(null);
			dc.Add(new Square());
			dc.Insert(1,new Square());
			// test indexer
			Drawable draw= dc[0];
			System.Console.WriteLine(draw.ToString());
			// dc[0]= null;  // no action
			// dc[0]= "Hello";  // fails at compile time
			// test Count
			int num= dc.Count;
			System.Console.WriteLine(num.ToString());
			// test CopyTo
			Drawable[] copy= new Drawable[num];
			dc.CopyTo(copy,0);
			foreach(Drawable d in copy) 
			{
				System.Console.WriteLine(d.ToString());
			} 
			// test IEnumerator
			foreach(Drawable d in dc) 
			{
				System.Console.WriteLine(d.ToString());
			} 
			// Create a WrapCollection
			WrapCollection wrap= new WrapCollection(dc);
			// try this instead!
			// WrapCollection wrap= new WrapCollection(null); 
			// test Count
			int count= wrap.Count;
			System.Console.WriteLine(count.ToString());
			// test indexer
			if (count > 0) 
			{
				System.Console.WriteLine(wrap[0].ToString());
			}
			// test IEnumerator
			foreach(Drawable d in wrap) 
			{
				System.Console.WriteLine(d.ToString());
			}
			System.Console.ReadLine();		
		}

Be careful, the code can still throw an ArgumentOutOfRangeException. Well, I hope you have a better feel for inheritance and containment!

Note: I should mention that a viable alternative to wrapping a mutable object in a read only wrapper class is to use immutable objects. An immutable object is an object that cannot be modified. Although this may cause anxiety in some of us, immutable objects have their champions including Joshua Block in "Effective Java", Addison Wesley, 2001. Joshua Block argues that immutable objects are "easier to design, implement and use than mutable classes. They are less prone to error and more secure." Specifically Joshua Block argues that immutable objects are:

bulletSimple
bulletInherently thread safe
bulletCan be shared freely

It is the last point that makes immutable objects safe to use with external methods.

Note: The NET 2.0 CLR supports generics and many of the read write generic collections provide the AsReadOnly() method that returns an object that implements the read only IList interface. The IList object is a read only wrapper that contains the read write collection. 

Addendum

using System;
// One complaint that I have heard is about the extra
// coding required to wrap each concrete null, typesafe collection
// as read only. Here is an attempt at a Generic read only wrapper 
// class WrapNullCollection that takes a NullSafeCollection
namespace JAL
{
	// an interface version of Drawable
	interface IDrawable 
	{
		void DrawYourself();
	}
	class Circle : IDrawable 
	{
		public void DrawYourself() 
		{
			System.Console.WriteLine("Circle");
		}
	}
	class Square : IDrawable 
	{
		public void DrawYourself() 
		{
			System.Console.WriteLine("Square");
		}
	}
	// A public null safe collection.
	class NullSafeCollection : System.Collections.CollectionBase 
	{
		// Custom implementations of the protected members of IList
		// These methods are for internal use by a type safe subclass
		// returns -1 if parameter is null
		protected int Add(object value) 
		{
			if (value != null) 
			{
				// throws NotSupportedException
				return List.Add(value); 
			}
			else 
			{
				return -1;
			}
		}
		// for internal use only by subclass
		protected void Insert(int index, object value) 
		{
			if (value != null) 
			{
				//throws ArgumentOutOfRangeException
				List.Insert(index, value); 
			}
			// else do nothing
		}
		// provide an indexer
		public object this[int index] 
		{
			get 
			{
				//throws ArgumentOutOfRangeException
				return List[index];  
			}
			set 
			{
				//throws ArgumentOutOfRangeException
				Insert(index,value); 
			}
		}
		
		// expose single public method, get only CopyTo
		public void CopyTo(Array array, int start) 
		{
			//throws ArgumentOutOfRangeException
			List.CopyTo(array, start); 
		}
	}
	// a type and null safe collection of IDrawable objects
	class DrawableCollection : NullSafeCollection 
	{
		// Custom implementations of the protected members of IList
		// returns -1 if parameter is null
		public int Add(IDrawable value) 
		{
			return base.Add(value);
		}
		public void Insert(int index, IDrawable value) 
		{
			base.Insert(index, value);
		}
		// provide an indexer
		public new IDrawable this[int index] 
		{
			get 
			{
				//throws ArgumentOutOfRangeException
				return (IDrawable)base[index];  
			}
			set 
			{
				//throws ArgumentOutOfRangeException
				base.Insert(index,value); 
			}
		}
	}
	/// <summary>
	/// WrapNullCollection
	/// Demonstrates wrapping a concrete NullSafeCollection to limit access
	/// Demonstrates the use of GENERIC Containment
	/// WrapCollection contains, HAS_A, NullSafeCollection
	/// Adapts the read write interface to a read only interface
	/// Demonstrates the Adapter Design Pattern
	/// </summary>
	class WrapNullCollection 
	{
		private NullSafeCollection collection= null;
		// constructor
		public WrapNullCollection(NullSafeCollection collection)
		{
			if (collection != null) 
			{
				// reference to an existing collection
				this.collection= collection; 
			}
			else 
			{
				this.collection= new NullSafeCollection();
			} 
		}
		// provide a get only indexer
		// returns generic object
		// throws ArgumentOutOfRange if collection is empty
		public object this[int index] 
		{
			get 
			{
				// throws ArgumentOutOfRangeException
				return collection[index];  
			}
		}
		public System.Collections.IEnumerator GetEnumerator() 
		{
			return collection.GetEnumerator();
		}
		public int Count 
		{
			get 
			{
				return collection.Count;
			}
		}
		public static void Main(string[] args) 
		{
			DrawableCollection dc= new DrawableCollection();
			dc.Add(new Circle());
			dc.Add(new Circle());
			dc.Add(null);
			dc.Add(new Square());
			dc.Insert(1,new Square());
			// test indexer
			IDrawable draw= dc[0];
			System.Console.WriteLine(draw.ToString());
			// dc[0]= null;  // no action
			// dc[0]= "Hello";  // fails at compile time
			// test Count
			int num= dc.Count;
			System.Console.WriteLine(num.ToString());
			// test CopyTo
			IDrawable[] copy= new IDrawable[num];
			dc.CopyTo(copy,0);
			foreach(IDrawable d in copy) 
			{
				System.Console.WriteLine(d.ToString());
			} 
			// test IEnumerator
			foreach(IDrawable d in dc) 
			{
				System.Console.WriteLine(d.ToString());
			} 
			// Create a WrapCollection
			WrapNullCollection wrapDrawable= new WrapNullCollection(dc);
			// try this instead!
			// WrapNullCollection wrap= new WrapNullCollection(null); 
			int count= wrapDrawable.Count;
			// test indexer
			for (int i=0;i<count;i++)
			{
				System.Console.WriteLine(wrapDrawable[i].ToString());
			}
			// test IEnumerator
			foreach(IDrawable d in wrapDrawable) 
			{
				System.Console.WriteLine(d.ToString());
			}
			System.Console.ReadLine();	
		}
	}
}

 

All Rights Reserved Jeff Louie 2003, 2004

<% ShowFooter() %>

 

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1