Showing posts with label Inheritance. Show all posts
Showing posts with label Inheritance. Show all posts

Saturday, September 21, 2024

The most Powerful Concepts in Programming Languages: Object-oriented Paradigm



In this blog, I want to present the concepts that I have found to be the strongest in programming languages, and if you master them, you have solid coding thinking. I will explain them individually by providing examples in Java and Python. In this article, I will explain the first one (Object-oriented) and present the others in future posts.


1. Object-oriented

2. Recursion
3. Concurrency
4. Functional Programming and Lambda Expressions
5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning



1. What is Object-oriented ?

Object-oriented is one of the most powerful concepts that has revolutionized programming languages. It allows you to create software similar to real entities by capturing their behavior and interactions with each other.


Object-oriented is a programming paradigm that aims to structure code as reusable components called objects. These objects represent real-world entities and encapsulate static (data) and dynamic behavior. Data are represented by fields or properties. In other words, they are constants and variables with different scopes, e.g. private or public. The values of an object’s fields at a given instant constitute its state. The behavior of an object is the change in its state (i.e, the value of its variables). That is possible thanks to the execution of its methods. 


A method is a function with arguments that, once called, is executed within the object itself or from another object. When an object invokes a method of another object, we call this action ‘message passing’. That implements the interactions between objects.


An example of an object is an employee with properties such as name or salary. These fields represent the object employee’s data. When an employee starts work, an ‘employee’ object can be created, and when he quits, its representative object is destroyed.


As you notice, many employees have the same properties and are subject to the same action. Therefore, we can group them in a collection called a class. A class is a template for defining the common properties and methods of all objects in that class.


The software in the object-oriented paradigm is a collection of self-contained modules that interact with each other. The module itself is composed of classes.


The advantages of object-oriented are multiple. It promotes well-organized, self-contained code, modular, and reusable code. That is helpful in developing and maintaining complex software. In object-oriented systems, many developers can work on the same project in parallel and collaborate easily with each other. That helps speed up the development process and reduce its costs. In the next subsections, I introduce a brief description of the main concepts of the object-oriented paradigm.


1.1. Encapsulation

When using an object-oriented library, all you need to know is the signature of the class or its methods and what they do. You don’t need to know the implementation details of such a class or its methods. That is called encapsulation and aims to hide details and complexities. The object-oriented paradigm exposes only the necessary class interface and its methods so that you can use it without getting bogged down in implementation details that remain hidden from you.


1.2. Inheritance

It is a mechanism that allows a class (called sub-class in this case) to inherit properties and behaviors from another class, called the super-class or base class. Inheritance promotes code reuse because it implements ‘is-a’ relationship for more naturally structured code. Inheritance is a relationship between classes. For instance, if you have classes like ‘Dog’ or ‘Cat’, you realize that they are both animals. You can create an ‘Animal’ class to encapsulate common properties and methods between them, such as size, color, eating, or breathing.


1.3. Abstraction

Encapsulation and inheritance promote the concept of abstraction. By hiding unnecessary implementation details and revealing the essential characteristics of objects, the code becomes more readable and reusable. Note that classes’ interfaces are defined independently of their implementations, which means you can always think abstractly. You can first define the classes and then group common behavior between certain classes to get abstract classes.


For example, all employees have general properties such as names and dates of birth. These properties are not relevant to the ‘Employee’ class but rather to the ‘Person’ class. You can extract an abstract ‘Person’ class to wrap the generic behavior of all employees. Employees can be divided into different groups like managers, secretaries, or software developers. You can keep specialized classes, from the most abstract to the most concrete, to form a class hierarchy. This decomposition of your software system generates small manageable and understandable components.


1.4. Association

An association is a relationship between classes that allows objects to interact with each other. In an association, objects exchange information and collaborate to achieve an overall goal. Associations can be of different types such as one-to-one, one-to-many, or many-to-many. An example of a one-to-many relationship is that an employee can have multiple accounts or addresses.


1.5. Composition

It implements ‘has-a’ relationship and it is a relationship between objects. A composition is an association when an object owns another one. For instance, a car has wheels.


