This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Object-Oriented Programming

Object-Oriented Programming (OOP) is a programming paradigm that has become indispensable nowadays. This approach models real-world elements as “objects” that have properties and behaviours, which allows for more intuitive and maintainable programmes to be created. In this article we will look at the basic concepts of OOP and its advantages over other paradigms like procedural programming. Let’s get started!

This paradigm is based on two fundamental concepts:

  • Objects: entities that combine state (data) and behaviour (operations) in a single unit. For example, a “car” object would have properties like colour, number of doors, maximum speed, etc. And behaviours like accelerate, brake, steer, etc.
  • Classes: specifications that define the common structure and behaviour of a group of objects. The “car” class would serve as a template for creating car objects with the same characteristics.

As explained by programmer Alan Kay, one of the creators of OOP:

“The big idea is to design programmes in terms of conceptual objects and concepts from the real world. The interfaces with the real world should, therefore, be constructed in terms of these conceptual objects.”1

That is, OOP conceptually models real-world elements to make programming more intuitive.


Programming paradigms

Before delving into OOP, it is worth understanding that there are different paradigms or approaches to tackle programming. The main ones are:

Procedural programming

Ordered sequence of instructions that the programme must follow step-by-step. The focus is on procedures and functions. For example, C is a language geared towards procedural programming.

Procedural programming is better for:

  • Simple problems or sequential algorithms.
  • Code that won’t need heavy reusing or expanding.
  • Cases where performance and efficiency are critical.

Object-Oriented programming

Model based on objects that contain data and code in cohesive units. The focus is on classes and the interaction between objects. For example, Java and Python are object-oriented languages.

OOP allows modelling real-world elements more directly, better encapsulating data, and reusing code through inheritance between classes.

The main advantages of OOP over procedural programming are:

  • Modularity: objects group related data and operations, encapsulating internal complexity. This allows working with independent modules.
  • Information hiding: Objects can expose a simple interface and hide internal implementation details. This reduces coupling.
  • Reusability: Classes enable code reuse. An abstract class can inherit to multiple subclasses.
  • Extensibility: We can extend the behaviour of parent classes by creating new subclasses.
  • Conceptual mapping: Objects represent real-world entities, which eases the translation of requirements into code.

However, OOP also has disadvantages. According to programmer Paul Graham:

“Object-oriented programming often makes things harder than they need to be.”2

For example, for simple problems OOP can be excessive. And in large projects there is a risk of overusing inheritance and polymorphism, making the code difficult to follow.

Ultimately, OOP is more suitable when:

  • The problem to be modelled has clear, structured entities.
  • We want to reuse encapsulated code in modular classes.
  • We work on systems that need to be easily extended and maintained.

Basic OOP concepts

Now that we know the general ideas behind OOP, let’s look at some key concepts:

Objects

An object is a combination of data (properties) and behaviours (methods). For example, a Car object would have properties like make, model, colour and methods like accelerate, brake, etc.

# Car Class
class Car:

  def __init__(self, make, colour):
    self.make = make
    self.colour = colour

  def accelerate(self):
    print("Accelerating", self.make)

# Create Object
my_car = Car("Toyota", "Red")
  • my_car is now a Car object with properties and methods defined in its class.

Classes

A class defines the common attributes (properties) and methods (functions) for a group of objects. It works like a template for creating similar objects.

By convention, classes are defined with the first letter capitalised. The properties and methods of a class get the self prefix to indicate they belong to that object instance.

class Circle:

  def __init__(self, radius):
    self.radius = radius

  def area(self):
    return 3.1416 * (self.radius ** 2)
  • Circle defines the class with radius property and area() method.

Methods

Methods are functions that define the behaviour of an object. They are declared inside the class and can access the object’s properties via self.

A constructor is a special method (__init__) that runs when creating objects to initialise their properties.

class Person:

  def __init__(self, name, age):
    self.name = name
    self.age = age

  def greet(self):
    print(f"Hello! I'm {self.name}")

