Object Oriented Programming

Learning goals

  • Software design approaches and patterns, to identify reusable solutions to commonly occurring problems
  • Apply an appropriate software development approach according to the relevant paradigm (for example object-oriented, event-driven or procedural)

Object Oriented Programming (OOP) is a coding style focused around objects which encapsulate all the data (information) and code (behaviour) in an application. The majority of computer programs written today use OOP as part of their programming style, normally the main part and almost all mainstream languages support object orientation.

This article considers OOP specifically in the context of C#, but the principles apply to any language.

Classes

You are hopefully already familiar with the concept of a class. This is the unit in which you write C# code, normally putting one class in a file. In OOP, you can consider a class to be a blueprint to create objects. The class has two key types of content:

  • Data – information that’s stored in the class
  • Behaviour – methods that you can call on the class

We’ll use as an example a Zoo Management System application – a simple app that helps zookeepers keep tabs on their animals. Here are some classes you might find in the Zoo app:

  • Lion – represents a lion. You might want to store data about the lion’s age, and you might want to implement some program behaviour to deal with the time when the lion is fed.
  • Keeper – represents a zookeeper. A zookeeper probably has a list of animals she’s responsible for.
  • FeedingScheduler – a class that is responsible for managing the animals’ feeding schedule. This one is less recognisable as a “thing” in the real world, but it’s still very much a type of thing in your program.
  • Config – stores configuration information about your program. This is a class storing a useful conceptual object that we will explore later.

Go to the Zoo Management System repo and follow the instructions laid out in the README.

Open up your local copy of the project in your IDE and take a look at the classes described above – for each one, make sure you can follow its responsibilities, both in terms of data and behaviour.

Instances

An instance of a class is an object that follows the blueprint of the class it belongs to. So, the class Lion represents lions generally; an instance of that class represents a single specific lion. A class can have many objects that belong to it. You create a new instance of a class using the new keyword:

var myLion = new Lion();

As mentioned, these instances are referred to as “objects” – hence the term Object Oriented Programming.

Once you have an object, then you call methods on the object itself:

if (myLion.IsHungry()) {
    myLion.Feed();
}

Static vs nonstatic

By default, data (stored in fields) and behaviour (stored in methods) are associated with the instance. The Rabbit class defines lastGroomed, and each individual Rabbit instance has a value for this field.

You can alternatively associate data and behaviour with the class itself. To do this, use the static keyword. Now this field or method is called on the class itself, not an instance of the class.

Take a look at the FeedingScheduler class. We’ve added a static field and a static property to the class – to access the instance field, you call FeedingScheduler.Instance. In contrast, to call the nonstatic method AssignFeedingJobs() you’d invoke it on a specific object, as in myScheduler.AssignFeedingJobs(etc, etc).

When should you use a static field or method? On the whole, not very often. OOP works best if you create instances of classes because then you unlock the power of inheritance, polymorphism, etc. which we’ll come onto below. It also makes testing your code easier. But occasionally static is useful – here are some of the main examples:

  • Where you have unchanging data which can be shared by all instances of a class. Perhaps the zoo has a policy on lion feeding times that applies to all lions; this could go as a static field on the Lion class because it’s shared.
  • The Singleton pattern. This is where there is strictly only one instance of a particular class allowed; the FeedingScheduler is an example of how this pattern is used. The single instance of the class is stored in a private static field, and anyone who wants “the” FeedingScheduler can get it via a static property. (You might well ask, why not just make all the fields and methods on the FeedingScheduler static, and not bother with instances at all? That’s because of the above point about how the power of OOP will eventually benefit you most if you use instances).
  • The Main method. This must be static by definition. Again it’s best practice to keep this short, though, and start using instances as soon as possible.

As a rule, aim to make everything nonstatic, and make a conscious decision to make things static where appropriate – not the other way around.

Incidentally, you can also make classes static. That just means you can’t create instances of them at all, i.e. all fields and methods on them must be static too. The Config class in the Zoo Management System is an example of a static class – it just holds some fixed configuration values, so it doesn’t make sense to create an instance of it.

Inheritance

Inheritance is where a lot of the power of OOP comes from. If a child class “inherits” from a parent class, the child class gets all the data and behaviour definitions from its parent, and can then add additional behaviour on top.