1.6. Polymorphism

Polymorphism comes from two Greek words, ‘Poly’ which means numerous, and ‘Morphs’ which means forms. Polymorphism is the ability of an object to respond in different ways to different situations. For example, the method ‘Equal’ which returns true or false whether two objects are equal or not, is a polymorph method. It acts on all types of the two objects. 


2. Example of OO in Java

In this section, we create a small example to illustrate the concept of object-oriented with inheritance and polymorphism. The example concerns certain classes ‘Shape’, ‘Rectangle’, and ‘Circle’. As you can see, the ‘Rectangle’ and ‘Circle’ classes are specific shapes. In other words, the ‘Shape’ class is the super-class of ‘Rectangle’ and ‘Circle’ and will encompass the common properties and behaviors of the circle, rectangle, and any other shape. Let’s imagine some code for this class:


public class Shape {

    protected String name;

    public Shape(String name) {

        this.name = name;

    }

    public double getArea() {

        return 0.0;

    }

    public double getPerimeter(){

        return 0.0;

    }

    public void showInfo() {

        System.out.println("Shape Type: Shape");

        System.out.println("Name: " + name);

    }

}


According to this code, each shape has a name declared as protected to be visible at the level of its sub-classes. Each shape has an area and a perimeter. For this reason, we created two methods ‘getArea’ and ‘getPerimeter’ to calculate them respectively. But inside the code of a shape, we don’t know how to do it. So we just return 0.0. We also declare a method that helps us display some information about the shape. We now create the ‘Rectangle’ and ‘Circle’ sub-classes that inherit from the ‘Shape’ class:


public class Rectangle extends Shape {

    private double width;

    private double height;

    public Rectangle(String name, double width, double height) {

        super(name);

        this.width = width;

        this.height = height;

    }

    @Override

    public double getArea() {

        return width * height;

    }

    @Override

    public double getPerimeter(){

        return 2*(width+height);

    }

    @Override

    public void showInfo() {

        System.out.println("Shape Type: Rectangle");

        System.out.println("Name: " + name);

        System.out.println("Width: " + width);

        System.out.println("Height: " + height);

        System.out.println("Area: " + getArea());

        System.out.println("Perimeter: " + getPerimeter());

    }

}


public class Circle extends Shape{

    private double radius;


    public Circle(String name, double radius) {

        super(name);

        this.radius = radius;

    }


    @Override

    public double getArea() {

        return Math.PI * radius * radius;

    }


    @Override

    public double getPerimeter(){

        return 2*Math.PI*radius;

    }


    @Override

    public void showInfo() {

        System.out.println("Shape Type: Circle");

        System.out.println("Name: " + name);

        System.out.println("Radius: " + radius);

        System.out.println("Area: " + getArea());

        System.out.println("Perimeter: " + getPerimeter());

    }

}


With the ‘extends’ keyword, the ‘Rectangle’ and ‘Circle’ classes inherit from the ‘Shape’ class. This inheritance allows them to have the ‘name’ field as their own property and they can add new attributes according to their specificity. In the ‘Rectangle’ class, we add the ‘width’ and ‘height’ fields, because a rectangle has these two dimensions. It’s now possible to calculate the area and perimeter of a rectangle using its newly added fields. 

In the ‘Rectangle’ class, we override the methods ‘getArea’ and ‘getPerimeter’ with new code to effectively calculate the area and perimeter of a rectangle. We also override the method ‘showInfo’ because we have more information about the ‘rectangle’ object. The same logic is applied to the circle. The ‘Circle’ class extends the ‘Shape’ class with the property ‘radius’ and overrides the methods ‘getArea’, ‘getPerimeter’, and ‘showInfo’ to provide its specific implementation.


We now create a class named ‘OODemo’ to instantiate the ‘Rectangle’ and ‘Circle’ classes and call their methods. 


public class OODemo {

    public static void main(String[] args) {

        Shape rectangle = new Rectangle("My Rectangle", 5.0, 7.0);

        Shape circle = new Circle("My Circle", 6.0);

        System.out.println("Rectangle Info:");

        rectangle.showInfo();

        System.out.println();

        System.out.println("Circle Info:");

        circle.showInfo();

    }

}


