Verity Marsterson is a Software Engineering apprentice at Sky, is based in Leeds and one of the founding members of the WhiteHat North group.
In this article, I'm not assuming that you have any real prior knowledge of coding or development, but instead invite you to dive into the abstract and logical world of programming. I will therefore be giving a brief summary of what objects and classes are, some of the key design principles of object oriented programming before finishing on some of the disadvantages and benefits of object oriented programming.
What is Object Oriented Programming (OOP)?
Object Oriented Programming (or OOP) in technical terms according to Margaret Rouse is
"A computer programming model that organizes software design around data, or objects, rather than functions and logic"
Simple right? But what does this actually mean?
For the purpose of explaining, let's imagine we are building a fantasy football style app where a user can pick football players from across the Premier League to add to your own 'ultimate football team' and then gain points each depending on if the players in your team score in real life or not which might look a bit like this:
Now, with our app we of course want to have a part of our programme which hold values such as the name of the user, the number of football players you can have in your team, and the name of the user's team. These are all different variables.
In addition to this, we want our app to be able to do something with those values so that the programme runs and the user can interact with the game dynamically. For example, this could be a function that allows the user to click on a player icon, which in turn adds that player to their personalised team. Or, it could be a function that describes how, when a footballer scores or gets an injury in real life, this updates the virtual player in our fantasy football team's track history. These blocks of code which act as commands to allow parts of the app to complete certain task are called functions.
While we need both variables and functions for a working app, we also need a way to combine these two elements in a logical and efficient manner. Object oriented programming is one of the ways in which we can do this.
Object oriented programming is different from other means of organising our code such as functional or procedural programming as it allows us to structure our code by breaking them into classes and objects.
What do we mean by objects and classes?
- Classes are the blueprints that describe how to build the thing you want in your app. They tell you the shape of a prospectus object, and the properties and functionality that each object can have when you choose you want to build one.
- Objects are the core features of an app, which are built from the blueprints outlined in the class and which contain functions and properties (variables). These objects exist and therefore have a state and the capacity to react with other objects and provide functionality within your app.
To apply this to a concrete example, we'll take our fantasy football application. For the purpose of this blog, I'll use Kotlin code snippets, but it's important to recognise the general principles of OOP apply to all OOP languages.
Now, we know that all footballers have a name, belong to a club, start the season having scored no goals, but have the potential to score in any game.
We therefore want to create a Footballer class, or blueprint that tells our programme that when we want to build a footballer, all of these characteristics are possible. This can be laid out in the following format, which shows how a Footballer is a class, and has the properties that allow it to have a name, team, a total number of goals, as well as the potential to run a function (task) that enables them to score a goal:
In Kotlin, this can be written as:
Now that we have our football blueprint, we want to use this, to create our specific, individual footballer, otherwise known as our object. In this case, I want to create Mohamed Salah, with an object that looks like this:
If we didn't have the functionality to create classes, and therefore had to create individual objects for each footballer, we might end up writing out a long block of code for each footballer that looked a little bit like this:
However, thanks to the fact that we have already created a Footballer class, which outlines the structure for all footballer objects, we can relay the same information as the example above, but instead create each object using our Footballer class to make our code far more succinct:
However, the real benefit of using classes and objects isn't necessarily showcased when creating a single footballer. After all, the code above for a stand alone object is pretty similar to the code we wrote to create our initial footballer class so doesn't necessarily look that much more efficient.
However, in our app, we don't want to just create one footballer, but instead an entire team of footballers for each user. In fact, we want to have the option to be able to create objects for every single footballer in the premier league.
Thankfully, by building our code using OOP, we have the blueprint from our class to create multiple objects with ease, and manipulate each player object based on the different actions carried out by each unique player. In the below example, we can see how this works when building a Salah object and a Kane object wherein after only Salah scores.
Beyond this, we would also want to create classes for the users themselves, a class for the user's team (specifying the rules on how many players you can have, and storing variables such as the total cost of the team etc) on top of the original footballer class. Each of these would be used to create numerous objects with different functions and variables.
As you can imagine, having lots of classes and objects can therefore get quite complicated. To avoid getting lost in code we have an additional set of principles which developers look to follow when developing in an object oriented manner which I will outline in the following section:
When developing classes and interfaces we can hide variables and functions from the outside and only expose high-level mechanisms for using them. Really, we're only interested in the input and output of code blocks and the mechanisms of how the app behaves can be hidden at a higher level.
For example, with our footballers, we can have an overarching footballer class but we don't need to know everything that is happening within that class. We don't need to know the logic behind how we convert a real life goal into a virtual goal, we just know that the individual footballer objects (e.g. Salah) has the potential to score.
This makes it easier to update our app and reduces the impact of change.
When we create classes with similar properties and functions, we can reduce the code we write by creating a base class from which they can inherit.
With our fantasy football app, we may decide that we're not happy with simply restricting our app to football, and instead also want to create an option that allows users to score points from backing their favourite players in a Korfball league too. Without inheritance, we would create separate classes, with very repetitive code:
As we can see, just like with our footballers, our korfball players will have a name, a club they are associated to and the ability to score goals. Beyond this however, they have their own specific traits, like being able to score a free pass rather than kick, and penalty throw rather than kick. Rather than writing out the same properties and functions for each class, we can create new classes which inherit (aka take all of the properties) of our base class
SportsPerson, and add the new properties and functions on top of this inherited layer so that our structure is much more concise as so:
Literally translated as 'many forms', polymorphism means we can render objects with the same properties and/or functions differently depending on the type being passed into the code block/ interface.
For example, in our codebase for our sports app, we might have an interface which runs a block of code which works out how much a goal is worth. When we pass a footballer into the 'score()' function, we would get an output of "1 point". However, running a rugby player through a 'score()' function would instead return "5 points". Our interface remains the same, but we can pass different objects through that morph to appear differently.
This is good as it allows us to get rid of long switch or if statements that we might have used in the past with functional programming.
To avoid conflicts within our code, for example where we could accidentally redeclare variables or functions in different sections of our code, we can wrap our variables and functions within classes or objects and choose which parts to make public and which parts to keep private.
Private variables or functions cannot be accessed by other classes or functions directly, and instead, if we want to update them externally they will either have to be made public or use a function within the class or object which is public to do the work for us.
For example, our 'totalGoals' variable, is used in both our Kane and Salah objects. However, even though both sets of variables have the same name ('totalGoals'), we don't want Kane's goals to be updated every time Salah scores or vice versa. Therefore, we would want our 'totalGoals' object to be private to each footballer object, and ensure that the only way this can be updated is through the 'scoreGoal()' function specific to that player.
Of course, every approach has its advantages and disadvantages, and so when developing applications its important to be aware of some of the limitations of object orientated programming which include:
- A strong emphasis on the data side of our app means that sometimes we don't focus enough on computation or algorithms
- If not carefully structured, the code can become over complicated and bloated
- You sometimes need to execute additional programming which can use more CPU than other methods, making your app extremely large and inefficient. Some programmers therefore sometimes opt to write the most time-critical parts such as assemblers in an non-OOP language (like C) and use OOP languages for UI such as windows or images
However, we cannot deny the benefits of using object oriented programming including:
- Previously, in procedural and functional programming, we used to have a list of functions that were performed in order. However, this lead to lots of interdependency and the creation of 'spaghetti code'. With OOP, classes are often reusable and therefore can reduce the amount of code we have to write which allows us to easily maintain our codebase.
- When working with a team, multiple developers can work on different classes simultaneously allowing for parallel development
- As we are grouping data into logical units (objects), the way in which we pass data into functions is simplified