The conception, birth, and first steps of an application named Charlie

Subscribe: Atom or RSS

The Simplicity of Casting

by Alister Jones (SomeNewKid)

In my earlier entry titled Charlie Takes Inspiration from Cuyahoga, I described how Cuyahoga taught me the value of Reflection, Interfaces, Composition, and O/R Mappers. What I forgot to mention is that it also taught me the value of Casting.

(I must point out that I am using the term Casting when it may in fact be more accurate to say polymorphism or the Liskov substitution principle. However, I must admit that I don’t truly understand the difference between Casting, Polymorphism, and the substitution princple. They all seem closely related, but I don’t quite understand how they differ. So, I’m going to use the term Casting, as it is the simplest term. If I use the term incorrectly, the error is all mine, and not an error by Martijn Boland or his Cuyahoga project.)

To understand Casting, let’s look at an example of kids. In this example, a kid does just two things: eat and sleep. The Kid class may look like this:

public class Kid
{
   public void Eat()
   {
      Print("I am eating.");
   }

   public void Sleep()
   {
      Print("I am eating.");
   }
}

Now, some kids go to school where they learn to read and write. But not all kids go to school, so we cannot put the Read and Write behaviour down in the Kid class. Instead, we create a new class named Student which inherits from Kid:

public class Student : Kid
{
   public void Read()
   {
      Print("I am reading.");
   }

   public void Write()
   {
      Print("I am writing.");
   }
}

Now, some kids are interested in the performing arts, so these kids are enrolled in the performance programme of the school where they learn to sing and dance in addition to learning to read and write:

public class PerformingStudent : Student
{
   public void Sing()
   {
      Print("I am singing.");
   }

   public void Dance()
   {
      Print("I am dancing.");
   }
}

So far, this is about as simple as object-oriented programming can get, and is the sort of thing you see in textbooks.

Now, let’s design a Home class, where the incoming Kid will either eat or sleep, depending on whether it is before or after 7pm. The class may look like this:

public class Home
{
   public void Enter(Kid kid)
   {
      if (time > 1900)
      {
         kid.Sleep();
      }
      else
      {
         kid.Eat();
      }
   }
}

That looks okay. Now, let’s design a School class, where before noon the kids learn to read, and after noon the kids learn to write.

public class School
{
   public void Enter(Student student)
   {
      if (time < 1200)
      {
         student.Read();
      }
      else
      {
         student.Write();
      }
   }
}

That looks okay too. Let’s press on and create our PerformanceSchool class:

public class PerformanceSchool
{
   public void Enter(PerformingStudent performer)
   {
      if (time > 1000 && time < 1200)
      {
         performer.Read();
      }
      else if (time > 1200 && time < 1400)
      {
         performer.Write();
      }
      else if (time > 1400 && time < 1600)
      {
         performer.Sing();
      }
      else
      {
         perfomer.Dance();
      }
   }
}

Do the above classes look okay? We know that any given child can be a Kid, Student, or PerformingStudent. So, we have created a Home, School, and PerformanceSchool that will accept the appropriate type of child, and will work with that child.

This stuff is so simple that you might wonder what I am banging on about. Well, let’s create a few children, but without the thrill of sex:

public class Program
{
   public void Main()
   {
      Student adrian = new Student();
      Kid beth = new Kid();
      PerformingStudent claire = new PerformingStudent();
      Student david = new Student();
   }
}

Once again, this is simple stuff. The difficulty comes when we consider how we are going to shuffle these children around in our application. Let’s create a Bus which will be used to move the children around. Because all childen are a Kid or inherit from Kid, it is very easy to get the various children onto our Bus:

public class Bus
{
   // Here is our private record of the kids on this bus.
   // Let's not worry about sizing this array.
   private Kid[] kids;

   public void Enter(Kid kid)
   {
      kids.Add(kid);
   }
}

So we have our children, and we can get them onto the Bus. When the Bus arrives at School, what happens? How do we get the kids from the Bus to the School? Let’s have a go at implementing a GetAllKids method for the Bus:

public class Bus
{
   // Here is our private record of the kids on this bus.
   // Let's not worry about sizing this array.
   private Kid[] kids = new Kid[];

   public void Enter(Kid kid)
   {
      kids.Add(kid);
   }

   public Kids[] GetAllKids()
   {
      return kids;
   }
}

Now, let’s move the kids off the Bus and into School:

public class Program
{
   public void SendKidsToSchool(Bus bus)
   {
      School school = new School();
      Kid[] kids = bus.GetAllKids();
      foreach(Kid kid in kids)
      {
         school.Enter(kid);
      }
   }
}