As you notice, the ‘rectangle’ and ‘circle’ instances are both declared to be ‘shape’. Bhowever, they are instantiated by using their own constructors to enable access to newly defined properties and methods in their ‘Rectangle’ and ‘Circle’ classes. The ‘rectangle’ instance will call its own ‘showInfo’ implementation, just like the ‘circle’ instance. Depending on each type, the appropriate ‘showInfo’ method will be called as well as the ‘getArea’ and ‘getPerimeter’ methods. Therefore, these methods are polymorph. 


The output after executing the main method of the class ‘OODemo’ displays the properties as well as the calculated area and perimeter for the rectangle and circle:


Rectangle Info:

Shape Type: Rectangle

Name: My Rectangle

Width: 5.0

Height: 7.0

Area: 35.0

Perimeter: 24.0


Circle Info:

Shape Type: Circle

Name: My Circle

Radius: 6.0

Area: 113.09733552923255

Perimeter: 37.69911184307752


3. Example of OO in Python

Python supports extensively the concepts of object-oriented and is considered an object-oriented programming language. You can start using object-oriented concepts in Python by defining classes with the ‘class’ keyword. A class in Python allows the encapsulation of attributes and methods that update the attributes. Let’s illustrate that through an example. 


We can take the same example described above for Java to show how Python implements object-oriented concepts. For that, we need to import the ‘math’ module to access the value of PI (3.14). Moreover, we import from the ‘abc’ module, the abstract class ‘ABC’, and the annotation ‘abstractmethod’.


Python provides such elements to permit you to define abstract classes that can also have abstract methods. Abstract methods should be overridden in sub-classes to be executed. The expression ‘Shape(ABC)’ means that the ‘Shape’ class inherits from the ‘ABC’ class. You have surely noticed this ‘__init__’ method which aims to initialize a newly created object.


import math

from abc import ABC, abstractmethod


class Shape(ABC):

    def __init__(self, name):

        self.name = name


    @abstractmethod

    def calc_area(self):

        pass


    @abstractmethod

    def calc_perimeter(self):

        pass


    def show_info(self):

        print("Shape Type: Shape")

        print("Name: " + self.name)


We create another class ‘Rectangle’ which inherits from the ‘Shape’ class. The ‘Rectangle’ class extends ‘Shape’ with new attributes (‘width’, ‘height’) and overrides the other methods (‘calc_area’, ‘calc_perimeter’, and ‘show_info’).


class Rectangle(Shape):

    def __init__(self, name, width, height):

        super().__init__(name)

        self.width = width

        self.height = height


    def calc_area(self):

        return self.width * self.height


    def calc_perimeter(self):

        return 2 * (self.width + self.height)


    def show_info(self):

        print("Shape Type: Rectangle")

        print("Name: " + self.name)

        print("Width: " + str(self.width))

        print("Height: " + str(self.height))

        print("Area: " + str(self.calc_area()))

        print("Perimeter: " + str(self.calc_perimeter()))


We can also create another subclass ‘Circle’ of the ‘Shape’ class which will define its own attributes like ‘radius’ and override the ‘calc_area’, ‘calc_perimeter’, and ‘show_info’ methods as follows:


class Circle(Shape):

    def __init__(self, name, radius):

        super().__init__(name)

        self.radius = radius


    def calc_area(self):

        return math.pi * self.radius ** 2


    def calc_perimeter(self):

        return 2 * math.pi * self.radius


    def show_info(self):

        print("Shape Type: Circle")

        print("Name: " + self.name)

        print("Radius: " + str(self.radius))

        print("Area: " + str(self.calc_area()))

        print("Perimeter: " + str(self.calc_perimeter()))


Like in Java code, these methods are also polymorph in Python. To see this, let’s create an object of the ‘Rectangle’ class and another of the ‘Circle’ class in Python as follows: 


rectangle = Rectangle("My Rectangle", 5.0, 7.0)

circle = Circle("My Circle", 6.0)

print("Rectangle Info:")

rectangle.show_info()

print("")

