Example 1

Let's consider an example where we need to create different sorting algorithms for a list of integers. Without using an abstract class, we might create individual classes for each sorting algorithm:

Without Abstract Class:

class BubbleSort:
    def sort(self, data):
        # Implementation of Bubble Sort
        pass

class MergeSort:
    def sort(self, data):
        # Implementation of Merge Sort
        pass

class QuickSort:
    def sort(self, data):
        # Implementation of Quick Sort
        pass

In this approach, each sorting algorithm is defined in a separate class, resulting in code duplication, potential inconsistencies, and a lack of a clear common interface. Suppose we want to use these sorting algorithms in a pipeline. In that case, we would need to handle each class differently, leading to harder-to-maintain code and potential issues when adding new algorithms.

Problems and Consequences:

  1. Code Duplication: Each sorting algorithm class contains its implementation, which can lead to redundant code, making it harder to maintain and update the codebase.
  2. Lack of Polymorphism: Without a common interface, using these sorting classes in a pipeline or interchangeably becomes challenging, as they do not share the same method name.
  3. Inconsistent Interfaces: Each class may have different method names or parameter lists, making it harder for developers to work with them uniformly.

Using Abstract Class:

Let's improve the code by using an abstract class to define the common interface for sorting algorithms:

from abc import ABC, abstractmethod

class SortingAlgorithm(ABC):
    @abstractmethod
    def sort(self, data):
        pass

class BubbleSort(SortingAlgorithm):
    def sort(self, data):
        # Implementation of Bubble Sort
        pass

class MergeSort(SortingAlgorithm):
    def sort(self, data):
        # Implementation of Merge Sort
        pass

class QuickSort(SortingAlgorithm):
    def sort(self, data):
        # Implementation of Quick Sort
        pass

Advantages:

  1. Code Reusability: By using an abstract class, the common sorting algorithm interface is defined only once, reducing code duplication and making it easier to add new algorithms in the future.
  2. Polymorphism and Substitution: All sorting algorithms now share the same method name "sort," allowing for uniform usage, such as in pipelines or interchangeable operations.
  3. Consistent Interfaces: Each sorting algorithm subclass adheres to the common interface, promoting better maintainability and easier comprehension for developers.

Using an abstract class ensures that all sorting algorithms adhere to a common interface, making the codebase more modular, easier to maintain, and scalable when adding new sorting algorithms in the future. It fosters code reuse, promotes consistency, and allows for polymorphic behavior, resulting in a more efficient and maintainable codebase.