Object-oriented programming, or OOP, is a programming paradigm centred around the concept of objects. This article will aim to provide a basic understanding of objects, classes, OOP principles and various other concepts within the paradigm. Code examples will be mediated in the Ruby programming language, which is an example of an object-oriented language. In Ruby, any value is an object, even data types like String and Integer, which are often represented as primitive types in other object oriented languages.
Objects and classes
As mentioned previously, any value in Ruby is an object. Objects in Ruby contain two primary properties: states and methods. States are the data values which are stored inside an object. Methods are the functions which are associated with an object. Objects are built based on classes, which can be viewed as the ‘blueprint’ for an object. Classes contain the definition of what an object of that class will consist of and what procedures can be performed on the object.
The following example shows the object ‘obj’ being initialised and then the class method being invoked on it. The class method can be used on any object in Ruby to show which class an object is made from. We can see that the object ‘obj’ is an instance of the class String.
Classes will often contain a constructor, which is a class method that will be invoked whenever an object of that class is created. Constructors are used to initialise variables whenever an object of the class is created. These variables define the state of the object. Constructors can be passed arguments, which will initialise the variables with relevant values. For example, an object could have a constructor that takes an argument name and assigns the value it is passed to the variable @name when an instance of that object is created. Variables defined in this way are known as instance variables.
Methods define the behaviour of an object. They can be viewed as actions that an object is able to perform. Methods in Ruby are very similar to methods in other languages. Methods can have a level of access control which varies between public, private and protected. If a method is defined within a class, then it will be public by default, assuming it is not defined under a private or protected keyword. A public method can be called from anywhere else in the code via invoking that method on an object of the class.
A private method is a method defined under the private keyword. These methods are for internal usage and cannot be called directly from outside the class, however they can be called from within the class’s public methods. An example of where this behaviour may be useful is in a large codebase where a programmer changes the name of a public method. That change will produce errors at every point in the program where that method is invoked on an object, which will lead to a tedious tidy up. Instead if this method was private and only called within the class’s public methods, the only changes required would be within its definition and wherever it is called in the class.
A common example to illustrate some of these concepts is a Car class.
The example above shows the definition of a Car class with a constructor which will set the variables to whatever values are passed in as parameters. The class also contains some public and private methods. An object of class Car, audi, is then created and the print_informations method invoked on it. When run, this returns the following:
As mentioned previously, private methods cannot be called from outside the class. When attempting to invoke the private information method on the audi object, the result is a NoMethodError.
Four of the most important OOP principles are known as the ‘four pillars’. These are abstraction, encapsulation, inheritance and polymorphism. In this section I will give a brief and basic introduction to each of these concepts.
Abstraction can be viewed as a method of handling complexity within a program by hiding unnecessary details and showing only relevant data, which increases efficiency and reduces complexity. Objects are a perfect example of this. The methods that are invokable on the object and its relevant parameters are available, but the actual implementation is abstracted within the object’s definition.
Encapsulation is the process of bundling related data and methods together into one overall unit. This process, also known as information hiding, conceals segments of functionality from the rest of the code base, so that it cannot be altered unintentionally or viewed from the outside. Encapsulation can be seen as the boundaries within your code. An example of encapsulation in practice are classes.
Inheritance allows characteristics of one class to be inherited into another. In an inheritance scenario, there is a ‘subclass’ which will inherit characteristics from the ‘superclass’. This concept has many applications and is key to providing reusability within programs. For example, if a subclass of the Car class was created, it would then have access to characteristics such as the print_information method.
When run, this returns the following:
Polymorphism is composed of two words, poly and morph, which mean many and forms respectively. In an OOP context, this can be viewed as the ability to execute the same method using different objects. In Ruby, one way of achieving this is via inheritance. If two subclasses of the previously discussed Car class were created with slightly differing definitions of the start_engine method, such as ‘Toyota engine has started’ and ‘Audi engine has started’, creating an object for each sub class and invoking the start_engine method would be an example of polymorphism, as the method would return different results. This type of polymorphism is known as subtyping, or subtype polymorphism.
When run, this returns the following:
There are many advantages to the object-oriented approach to programming. In this section I will discuss only a few of the reasons why this paradigm is so effective for creating well designed and effective programs.
In general, object-oriented programs have a high level of readability compared to other approaches. The use of objects makes reading the program’s source code easier at a glance, and it is easy to find relevant code for any object via its class file. This also makes modification and maintenance of the program easier.
OOP allows for the easy reuse of existing code, which helps in eliminating a lot of redundant or repeated code. In the situation where a programmer is defining a new class and realises that there is an existing class with some of the functionality required, they can easily allow the new class to inherit that functionality from the existing class. This drastically increases reusability and avoids repetitive code.
Encapsulation provides modularity. As objects are self contained the troubleshooting process is easier. Encapsulation also increases overall security, as it prevents data from being unintentionally altered or viewed from the outside. Polymorphism increases flexibility, allowing the reuse of methods which saves time.
Object-oriented programming is an excellent paradigm for producing readable and effective code that can be easily understood and maintained. The use of objects and classes allows for complex concepts to be represented as simple structures that maintain a high level of readability, as well as providing a multitude of other advantages such as reusability and easiness of debugging. These, among many other benefits, are provided via following the various OOP methodologies such as the four pillars.