This will cause a runtime error. Beth is on the bus, but she is not a Student. We have a mismatch between the Bus (which works with Kids) and the School (which works only with Students). The same thing will occur if we try to get the kids off the bus and into the PerformanceSchool (which works only with PerformingStudents), as Claire is the only PerformingStudent on the bus.

The wrong solution is to update the Bus:

public class Bus
{
   // Here is our private record of the kids on this bus.
   // Let's not worry about sizing this array.
   private Kid[] kids;

   public void Enter(Kid kid)
   {
      kids.Add(kid);
   }

   pubic Kid[] GetAllKids()
   {
      return kids;
   }

   public Student[] GetAllStudents()
   {
      Student[] students = new Student[];
      foreach(Kid kid in kids)
      {
         if (kid is Student)
         {
            students.Add(kid);
         }
      }
      return students;
   }
}

Now that we have a way of getting only the Students on the Bus, we can send them to School:

public class Program
{
   public void SendStudentsToSchool(Bus bus)
   {
      School school = new School();
      Student[] students = bus.GetAllStudents();
      foreach(Student student in students)
      {
         school.Enter(student);
      }
   }
}

We can update the Bus in the same way to expose a GetAllPerformingStudents method, and update our Program with a SendPerformingStudentsToPerformanceSchool method. With the Bus updated this way, our program would work.

If this works, why did I say that this is the wrong solution? Well, we have three types of children (Kid, Student, and PerformingStudent) and three locations (Home, School, PerformingSchool). The Bus must know about the different types of children on the bus. The Program must know about the three types of children, and about the three types of location.

Now our application expands to be more real-world, and we introduce a HighSchoolStudent (which inherits from Student and adds Maths and Gym methods), and a UniversityStudent (which inherits from HighSchoolStudent and adds Thesis and Smoko methods). We also introduce interfaces to represents the sports these people can play, including ISoccerPlayer, IFootballPlayer, and ISoftballPlayer.

The Bus must now become a very complex class, with lots of different Get methods. The Program gets even more complex, as in addition to knowing about all the new types of people, it must also know about the new HighSchool, University, SoccerStadium, FootballField, and SoftballDiamond.

This is getting close to the problem faced in a real-world application. We have lots of business objects, and we must somehow come up with a system of passing them around. And we need to come up with a way to pass them around without the program or the bus having to change every time we add a new business object. How?

The answer is Casting.

The program and the bus deal with the “lowest” level of children, which is Kid. The Bus class lets kids on, and lets kids off. It does not know which kids are Students and which kids are UniversityStudents. In fact, the Bus class does not even know that Students and UniversityStudents exist—all it knows about are Kids.

Because the bus works with Kids, what we need to do is change our School class from this:

public class School
{
   public void Enter(Student student)
   {
      if (time < 1200)
      {
         student.Read();
      }
      else
      {
         student.Write();
      }
   }
}

To this:

public class School
{
   public void Enter(Kid kid)
   {
      if (time < 1200)
      {
         kid.Read();
      }
      else
      {
         kid.Write();
      }
   }
}

If you think ahead, this actually introduces its own problem. Not all kids are students, so when Beth (who is a Kid, but not a Student) gets off the bus and enters the school, this will cause a runtime error.

So, what we must to is cast the incoming kid up from a Kid to a Student. For Beth (who is just a Kid, not a Student), the result will be null. So, we can update our School class like this:

public class School
{
   public void Enter(Kid kid)
   {
      Student student = (Student)kid;
      if (student != null)
      {
         if (time < 1200)
         {
            student.Read();
         }
         else
         {
            student.Write();
         }
      }
   }
}

With the use of casting, we have created a system whereby children can be shuffled around the Program by the Bus, without the Program or the Bus ever having to know about the various types of children. The only classes that need to know about a specific child type (such as PerformingStudent) is a class that uses that specific child type (such as PerformanceScool). Such classes just need to cast the incoming Kid up to the appropriate type.

You will see that the end result is extremely simple. We just pass around the low-level, general types. Yet, in most books and online literature, the parameters of methods will be the “high” specific type, not the “low” general type. We would be forgiven then for missing just how much simplicity and flexibility is obtained if we pass “low” general types around in our application.

As I opened by saying, this was a lesson I learned from Cuyahoga. Had I not learned this lesson, Charlie would be passing “high” specific types around. Now, Charlie is passing “low” general types around and, in so doing, achieves great simplicity and flexibility.

by Alister Jones | Next up: Expert C# Business Objects

0 comments

----