python/SOLID

Liskov Substitution Principle

wefree 2023. 12. 6. 22:53

정의

Subclasses should NOT change the behavior of superclasses in unexpected ways.

 

Selecting on types 유형

위반 코드

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


class Manager(Employee):
    def __init__(self, name, department):
        super().__init__(name)
        self.department = department


def print_employee(e):
    if type(e) is Employee:
        print(f"{e.name} is an employee")
    elif type(e) is Manager:
        print(f"{e.name} leads department {e.department}")

 

  • Manager 클래스의 인스턴스가 필요한 곳에, Employee 클래스(Manager 클래스의 superclass) 의 인스턴로 대체할 수 없다. (Manager type 인지 Employee type 인지 체크되어야 대체될 수 있다.)

개선 코드

https://wefree.tistory.com/328 참고

 

Break the is-a relationship 유형

위반 코드

아래처럼 초기 Employee 코드에서

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def print_year_salary(self):
        print(f"{self.name} year salary ${self.salary * 12}")

 

salary 를 받지 않는 Intern 클래스를 Employee 클래스를 상속 받아 추가했다.

class Intern(Employee):
    def __init__(self, name, salary):
        # Intern 은 salary 를 받지 않으므로 None 으로 세팅, 0 으로 세팅하는 것이 좋을거라 생각할 수 있지만?
        super().__init__(name, None)

 

  • salary 값을 None 으로 세팅했는데, 0 이 더 좋을거라 생각할 수도 있다. 0으로 세팅했을 경우, 만약 Employee 의 평균 salary 값을 구하는 함수를 추가할 때 0 으로 세팅한 값 때문에 문제가 될 수 있다. (None 과 0 은 다르다!)

Intern 의 salary 값이 None 이기 때문에, superclass 의 print_year_salary() 가 잘 동작하기 위해서는 아래처럼 수정되어야 한다.

    def print_year_salary(self):
        if type(self) is not Intern:
            print(f"{self.name} year salary ${self.salary * 12}")

 

즉, subclass Intern 때문에 superclass Employee 의 print_year_salary() 가 변경되어야 했다 !!!

근본적으로 Intern 을 Employee 로 부터 상속받아 is-a 관계를 만든것이 잘못이다.

 

Raise error in overridden method 유형

위반 코드

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

    def promote(self):
        print("Promote employee")


class Intern(Employee):
    def promote(self):
        raise NotImplementedError("Interns cannot be promoted")


def promote_employee(e):
    e.promote()
  • pormote() 함수의 경우 superclass 인 Employee 에서는 Exception 이 발생하지 않는데, subclass 인 Intern 의 경우 Exception 이 발생할 수 있다.
  • Employee 인스턴스를 받아 Exception  발생 없이 잘 처리되던 promote_employee() 함수였다. 그런데 Employee 인스턴스 대신에 subclass 인 Intern 인스턴스로 교체했더니 promete_employee() 호출 도중 Exception 이 발생해 프로그램이 깨져버렸다.

 

Break constraints

위반 코드

아래처럼 초기 코드가 있는 상태에서

class Employee:
    def __init__(self, employee_id, name):
        self.employee_id = employee_id
        self.name = name

    def is_employee_id_valid(self):
        return type(self.employee_id) is int and self.employee_id > 0


class Intern(Employee):
    def __init__(self, employee_id, name):
        super().__init__(employee_id, name)

 

Intern 클래스의 employee_id 는 I 로 시작하도록 변경해 달라고 요청이 왔다. 시간이 없어 꼼수로 아래처럼 수정했다.

class Intern(Employee):
    def __init__(self, employee_id, name):
        super().__init__(f"I{employee_id}", name)

 

그런데, 이렇게 구현할 경우 superclass 의 is_employee_id_valid() 의도에서 벗어나게된다. (superclass 를 수정해야 하나?)

 

참고: https://www.udemy.com/course/solid-design-principles-with-python