Object Oriented Programming
- 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
’sFeed
method is markedvirtual
. That says subclasses (child classes) are allowed to change its behaviour. - The
Rabbit
’sFeed
method is markedoverride
. That says this is replacing the base class (parent)’s implementation. - The
Rabbit
’sFeed
method callsbase.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
andRabbit
implement this interfaceKeeper.GroomAnimal
accepts anything of typeICanBeGroomed
If it wasn’t for the ICanBeGroomed
interface, it would be impossible to define a safe Keeper.GroomAnimal
method.
- If
Keeper.GroomAnimal
took aRabbit
, you couldn’t pass in aZebra
, because a Zebra is not a Rabbit. - If
Keeper.GroomAnimal
took anAnimal
(likeFeedAnimal
does), you could pass in anything but then you can’t callGroom
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 typeAnimal
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 fromAnimal
- A
Lion
, because it inherits fromAnimal
- 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 markedabstract
, 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
calledAnimalThatCanBeGroomed
- Change
Zebra
andRabbit
to inherit fromAnimalThatCanBeGroomed
, 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 anAnimalThatCanBeGroomed
, which is anAnimal
” - Composition expresses a “has a” relationship, as in “an
Animal
has aGroomability
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
arePen
andBook
- The interface
ProductFactory
has the methodCreateProduct()
, which creates and returnsProduct
objects - The class
PenFactory
implementsProductFactory
, and itsCreateProduct()
method creates and returns a newPen
- The class
BookFactory
implementsProductFactory
, and itsCreateProduct()
method creates and returns a newBook
- 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
- Two classes that implement
-
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 methodLoadTransactions()
, which returns an array ofTransaction
objects- There are three implementations of
TransactionReader
, each of which knows how to parse its particular file type, convert values where necessary and produceTransaction
objects - Note that you might use a Factory pattern to create the
TransactionReader
s, 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