Look at the Models\Animals folder: there’s a hierarchy of classes. Lion inherits from Animal and hence inherits the dateOfBirth field, Age method, and feeding behaviour. This can be most naturally phrased as “A Lion is an Animal”. Rabbit is implemented similarly, but unlike Lion it contains some rabbit-specific behaviour – rabbits can be groomed.

Inheritance also allows behaviour to be modified, not just added. Take a look at the Rabbit’s Feed method. If someone calls the Feed method on a rabbit, this method is invoked instead of the one on Animal. Key things to note:

  • The Animal’s Feed method is marked virtual. That says subclasses (child classes) are allowed to change its behaviour.
  • The Rabbit’s Feed method is marked override. That says this is replacing the base class (parent)’s implementation.
  • The Rabbit’s Feed method calls base.Feed(). That’s entirely optional, but invokes the behaviour in the base class – i.e. the rabbit makes a munching noise, and then does the normal stuff that happens when you feed any animal.

Note that the rabbit’s implementation of Feed will be invoked regardless of the data type of the variable you’re calling it on. So consider the following example:

Rabbit rabbit = new Rabbit();
Animal animal = rabbit;

rabbit.Feed();
animal.Feed();

animal = new Lion();
animal.Feed();

Both the first two feedings will call the rabbit version because the object (instance) in question is a rabbit – even though animal.Feed() doesn’t appear to know that. However, the final feeding will call the general animal version of the method, because Lion doesn’t override the Feed method.

Interfaces

An interface is a promise by the class to implement certain methods. The interface doesn’t contain any actual behaviour; each class that implements the interface defines its own behaviour. Interfaces only contain method names and their parameters. Interfaces in C# can also mandate properties.

Look at the ICanBeGroomed interface definition – it specifies a single Groom method. (The “I” is a convention showing that it’s an interface; it’s not required, but is widely considered good practice.) Note that we don’t say public, even though the method will always be public because all interface methods are public by default.

You cannot create an instance of ICanBeGroomed – it’s not a class. Notice that:

  • Zebra and Rabbit implement this interface
  • Keeper.GroomAnimal accepts anything of type ICanBeGroomed

If it wasn’t for the ICanBeGroomed interface, it would be impossible to define a safe Keeper.GroomAnimal method.

  • If Keeper.GroomAnimal took a Rabbit, you couldn’t pass in a Zebra, because a Zebra is not a Rabbit.
  • If Keeper.GroomAnimal took an Animal (like FeedAnimal does), you could pass in anything but then you can’t call Groom on that animal. This applies even if the animal is, in fact, a zebra – the C# compiler cannot know that you’ve passed in a zebra on this particular occasion because the variable is of type Animal and hence might be any animal.

Note that there’s no direct link between animals and things-that-can-be-groomed. The keeper can now groom anything implementing that interface – it doesn’t have to be an Animal! This is perfectly reasonable – if all the keeper does is call the Groom method, it really doesn’t care whether it’s an animal or not. The only thing that matters is that the Groom method does actually exist, and that’s precisely what the interface is for.

Polymorphism

Polymorphism means the ability to provide instances of different classes to a single method. Take a look at the Keeper class. The method FeedAnimal takes a single parameter with type Animal and the method GroomAnimal takes a single parameter with type ICanBeGroomed. Thanks to polymorphism, we can provide any class inheriting from Animal to FeedAnimal and any class implementing ICanBeGroomed to GroomAnimal.

In particular, we could pass any of the following to FeedAnimal:

  • A Rabbit, because it inherits from Animal
  • A Lion, because it inherits from Animal
  • An animal we’ve never heard of, but that someone else has defined and marked as inheriting from Animal(this might be relevant if you were writing an application library, where you don’t know what the user will do but want to give them flexibility when writing their own programs)
  • An Animal itself (i.e. an instance of the base class). Except actually right now you can’t create one of those because that class is marked abstract, which means you’re not allowed to create instances of it and it’s only there for use as a base class to inherit other classes from

Polymorphism is pretty powerful. As an experiment, try adding a GuineaFowl to the zoo, you can decide for yourself if a GuineaFowl can be groomed

Choosing between interfaces and inheritance