print("Circle Info:")

circle.show_info()


We called the method ‘show_info()’ of the ‘rectangle’ and ‘circle’ instances to obtain the following result:


Rectangle Info:

Shape Type: Rectangle

Name: My Rectangle

Width: 5.0

Height: 7.0

Area: 35.0

Perimeter: 24.0


Circle Info:

Shape Type: Circle

Name: My Circle

Radius: 6.0

Area: 113.09733552923255

Perimeter: 37.69911184307752


Each instance will call its own implementation of one of the three methods depending on its needs.




Friday, September 20, 2024

Composition over Inheritance Principle



In this series, I will highlight some areas where you can improve the quality of your code during the development process. After presenting some architectural concepts like SOLID principles in previous articles, I will elucidate the ‘Inheritance over Composition Principle’ in this article. I will show how composition is implemented in Java and Python using examples.


1. Key Concepts

Inheritance and composition are object-oriented concepts used to set up relationships between classes or objects. You should use these two principles soundly because of their major effect on your software architecture. Let’s define these two mechanisms and compare them.


1.1. Inheritance

Inheritance is a fundamental concept in object-oriented programming (OOP). It is a relationship between classes, in which one class (called a sub-class) inherits properties and behaviors (fields and methods) from another class (called a super-class). Inheritance implements the ‘is-a’ relationship and draws a hierarchical relationship between classes. This concept is easy to understand and promotes code reuse.


Benefits of Inheritance

Reusability: Sub-classes reuse the fields and methods of existing super-classes.

Method Overriding: A method inherited from a super-class may not satisfy the specific behavior in a sub-class. Therefore, a sub-class can override this method and provide its own implementation.

Extensibility: Sub-classes can add new functionality to extend their super-classes.

Polymorphism: Objects are treated as instances of their super-class.


1.2. Composition

Composition is an object-oriented programming concept that defines a relationship between objects. Composition means that an object of one class (called a composite) is composed of objects of other classes (called components).


Composition implements the ‘has-a’ relationship and allows the combination of simple objects to form complex objects. Composite object methods can delegate tasks to contained object methods. This contributes to functionality reuse without inheritance.


Benefits of Composition

We’ll talk about the benefits of composition in the next section when we compare it to inheritance.


2. Inheritance over Composition Principle

The principle of composition over inheritance (or composite reuse) recommends fostering composition over inheritance for the reasons below. This doesn’t mean you never use the inheritance concept. But do it in a limited way. Overusing inheritance can lead to tight coupling and inflexible designs.


Inheritance is a powerful mechanism when you respect in your design the nature of the real system that the software represents. When you recognize an intrinsic ‘is-a’ relationship, such as a car is a vehicle, use inheritance between classes.


Preferring composition over inheritance is due to the problems induced by inheritance, such as:

Broken encapsulation: As a subclass inherits from a super-class, the sub-class can see all the details of its parent class. That’s why the concept of encapsulation is considered broken.

Large hierarchies: If you have a deep inheritance hierarchy, this will result in growing complexity and reduced clarity. When you make some changes to super-classes, it would be hard to estimate their impact on the sub-classes.

Tight coupling: Any change in the top-level super-class will lead to many changes at the sub-classes level.


On the other hand, the composition has multiple advantages like:

Flexibility: Since the component object is injected into its composite object using its interface, it is easy to override its implementation.

Reusability: Different classes can reuse the same component classes.

Loose coupling: Objects are independent of each other. Modifying one has no impact on the other. This makes the code base easier to maintain and extend.

Testability: It is better when you use composition.


3. Examples

In this section, we explain the concept of composition with the help of examples in Java and Python.

3.1. Example in Java

In this example, we show how to implement composition in Java. We define a class ‘Vehicle’, its sub-class ‘Car’, and another class ‘Steeringwheel’. An object of the ‘Steeringwheel’ class is a component of an object of the ‘Car’ class. To fulfill such a composition, we create an interface ‘ISteeringwheel’ and make the class ‘Steeringwheel’ implement this interface. 


