Object Oriented Programming

Object-orientation (OO) is an approved paradigm to organize the complexity of software. The methods of object-oriented analysis (OOA) and design (OOD) provide an engineering approach, which leads from the specific requirements to its software implementation. The general procedure is based on principles of modularization (decomposition), encapsulation, abstraction and hierarchy.

In the 1980s Alan Kay introduced the term „Object Oriented Programming“.

An OO Language should at least support the following three key principles:

  • Encapsulation
  • Inheritance
  • Polymorphism

Encapsulation

Encapsulation essentially has two use cases:

  • keep state valid
Note!

An object is responsible to guarantee it’s state is valid!

  • information hiding

Imagine a class point that can be moved with Cartesian coordinates or with polar coordinates. How the data is stored internally is not relevant for the caller.

class Point:
    def translate(x, y) -> None:
        # intern implementation

    def translate(radius, phi) -> None:
        # intern implementation

Implementation Details Many programming languages provides explicit visibility keywords. e.g. in C++:

class Point {
    private:
        int x = 0;
        int y = 0;
}

However in Python all functions and attributes are publicly accessible. Therefore, as a convention, an underscore is placed in front of “private” functions and attributes.

class Point:
    def __init__:
        self._x = 0
        self._y = 0

Inheritance

Classes can be specializations of other classes. That means classes can be in hierarchical order by inheriting properties and behavior of higher ranked classes. Lower ranked classes can specialize (override) or extend higher classes. On code level Inheritance may be used to avoid redundancy by allowing code reuse.

Inheritance can be implemented very easily in python with the class declaration class <class_name>(base_class_name)

class Shape:

    def __init__(self) -> None:
        self._center_x = 0.
        self._center_y = 0.

    def translate(self, delta_x: float, delta_y: float) -> None:
        self._center_x += delta_x
        self._center_y += delta_y

    def draw(self) -> None:
        print(self)

class Circle(Shape): # Circle inherits from Shape

    def __init__(self, radius: float) -> None:
        super().__init__() # calling the base class constructor in the sub class
        self._radius = radius

    def __str__(self) -> str:
        return "I am a circle!"

However, the inheritance is a very strong dependence of the inheriting class to the base class. This means that inherited behavior cannot be replaced. it is different with composition. When it comes to redundancy avoidance, composition should be prioritized.

Note!

Favor composition over inheritance

However, inheritance enables another very important principle: polymorphism.

Polymorphism

In general, polymorphism is the ability to have different individuals of a species. In object-oriented programming, polymorphism refers to a programming language’s ability of objects to react differently to one and the same message depending on their class. Technically, this is achieved by redefining methods in derived classes - Inheritance. One may also speak of the autonomy or independence of objects.

In this example, an object of the class Lamp depends only on the interface LightBulb and the method lightUp(), which means that it can call its methods.

class Lamp:
    def __init__(bulb: LightBulb) -> None: # dependency (constructor) injection
        self._bulb: LightBulb = bulb

    def on(self) -> None:
        self._bulb.lightUp() # sending a message to the dependency

Lamp does not now about the specific LightBulb implementations, like ClassicalBulb or EnergySavingBulb.

The construction of Lamp and the specific LightBulb needs to take place somewhere else, e.g. in the main script. Depending on how bulb is instantiated, it behaves in Lamp differently. Here you can see the autonomy of the object.

bulb: LightBulb

if save_energy
    bulb = EnergySavingBulb()
else
    bulb = ClassicalBulb()

lamp = Lamp(bulb)