It’s worth pointing out that our use of an interface here leads to a slightly unfortunate side-effect, which is that the Rabbit and Zebra classes have a lot of duplicated code. They don’t just share a Groom method; they have completely identical implementations of that method. Here’s another way of implementing this behaviour to avoid this:

  • Create a new subclass of Animal called AnimalThatCanBeGroomed
  • Change Zebra and Rabbit to inherit from AnimalThatCanBeGroomed, not from Animal
  • Get rid of the interface, and replace all other references to it with AnimalThatCanBeGroomed

Since their common code lives in AnimalThatCanBeGroomed, this would make the individual animals (Rabbit and Zebra) much shorter.

However, there’s a potential disadvantage lurking too: you can only inherit from a single parent. Suppose we now add in some scheduling for keepers sweeping muck out of the larger enclosures. That applies to Lion and Zebra, the larger animals, but not Rabbit (no doubt we’ll get a separate hutch-cleaning regime in place soon enough). Now what do we do? We can repeat the above steps to create an ICanHaveMuckSweptOut interface or an AnimalThatCanHaveMuckSweptOut class. But if we do the latter, we can’t also have AnimalThatCanBeGroomed because Zebra can’t inherit from both at once. At this stage, it starts looking like we’re better off with the interface approach. We’ll have to find another way to minimise code duplication. What options can you come up with?

As another exercise, take a look at the Program.Main method. This currently creates two schedulers, one for feeding and one for grooming, and then calls them both with very similar calls. The methods (AssignFeedingJobs and AssignGroomingJobs) currently have different names, but we could rename them both to just AssignJobs and then try to implement some common code that takes a list of schedulers and calls AssignJobs on each in turn. Should we do this by creating an IScheduler interface, or by creating a common Scheduler base class? Think about the pros and cons.

Composition

Composition is an OOP pattern that is an alternative to inheritance as a way of sharing common characteristics and behaviour. We’ll look at how it could be used as a different approach to the issue of grooming and sweeping out muck.

  • Inheritance expresses an “is a” relationship, as in “a Zebra is an AnimalThatCanBeGroomed, which is an Animal
  • Composition expresses a “has a” relationship, as in “an Animal has a Groomability characteristic”

In the latter case, Groomability would be an interface that has at least two implementations, GroomableFeature and UngroomableFeature:

public interface Groomability 
{
    boolean IsGroomable();
    void Groom();
}

public class GroomableFeature : Groomability 
{
    public boolean IsGroomable() 
    {
        return true;
    }

    public void Groom() 
    {
        lastGroomed = DateTime.now();
    }
}

public class UngroomableFeature : Groomability 
{
    public boolean IsGroomable() 
    {
        return false;
    }

    public void Groom() 
    {
        throw new Exception("I told you I can't be groomed");
    }
}

So then the classes might be composed in the following way.

public abstract class Animal 
{
    private Groomability groomability;
    private Muckiness muckiness;

    protected Animal(Groomability groomability, Muckiness muckiness) 
    {
        this.groomability = groomability;
        this.muckiness = muckiness;
    }
}

public class Zebra : Animal 
{
    public Zebra() : base(new GroomableFeature(), new NeedsMuckSweeping())
    {
    }
}

public class Rabbit : Animal 
{
    public Rabbit() : base(new GroomableFeature(), new NonMucky())
    {
    }
}

public class Lion : Animal 
{
    public Lion() :base(new UngroomableFeature(), new NeedsMuckSweeping())
    {
    }
}

As you can see, composition enables much more code reuse than inheritance when the commonality between classes doesn’t fit a simple single-ancestor inheritance tree. Indeed, when you are designing an OOP solution, there is a general principle of preferring composition over inheritance – but both are valuable tools.

Object oriented design patterns

Design patterns are common ways of solving problems in software development. Each pattern is a structure that can be followed in your own code, that you implement for your specific situation. Following established design patterns is advantageous because:

  • in general they have evolved as best practice, and so avoid the need for you to solve every problem anew – although “best practice” changes over time so some established patterns fall out of favour; and
  • other developers will probably be familiar with the patterns so it will be easier for them to understand and maintain your code.

