Search code examples
pythonsolid-principlesliskov-substitution-principle

Understanding LSP Python


So the LSP says that if S is a subtype of T, then any instance of S should be able to replace any instance of T without altering any of the desirable properties of that program.

Does that mean that this would fail the LSP because when you do dev_1.print_name()(substituting the instance of the subclass with that of the base class -ish), you get an unexpected result(failure) because you haven't initialised the name?

@dataclass
class Employee:
    name: str

    def print_name(self):
        print(self.name)

class Developer(Employee):
    def __init__(self, work_from_home: bool):
        self.work_from_home = work_from_home
    

dev_1 = Developer(True)
dev_1.print_name()

And the way to fix that would be to change the Developer class to something like this, to be compatible with all the methods of the Employee class?

class Developer(Employee):
    def __init__(self, work_from_home: bool, name:str):
        self.work_from_home = work_from_home
        super().__init__(name)

Solution

  • You are correct. As your code is written, creating an Employee will require that its name attribute be given a value. But as written, your code will not initialize name when creating a Developer. Just as you say, this leads to a Developer object not behaving like an Employee object, so it violates LSP.

    Your solution of adding a constructor that takes a value for the name attribute does indeed solve the problem. The standard way to get this same behavior is to annotate Developer as a data class as well so that you get the proper constructor for free:

    @dataclass
    class Employee:
        name: str
    
        def print_name(self):
            print(self.name)
    
    @dataclass
    class Developer(Employee):
        work_from_home: bool
    
        def print_developer(self):
            print(f"Developer {self.name} does{'' if self.work_from_home else ' not'} work from home")
    
    def main():
        dev_1 = Developer("Jack", False)
        dev_1.print_developer()
    

    Result:

    Developer Jack does not work from home
    

    You can also treat a Developer object as an Employee:

    emp_1 = Developer("Jill", True)
    emp_1.print_name()
    

    Result:

    Jill