Back To Basics

In this section, we will discuss the key concepts important to Object Oriented Programming (OOP). An object-oriented system has the following characteristics

  • Inheritance
  • Polymorphism
  • Abstraction
  • Encapsulation
  • Decoupling

Some systems (and some languages) don’t fully support all the above constructs and still refer to themselves as “object-oriented”. This is a matter of some debate, but it is my opinion that a language or system must implement each of the above concepts in some way in order to be considered object oriented.

Inheritance

Inheritance is the ability to create a class based on an existing class. In this model, the existing class is known as the “parent class”, or “base class” and the new class is known as the “child class” or “derived class”. By default, a child class will inherit all properties and methods of its parent class.

In C#, we inherit a class from a parent class by appending a colon and the name of the parent class to the child class definition, as in the following example:

public void ChildClass : ParentClass {} 

Marking a method as virtual in a parent class allows it to be overridden in a child class. This means that the method can be replaced in the child class with new code.

public virtual Int32 DoMath (Int32 num1, Int32 num2) 
{ 
  return num1 + num2; 
} 

We can then use the “override” keyword in the child class’s method to replace the code in the parent’s class method, as shown below

public override Int32 DoMath (Int32 num1, Int32 num2) 
{ 
  return num1 - num2; 
} 

It is possible to have multiple child classes that inherit from the same parent class. In some languages (but not in C#), it is also possible for a child class to inherit from multiple parent classes.

As a general rule, if I find myself writing a lot of conditional logic in a class’s methods – depending on how the object is created or the environment, the class runs one of several blocks of code – I will consider refactoring that class into a number of subclasses. Each class will inherit from a common parent class but the conditional logic is eliminated because each subclass contains only the code relevant to how it is created.

Interface Inheritance

Inheriting from an interface is similar to inheriting from a class, except the parent class does not contain any implementation, so each subclass contains all method code it needs to run. This is useful if a set of subclasses need to share the same public members but do not have any code in common. Interfaces also have the advantage that a single class can inherit from more than one interface.

Polymorphism

Earlier, we said that objects communicate by passing messages via their public interface.

Polymorphism is the ability for different objects to respond to the same messages in a different, but appropriate, way. In OOP, this is done using inheritance. Because a child class inherits properties, fields and methods from a parent class, we can have several different child classes sharing the same public members. Each of these classes would therefore accept the same message types. However, each base class may override some of the behavior of the base class and may do so in a way appropriate to itself.

For example, we may create a Vehicle class that has a method Drive() that accepts no parameters.

We can derive two child classes - Car and Airplane - from Vehicle and they will each inherit the Drive method. But driving a car is not like driving an airplane, so each of these methods would be overridden and the overridden child methods would be very different from one another. Calling the Car’s Drive method would cause the axels to turn and rotate the four wheels beneath the car. Calling the Airplane’s Drive method would explode jet fuel and propel the airplane through the sky at a high velocity.

This is an example of two objects (Car and Airplane) that accept the same message (Drive) and respond differently but appropriately.

The C# code for this set of classes would look similar to the following

public class Vehicle 
{ 
  public virtual void Drive() 
  { 
    Console.WriteLine(“Driving…”); 
  } 
} 

public class Automobile: Vehicle 
{ 
  public override void Drive() 
  { 
    Console.WriteLine(“Turn axel and wheels and tires…”); 
  } 
} 

public class Airplane: Vehicle 
{ 
  public override void Drive() 
  { 
    Console.WriteLine(“Explode jet fuel and propel through the air…”); 
  } 
} 

The picture below shows the hierarchy of classes that inherit from a single parent class.

Abstraction

Abstraction is the process of simplifying an object or system by focusing only on those parts of the system relevant to the current problem. In OOP, abstraction is typically implemented using encapsulation.

Encapsulation

Encapsulation hides implementation details of an object from that outside world. Again, this goes back to our goal of decreasing complexity. We don’t need to understand all the workings of an object in order to use it. Returning to our Automobile example, in order to start a car, I need only know to put the key into the ignition and turn it for a short time. A lot happens inside the car (gasoline is injected, battery is engaged, sparks fly, gas explodes) but I don’t need to the details of any of that in order to start the car. In fact, it’s easier for me to focus on starting the car if I ignore the other things going on within the automobile.

Decoupling

Finally, decoupling is a key point of object oriented programming that simplifies a system. In a decoupled system, the dependencies between objects are minimized. Encapsulation helps with this because external objects cannot change the internal attributes of an object if they cannot access it.

However, the responsibility for decoupling ultimately rests with the developer. Use messages to communicate between objects and maintain as little state as possible. Expose only those attributes needed by others and avoid coding side effects in your object’s methods.

In my experience, decoupling is the OOP concept that is ignored the most by people trying to build object oriented systems.

Summary

As we stated in part 1 of this series, the goal of Object Oriented Programming is to manage complexity. We do this by modeling our application as many people see their real world systems – as a set of loosely coupled objects that interact with one another. This helps us to split a complex problem into a number of more manageable objects. It also allows us to simplify the management of those objects by encapsulating complexity, maximizing flexibility through inheritance, and keeping the objects independent of one another.


Thanks to Chris Woodruff, who contributed to this article.