Although design patterns aren’t specifically tied to object oriented programming, most of the common design patterns that have arisen fit the OOP paradigm. The following are some common OOP design patterns.

  • Singleton: This pattern ensures that there is only ever one instance of a class. This could be important if your application needs a single shared configuration object or an object manages access to an external resource. This would be implemented by:

    • Having a static property in the class that can hold the singleton
    • Making the class constructor private so that new objects of that class cannot be created
    • Having a static public method that is used to access the singleton; if the shared object doesn’t exist yet then it is created
  • Factory: This is a pattern for creating objects that share an interface, without the caller needing to know about the various classes that implement that interface. For example, a factory pattern for creating objects that have the interface Product could look like:

    • Two classes that implement Product are Pen and Book
    • The interface ProductFactory has the method CreateProduct(), which creates and returns Product objects
    • The class PenFactory implements ProductFactory, and its CreateProduct() method creates and returns a new Pen
    • The class BookFactory implements ProductFactory, and its CreateProduct() method creates and returns a new Book
    • If code elsewhere in the application is given a ProductFactory for creating products, it doesn’t need to know what the products are or be told when new products are created
  • Model-View-Controller: You’ll be familiar with the MVC pattern from the Bootcamp Bookish exercise; it’s a very common way to structure user interfaces.

    • Model objects contains the data that is to be displayed (e.g., an entity that has been fetched from a database)
    • The View is a representation of the Model to the user (such as a table or graphical representation)
    • The Controller processes commands from the user and tells the Model objects to change appropriately
  • Adapter: This pattern would be suitable for the Bootcamp SupportBank exercise, in which you had to process data from files in different formats (CSV, JSON and XML). An adapter is a class that allows incompatible interfaces to interact. In the case of SupportBank, you might decide that:

    • TransactionReader is an interface that has the method LoadTransactions(), which returns an array of Transaction objects
    • There are three implementations of TransactionReader, each of which knows how to parse its particular file type, convert values where necessary and produce Transaction objects
    • Note that you might use a Factory pattern to create the TransactionReaders, so it’s easy to add support for new file types in future

Strengths of OOP

The following are strengths of the object oriented programmming paradigm.

Abstraction refers to the fact that object oriented code hides information that you don’t need to know, so if you’re using an object all you know about it is its publicly visible characteristics. Your code doesn’t know about how the object’s data and methods are implemented. This is especially clear when dealing with interfaces – code that interacts with an interface knows only the contract that the interface publishes, and that interface might be implemented by lots of different classes that have completely different structures and behaviour.

Encapsulation refers to the fact that the data and behaviour of an object are tied together into a single entity. If you think about programming without encapsulation, if you have a simple data structure with a set of fields in it and then want to run a function on it, you need to make sure that you find the function that correctly handles that specific data structure. With encapsulation, you just call the object’s own method.

Polymorphism refers to the fact that an object can be interacted with as if it were an instance of an ancestor class, while its behaviour will come from its actual class. When a new descendent class is defined with its own implementation of ancestor class methods, code that interacts with the base class will automatically call the new implementations without having to be updated.

Further reading

The Microsoft Virtual Academy video course A Guide to Object-Oriented Practices has some useful content that is worth working through. In general, the “lecture” sections are the best part, and the “demo” sections tend to use examples that are a bit complicated to follow – but even those parts are worth a look as you may find them helpful, and the core concepts being discussed are useful. The most relevant topics at this stage are:

  • “Encapsulation” covers public, private, protected, etc. – if you’re not comfortable with these concepts it’s worth revising them now.
  • “Inheritance” deals with the bulk of the material in this module, in a further layer of detail.
  • “Interfaces” covers the section on interfaces above.
  • “Abstract Classes” expands on the abstract class concept that was mentioned only briefly above.

The end of each topic includes a “lab”, a short exercise you can complete yourself. There’s a video explaining the solution for each, so you can check your work. If you go this route, it’s worth watching the “Introduction” topic first and making sure you’re set up with a similar environment to what they expect, and have any prerequisites installed.

If you prefer learning through reading rather than video, Learning C# 3.0 covers this material as follows:

  • Chapters 6 and 7 cover the basic building blocks
  • Chapter 11 covers “Inheritance and Polymorphism”, the bulk of the material in this topic
  • Chapter 13 covers “Interfaces”, which includes the remainder of the material here

There’s also plenty of relevant material in Head First C#:

  • Chapter 5 covers “Encapsulation”, an opportunity to revise public, private, protected, etc.
  • Chapter 6 covers “Inheritance”, the bulk of the material in this topic
  • Chapter 7 covers “Interfaces and Abstract Classes”, which covers the remainder of the topic