회원가입

13. [상속] 다중 상속의 위험성

NULL 2021-10-12

이번에는 Cashier 클래스와 DeliveryMan 클래스를 다중 상속해 보겠다.

Cashier 역할과 DeliveryMan 역할을 동시에 하는 CashierDeliveryMan 클래스를 만들어보자.

 

class CashierDeliveryMan(DeliveryMan, Cashier):

 

지금 클래스들 사이의 관계를 표현하면 다음과 같다.

그럼 CashierDeliveryMan 클래스의 __init__ 메서드는 어떻게 써야할까?

그냥 CashierDeliveryMan 클래스의 부모 클래스들이 __init__ 메서드에서 하던 걸 그대로 해 주면 된다.

class Employee:
    """직원 클래스"""
    company_name = "코드잇 버거"
    raise_percentage = 1.03

    def __init__(self, name, wage):
        """인스턴스 변수 설정"""
        self.name = name
        self.wage = wage

    def raise_pay(self):
        """직원 시급을 인상하는 메소드"""
        self.wage *= self.raise_percentage

    def __str__(self):
        """직원 정보를 문자열로 리턴하는 메소드"""
        return Employee.company_name + " 직원: " + self.name

class Cashier(Employee):
    """계산대 직원 클래스"""
    # 햄버거 가격
    burger_price = 4000
    raise_percentage = 1.05

    def __init__(self, name, wage, number_sold=0):
        super().__init__(name, wage)
        self.number_sold = number_sold

    def take_order(self, money_received):
        if Cashier.burger_price > money_received:
            print("돈이 충분하지 않습니다. 돈을 다시 계산해서 주세요!")
            return money_received
        else:
            self.number_sold += 1
            change = money_received - Cashier.burger_price
            return change

    def __str__(self):
        return Cashier.company_name + " 계산대 직원: " + self.name

class DeliveryMan(Employee):
    """배달원 클래스"""

    def __init__(self, name, wage, on_standby):
        super().__init__(name, wage)
        self.on_standby = on_standby

    def deliver(self, address):
        """배달원이 대기 중이면 주어진 주소로 배달을 보내고 아니면 메시지를 출력한다"""
        if self.on_standby:
            print(address + "로 배달 나갑니다!")
            self.on_standby = False
        else:
            print("이미 배달하러 나갔습니다!")

    def back(self):
        """배달원 복귀를 처리한다"""
        self.on_standby = True

    def __str__(self):
        return DeliveryMan.company_name + " 배달원: " + self.name

class CashierDeliveryMan(DeliveryMan, Cashier):
    def __init__(self, name, wage, on_standby, number_sold=0):
        Employee.__init__(self, name, wage)
        self.on_standby = on_standby
        self.number_sold = number_sold

cashier_and_delivery_man = CashierDeliveryMan("강영훈", 7000, True, 10)
cashier_and_delivery_man.take_order(3000)
cashier_and_delivery_man.deliver("코드잇 건물 101호")
cashier_and_delivery_man.deliver("코드잇 건물 102호")
cashier_and_delivery_man.back()

print(cashier_and_delivery_man)
돈이 충분하지 않습니다. 돈을 다시 계산해서 주세요!
코드잇 건물 101호로 배달 나갑니다!
이미 배달하러 나갔습니다!
코드잇 버거 배달원: 강영훈

아주 잘되는 것을 확인할 수 있다.

 

그렇지만 짚고 넘어가야 할 부분이 있다.

print(cashier_and_delivery_man)

위의 코드 결과는 아래와 같이 나왔다.

코드잇 버거 배달원: 강영훈

 

인스턴스를 출력하면 DeliveryMan 클래스의 __str__ 메서드가 호출된다.

일단 CashierDeliveryMan 클래스에는 __str__ 메서드가 없다.

하지만 CashierDeliveryMan 클래스의 부모 클래스Cashier 클래스와 DeliveryMan 클래스에는 모두 __str__ 메서드가 있다.

 

어느 부모의 __str__ 메서드가 실행된 것일까?

결과를 보면 DeliveryMan 클래스의 __str__ 이 실행되었다.

지금 Cashier 클래스와 DeliveryMan 클래스에 있는 __str__ 메서드 중 어느 게 실행될지 애매한 상황이다.

 

그런데 왜 DeliveryMan 클래스의 __str__ 이 실행된 것일까?

정답은 mro 메서드를 사용하면 알 수 있다.

print(CashierDeliveryMan.mro())
[<class '__main__.CashierDeliveryMan'>, <class '__main__.DeliveryMan'>, <class '__main__.Cashier'>, <class '__main__.Employee'>, <class 'object'>]

위의 순서에서 가장 먼저 앞에 있는 클래스에 의해 오버라이드 되어서 DeliveryMan 클래스의 __str__ 이 실행된 것이다.

 

하지만 이 mro 순서는 클래스 간의 상속 관계에 따라 순서가 변할 수 있다.

만약 상속 하는 클래스의 순서를 바꾸면

class CashierDeliveryMan(Cashier, DeliveryMan):
    def __init__(self, name, wage, on_standby, number_sold=0):
        Employee.__init__(self, name, wage)
        self.on_standby = on_standby
        self.number_sold = number_sold

print(CashierDeliveryMan.mro())
[<class '__main__.CashierDeliveryMan'>, <class '__main__.Cashier'>, <class '__main__.DeliveryMan'>, <class '__main__.Employee'>, <class 'object'>]

이번에는 Cashier 가 먼저 나온다.

 

중요


0 0