An Overview of Classes and Objects

Photo by Anshu A on Unsplash

An Overview of Classes and Objects

Introduction

In the previous articles of the series, we created various programs that manipulate data using variables of primitive data types and strings. We also learned about arrays, which allow organising and accessing multiple values of the same type using numeric indexes.

However, when more complex problems need to be solved, using only those data structures may not suffice—a different approach for storing and processing data may be required. The following listing shows how data of heterogeneous types have been logically grouped to model and represent real-world entities. In the example, each of the first two rows likely pertains to a Person entity, the data in the third row may be related to a Bank Account entity, and the last row organises data associated with a Car entity.

"James", "04/08/1962",  67.5 Kg, 175 cm 
"Tom", "05/03/1992",  85.7 Kg, 185 cm 
"A0123", "21/09/2022",  £300.50
"AB72CDE", "500", "Fiat", 55 HP, 95 km/h

Object's State: Attributes

At the core of the Object-Oriented Programming (OOP) paradigm lies the idea of designing programs starting from the data representing real-world entities involved in a specific problem to be solved. Following the above analysis, an abstract model definition is produced, and concrete instances of these entities—referred to as objects—will exist as part of an OOP program.

Each object is associated with a set of data pieces called attributes, which represent various characteristics or properties of an object. For example, the Person objects presented earlier include attributes such as name, date of birth, weight and height.

Considering a programming language viewpoint, attributes are variables allocated for each class object instance. For this reason, they are often called instance variables. The attributes of a Person object could be defined as the following set of variables:

class Person 
{
    private String name;
    private String dateOb;
    private double weight;
    private int height;

    // ...
}

An important concept in OOP is the notion of state. The state of an object is determined by the values its attributes hold at a given moment—a snapshot of the object.

According to the initial listing example, a Bank Account object's state is defined by the values of its number, creation date and balance. Similarly, the state of a Car object is based on the values of the registration number, model, make, power and speed attributes.

Object's Behaviours: Methods

In addition to its state, an object is characterised by a set of associated behaviours, namely operations it can perform (based on its state). When considering objects from the previous example, such behaviours could include actions like sayName, eat, getAge, and more for a Person object; deposit, withdraw, getBalance, and so forth for a Bank Account object; and setEngineOn, getSpeed, getModel, and similar actions for a Car object. Each of these behaviours is defined within the concept of method.

Methods represent operations that can be executed on an object. They often provide a means to retrieve attribute values or alter the object's state by updating the content of one or more attributes as needed.

From a programming perspective, a method is essentially a block of instructions delimited by curly braces { }. Methods have an associated name, so they can be called from other methods defined in the same program. Referring to the objects described at the beginning of this section, the sayName method may print the content of the attribute name for a Person object:

public void sayName()
{
    System.out.println("My name is " + name);
}

Likewise, the withdraw method can check the content of the balance attribute of a Bank Account object and perform a withdrawal if there is enough money in that account.

According to the encapsulation principle of OOP, each object contains its attributes and methods for accessing them directly. Encapsulation is a strong principle in OOP that supports data hiding and integrity, implementation hiding, and boosts code flexibility and maintainability.

Classes as Templates for Creating Objects

In general, we can say that all the objects that share common attributes and behaviours belong to the same class (of objects). As such, we could use a class to define a template—a blueprint to create objects sharing similar characteristics, i.e., common attributes and methods. In this sense, the figure below shows that a class could be seen as a cookie cutter from which multiple cookies (objects) of the same shape can be generated.

Although all objects created from a class template have the same features, the values associated with their attributes at a given time will likely differ, as each object has its specific state.

The following diagram shows how two different Person objects can be created from the same template—a Person class. They have the same attributes (name, dateOb, weight and height) and share common behaviours (sayName, eat, getAge), although a specific combination of attribute values describes their states. The left part of the diagram is a standardised way to represent classes, known as the UML Class Diagram.

In terms of code, a class represents a program module that defines the instance variables and the methods each object of that class will have. Building on the Person class example used so far, a corresponding class template could be defined as follows:

class Person {
    private String name;
    private String dateOb;
    private double weight;
    private int height;

    // methods to initialise instance variables ...

    // methods to retrieve or update the object state:
    public void sayName() {
        System.out.println("My name is " + name);
    }

    public void eat(int portions) {
        // update the weight 
    }

    public int getAge() {
        // retrieve the age
    } 
}

The above class definition is to be considered as a code starting point, and it is incomplete. Moreover, some syntax, notation and keywords may not be clear at this stage and will be described in other series articles.

Object Relationships

When designing an OOP system, it is important not only to identify the entities—with their attributes and methods—relevant to a specific problem. A proper design also should model the relationships between these entities. As a result, the objects created within a program will be associated with each other to model the relationships between the entities they represent. The following diagram uses a custom notation to present an example of possible relationships between objects of the Person, Bank Account, and Car classes mentioned earlier.

It can be noted that the person object James has a drives-a relationship with the Car object AB72CDE and a has-a relationship with the Bank Account object A0123. Because of these relationships, those objects can exchange data—or trigger the execution of behaviours of other objects—within the program where they have been created, as required by the specific problem to be solved.

Object Interaction and Encapsulation Principle

If the Person object James were to learn information about the model of the Car object AB72CDE, it would need to access the content of the corresponding model attribute of that object. However, according to the encapsulation principle mentioned earlier, only the Car object AB72CDE would have direct access to that attribute—the Car object would deliberately prevent objects of other classes from doing that.

In the OOP jargon, the above interaction would have to happen instead via "a message" sent from the Person object James to the Car object AB72CDE, requesting to provide the desired model information stored in the string attribute model (i.e., "500"). OOP languages implement the above interaction through method invocations. This is shown in the previous diagram via the red dashed line connecting the object James with the getModel method of the object AB72CDE.

The Person class defined in the previous section demonstrates the usage of the private and public keywords. Without going into details now, one should think that objects do not expose their attributes directly to the outside and keep them private; however, they provide an interface for other objects to interact with them through public methods. Hence, access to the model attribute of a Car object would not be performed directly by a Person object, but it would happen by invoking the getModel method that any Car object exposes. The details of the getModel methods and the implementation of the model attribute still do not need to be disclosed to objects of other classes.

A separate post on the encapsulation principle further discusses the above mechanisms and their advantages in designing maintainable code and preventing anomalies due to incorrect data.

Code Organisation: Modularity and Code Reuse

Furthermore, object relationships highlight the distinction between the functions provided by each object and their underlying implementations. Essentially, the classes used to create various types of objects define how specific functions are implemented. These classes serve as modular components of a program, which can be developed, tested, extended, and maintained independently. This modularity facilitates code reuse, eliminating the need to reimplement existing functions when creating new classes. Instead, code from other classes can be seamlessly reused, enhancing efficiency and maintainability.

Conclusions

Object-Oriented Programming (OOP) is a powerful paradigm that enhances the design and implementation of complex software systems. By modelling real-world entities as objects, OOP allows for a more intuitive and organised approach to programming. Each object encapsulates data (attributes) and behaviours (methods), promoting modularity and code reuse. Classes serve as blueprints for creating objects, ensuring consistency and facilitating the maintenance and extension of code. OOP's emphasis on objects and classes leads to more manageable and efficient code, making it an essential methodology in modern software development.