Skip to content

Lessons

TIP Composition over Inheritance. But, inheritance is not always evil. Inheritance is good when : * you have shallow (not deep) and narrow (not wide) heirarchy * the subclasses should be leaf nodes of your object graph * subclasses uses all behaviour of super class

Dependency Inversion Principle:

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.

Whenever doing Dependency injection, use interface rather than any specific class.

Rather than

class FXConverter:
    def convert(self, from_currency, to_currency, amount):
        ...
class App:
    def start(self, converter: FxConverter):
        converter = FXConverter()

Do this:

class CurrencyConverter(ABC):
    def convert(self, from_currency, to_currency, amount):
        ... 

class FXConverter(CurrencyConverter):
    def convert(self, from_currency, to_currency, amount):
        ...

class App:
    def __init__(self, converter: CurrencyConverter):
        self.converter = converter

Interface Segregation Principle:

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

If all subclasses dont implement the abstract class, separate out to new interface. Dont force.

Rather than

class Vehicle(ABC):
    @abstractmethod
    def go(self):
        pass

    @abstractmethod
    def fly(self):
        pass

Do this

class Movable(ABC):
    @abstractmethod
    def go(self):
        pass


class Flyable(Movable):
    @abstractmethod
    def fly(self):
        pass

Liskov Substitution Principle

This means that every subclass or derived class should be substitutable for their base or parent class.

Must implement all the methods of parent class.

Rather than

class Bird:
    def fly():
        ...
class Duck(Bird):
    """This is fine"""

class Ostrich(Bird):
    """This is not"""

Do this

class Bird:
    ....

class FlyingBird:
    def fly():
        ...

class Duck(FlyingBird):
    """This is fine"""

class Ostrich(Bird):
    """This is not"""

Open-Closed Principle

Open for extension but closed for modification. Objects or entities should be open for extension but closed for modification.

for adding any new behaviour you should have to change the existing code. Rather you should be able to extend the current behaviour

Rather than

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

class PersonStorage:
    def save_to_database(self, person):
        print(f'Save the {person} to database')

    def save_to_json(self, person):
        print(f'Save the {person} to a JSON file')

Do this

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

class PersonStorage(ABC):
    @abstractmethod
    def save(self, person):
        pass

class PersonDB(PersonStorage):
    def save(self, person):
        print(f'Save the {person} to database')

class PersonJSON(PersonStorage):
    def save(self, person):
        print(f'Save the {person} to a JSON file')

Single-Responsibility Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

Rather than

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

    @classmethod
    def save(cls, person):
        print(f'Save the {person} to the database')

Do this

class PersonDB:
    def save(self, person):
        print(f'Save the {person} to the database')

class Person:
    def __init__(self, name, db = PersonDB()):
        self.name = name
        self.db = db

    def save(self):
        self.db.save(person=self)