john = Person("John", 30) # Runs __init__
john.greet() # "Hello! I'm John"
  • The constructor assigns name and age. The greet() method accesses the name attribute.

Properties

Properties are variables associated with an object that define its characteristics or state. They are declared in the class and accessed through the object reference.

class Rectangle:

  def __init__(self, height, width):
    self.height = height
    self.width = width

r = Rectangle(3, 4)
print(r.height) # 3
r.width = 8
print(r.width) # 8

It is recommended to declare properties as private and access via getter/setter methods to respect encapsulation.

Encapsulation

It consists of hiding the internal implementation details of an object, exposing only a public interface. This is achieved by declaring methods and properties with public or private modifiers.

In Python this is denoted with a leading underscore for private methods/properties:

class BankAccount:

  def __init__(self, balance=0):
    self.balance = balance

  def deposit(self, amount):
    self._balance += amount

  def check_balance(self):
    return self._balance
  • _balance is private, only accessed internally or via check_balance().

Encapsulation makes it easier to change internal parts of a class without affecting its public interface.


Conclusion

Object-Oriented Programming models real-world elements as classes and objects, prioritising modularity, information hiding, and code reuse to create more robust and maintainable programmes.

Although it can be excessive for simple problems, OOP is ideal for medium/large systems that need to expand and evolve in complexity over time.

Concepts like inheritance, polymorphism, abstraction, and interfaces allow fully leveraging the advantages of this paradigm. With a solid understanding of its fundamentals we are ready to apply OOP in any programming language and project.

References


  1. Kay, Alan. The early history of Smalltalk. http://gagne.homedns.org/~tgagne/contrib/EarlyHistoryST.html ↩︎

  2. Graham, Paul. Why Arc Isn’t Especially Object-Oriented. http://www.paulgraham.com/noop.html ↩︎

1 - Classes and objects

In object-oriented programming, classes and objects are the key concepts to understand how we model elements of reality and define their structure and behaviour within software. Let’s look in detail at the anatomy of a class, how to create objects from it to use their properties and methods, and other key details of their relationship.

Anatomy of a class

A class acts as a blueprint or mould to construct similar objects, defining their common characteristics and functionalities. It is similar to the blueprint used to construct houses in the same neighbourhood: they all share certain key attributes.

The typical components of a class are:

Attributes (properties): Variables that characterise the object. For example, for a Person class, attributes like name, age, ID, etc.

class Person:
  id = ""
  name = ""
  age = 0

Methods: Functions that define behaviours. For example, a Person can walk(), talk(), eat(), etc. They access the attributes to implement said functionality.

Constructor: Special __init__() method that executes when instantiating the class and allows initialising attributes.

Destructor: __del__() method that executes when deleting the instance, freeing up resources. Optional in some languages.


Creating objects

From the class we generate objects, which are specific instances with their own defined attributes. Let’s say the House class is the blueprint, and a specific house on a particular street is the object.

In code, we create an object by invoking the class as if it were a method:

# Person class
class Person:
  def __init__(self, n, a):
    self.name = n
    self.age = a

# Specific Person objects
john = Person("John", 30)
mary = Person("Mary", 35)

Each object shares the general structure and behaviour but can store different data.

Using properties and methods

We now have a Person class and a john object of type Person. How do we interact with the object?

  • Properties: It is possible to access the value of an object attribute using the object reference (john) and the attribute name.
john.name  # "John"
john.age   # 30
  • Methods: In the same way as accessing attributes but adding parentheses inside which arguments are passed if it takes any.
# Person class
class Person:
  def __init__(self, n, a):
      self.name = n
      self.age = a

  def eat(self, food):
      print(f"Eating {food}")

# Specific Person object
john = Person("John", 30)
john.eat("pizza") # Prints "Eating pizza"

The john object now has its own state (properties) and behaviour (methods).


Self vs This

An important detail in methods is how they access the object’s attributes and other methods. Here another difference between languages comes into play:

  • Self: In Python, attributes and methods are accessed within the class by prepending self. This points to the instantiated object.