We add a ‘steeringwheel’ attribute to the ‘Car’ class but of type ‘Isteeringwheel’ (interface) and not of type ‘Steeringwheel’ (class). The component object (‘steeringwheel’) is injected using its interface when instantiating the ‘Car’ class.


public class Vehicle { }

public interface ISteeringwheel { }

public class Steeringwheel implements ISteeringwheel{ }

public class Car extends Vehicle{

    ISteeringwheel steeringwheel;

    public Car(ISteeringwheel steeringwheel) {

        this.steeringwheel = steeringwheel;

    }

}


public class CompositionDemo {

    public static void main(String[] args) {

        ISteeringwheel steeringwheel = new Steeringwheel();

        Car car = new Car(steeringwheel);

    }

}


The advantages of using composition are obvious here. The ‘Car’ and ‘Steeringwheel’ classes are independent. Any change in the ‘Steeringwheel’ class is not visible in the ‘Car’ class and vice versa. In addition, it is easy to replace the implementation of one object of the ‘Steeringwheel’ class with another, which makes it flexible.


3.2. Example in Python

Python supports the principle of composition to create complex objects based on simpler objects. Let’s take a well-known example of a system implementing a book library to illustrate how to implement composition in Python.


This system consists of three classes ‘Library’, ‘Book’, and ‘Author’. An object of the ‘Library’ class is made up of several objects of the ‘Book’ class and each book has an author.


Author Class: Represents an author with a first name, last name, and country. The constructor of the ‘Author’ class (__init__) initializes an instance of this class with the  ‘firstname’, ‘lastname’, and ‘country’ attributes. The ‘__str__’ method displays a string representation of an object of the ‘Author’ class. This string is the result of the concatenation of its three attributes.


class Author:

    def __init__(self, firstname, lastname, country):

        self.firstname = firstname

        self.lastname = lastname

        self.country = country


    def __str__(self):

        return f'{self.firstname} {self.lastname} ({self.country})'


Book Class: Represents a book with a title, an author (instance of the ‘Author’ class), the year of publication, and the language in which the book is published.


class Book:

    def __init__(self, title, author, year, language="English"):

        self.title = title

        self.author = author

        self.year = year

        self.language = language


    def __str__(self):

        return f'"{self.title}" by {self.author} in {self.language} (published in {self.year})'


Library Class: This class has an attribute ‘books’ that stores a list of ‘Book’ objects. This class provides ‘add_book’, ‘remove_book’, and ‘show_books’ methods. The ‘add_book’ method adds a book to the library. The ‘remove_book’ method removes a book from the library. The ‘show_books’ method lists the available books in the library.


class Library:

    def __init__(self):

        self.books = []


    def add_book(self, book):

        self.books.append(book)


    def remove_book(self, book):

        self.books.remove(book)


    def show_books(self):

        for idx, book in enumerate(self.books):

            print(f'{idx + 1}. {book})')


To use this code, we can create some objects of the ‘Library’, ‘Book’, and ‘Author’ as follows:


# Creating instances of the Author class

author1 = Author("Stephen", "King", "USA")

author2 = Author("Albert", "Camus", "France")


# Creating instances of the Book class

book1 = Book("The Shining", author1, 1977, "English")

book2 = Book("The Green Mile", author1, 1996, "English")

book3 = Book("La Peste", author2, 1947, "French")


# Creating an instance of the Library class and adding books

library = Library()

library.add_book(book1)

library.add_book(book2)

library.add_book(book3)

print('''

List of books in the library after adding three books:

''')


# Listing all books in the library

library.show_books()


print('''

List of books in the library after removing one book:

''')

# Removing a book from the library

library.remove_book(book3)


# Listing all books in the library

library.show_books()


The execution of these examples gives us the following output:


List of books in the library after adding three books:


1. "The Shining" by Stephen King (USA) in English (published in 1977))

2. "The Green Mile" by Stephen King (USA) in English (published in 1996))

3. "La Peste" by Albert Camus (France) in French (published in 1947))


List of books in the library after removing one book:


1. "The Shining" by Stephen King (USA) in English (published in 1977))

2. "The Green Mile" by Stephen King (USA) in English (published in 1996))

Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...