class Person:

  def __init__(self, name):
    self.name = name

  def greet(self):
    print(f"Hello! I'm {self.name}")

john = Person("John")
john.greet()
# Prints "Hello! I'm John"
  • This: In Java or C#, this is used instead of self. It fulfils the same functionality of pointing to the object’s members.
public class Person {

  private String name;

  public Person(String name) {
    this.name = name;
  }

  public void greet() {
    System.out.println("Hello! I'm " + this.name);
  }
}

Person john = new Person("John");
john.greet();
# Prints "Hello! I'm John"

Conclusion

Classes and objects are the key concepts in OOP, allowing modelling real-world entities and generating modular, generic components of our system to construct more robust and easy to understand programmes.

2 - Encapsulation

One of the fundamental pillars of object-oriented programming is encapsulation. This powerful characteristic allows us to control access to class members, hiding implementation details and protecting the state of our objects. In this article we will delve into the concept of encapsulation, the usefulness of getters, setters, public/private properties and methods, and the important benefits this provides us as developers.

The Oxford Dictionary defines encapsulation as “enclosed or contained as if in a capsule”. This is precisely what we seek to achieve - “packaging” data and code within a single capsule (the class) to hide its internal complexity.

The formal definition would be:

“Encapsulation refers to bundling data and functions that manipulate these data into a single entity or software module.”1

That is, keeping related data and behaviours together in order to restrict direct access to that data from other parts of the programme, interacting only through a controlled interface (public API).

This provides advantages like:

  • Control over data modification.
  • Flexibility to change internal parts without affecting others.
  • Protection of the consistent state of objects.
  • Hiding complexity from the user.

Let’s see with concrete examples how to encapsulate in OOP.


Getters and Setters

Say we have a BankAccount class, with properties like name, account number and balance:

class BankAccount:

    name = ""
    account_number = 0
    balance = 0.0

We can directly access attributes like:

account1 = BankAccount()
account1.name = "John"
account1.account_number = 1234
account1.balance = 2500

The problem is any other code can modify the balance to invalid values:

account1.balance = -9900 # Balance can't be negative in this bank!

This allows inconsistent state. To encapsulate we use getters and setters:

class BankAccount:

    def __init__(self):
        self.__balance = 0

    def get_balance(self):
        return self.__balance

    def set_balance(self, value):
        if value < 0:
            raise Exception("Balance can't be negative in this bank")
        self.__balance = value
  • __balance is now private. It is only manipulated via the public getters and setters.

  • The setter controls invalid values not being input.

In Python, prepending double underscore __ denotes a private method or attribute of the class. With a single underscore _ it’s by convention a protected element, accessible from class and subclasses but not externally. And with no underscores, methods and attributes are public.

In Java this is explicit using the keywords public, protected and private:

public class Person {

    private String name; // Private

    public String getName() { // Public
        return this.name;
    }

}

This notation helps declare the desired visibility to properly apply encapsulation.


Benefits of encapsulation

This powerful technique provides great advantages:

  • Information hiding: Implementation details are invisible to other objects, reducing coupling. Internal code can change minimizing impact.
  • Control over data: Integrity and validity of state is guaranteed via setters/validators.
  • Flexible code: Isolation between interfaces and specifics enables building more extensible and maintainable systems over time.

“Every module hides the complexity of its contents behind a simple fa??ade (interface)”, Gang of Four2.

Ultimately, when we need to control how internal state is manipulated in a class from other parts of the application, encapsulation is the best solution.


Conclusion

Applying encapsulation by restricting direct access to data and carefully coding a public access interface allows us to build more robust, secure and sustainable OOP systems over time.

Mastering these techniques requires experience and good judgement to find the right balance between information hiding and flexibility. But undoubtedly it???s worth the effort to leverage the benefits we???ve seen from this wonderful OOP principle.


References


  1. Byron, Jeff. Encapsulation in Java. https://stackify.com/encapsulation-in-java/ ↩︎

  2. Gamma, Erich et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. 1994. ↩︎