ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 클래스 - 메서드 오버라이딩과 다형성
    python 2021. 3. 11. 23:33

     

    다형성(polymorphism)이란 '상속 관계에 있는 다양한 클래스의 객체에서 같은 이름의 메서드를 호출할 때, 각 객체가 서로 다르게 구현된 메서드를 호출함으로써 서로 다른 행동(behavior), 기능, 결과를 가져오는 것'을 의미한다. 그리고 이를 구현하기 위해 파생 클래스(derived class) 안에서 상속받은 메서드를 다시 구현하는 것을 메서드 오버라이딩(method overriding)이라고 한다. 

     

    메서드 오버라이딩

    #code 1-1
    class CarOwner:
        def __init__(self, name):
            self.name = name
        
        def concentrate(self):
            print("{0} can not do anything else".format(self.name))
    
        #나머지 메서드
    class Car:
        def __init__(self, owner_name):
            self.owner = CarOwner(owner_name)
        
        def drive(self):
            self.owner.concentrate()
            print("{0} is driving now.".format(self.owner.name))

    Car 객체는 반드시 차 주인인 CarOwner 객체가 운전해야 하며 차 주인은 운전에만 집중해야 한다. drive() 메서드가 시작하면 CarOwner 객체의 concentrate() 메서드를 호출해 차 주인이 운전 외에는 아무것도 하지 못하게 한다. 

    #code 1-2
    class SelfDrivingCar(Car):
        def drive(self):
            print("Car is driving by itself")

    code 1-2는 Car를 상속받은 파생 클래스 SelfDrivingCar를 자율주행차로 모델링한 코드다. 자율주행차는 차 주인이 운전하지 않고 차가 스스로 운전하므로 상속받은 drive() 메서드가 어울리지 않는다. 이러한 경우 SelfDrivingCar 클래스를 상속하되 어울리지 않는 drive() 메서드만 클래스 안에서 다시 정의한다. 

     

    이렇게 drive() 메서드만 다시 구현하는 즉, 파생 클래스에서 상속받은 메서드를 다시 구현하는 것을 메서드 오버라이딩이라고 한다. 차 주인은 더 이상 차가 주행하는 동안 집중하지 않아도 되므로 오버라이딩된 drive() 메서드에는 차 주인의 concentrate() 메서드를 호출하지 않는다. 테스트 코드를 작성해서 결과를 확인해보자.

    #code 1-3
    if __name__ == "__main__":
        car = Car("Greg")
        car.drive()
        print("")
    
        s_car = SelfDrivingCar("John")
        s_car.drive()
    #실행 결과
    Greg can not do anything else
    Greg is driving now.
    
    Car is driving by itself

    Car 객체와 SelfDrivingCar 객체를 하나씩 만들어 각자 drive() 메서드를 호출한다. car.drive()는 Car 객체가 drive() 메서드를 호출하는 것이고, s_car.drive()는 SelfDrivngCar 객체가 drive() 메서드를 호출하는 것이다. 이름이 같은 메서드를 호출해도 구현 내용이 다르므로 결과는 다르게 나온다. 이처럼 같은 이름의 메서드를 호출해도 호출한 객체에 따라 다른 결과를 내는 것을 '다형성'이라고 한다. 

    오버라이딩은 다른 행동 혹은 기능을 의미한다. 기본 클래스 객체와 파생 클래스 객체의 여러 가지 행동(메서드)이 다르다면 과연 IS-A 관계가 맞는지 검토해 봐야 한다.

    다형성

    #code 2-1
    class Animal:
        def eat(self):
            print("eat something")
    
    class Lion(Animal):
        def eat(self):
            print("eat meat")
    
    class Deer(Animal):
        def eat(self):
            print("eat grass")
    
    class Human(Animal):
        def eat(self):
            print("eat meat and grass")
    
    if __name__=="__main__":
        animals = []
        animals.append(Lion())
        animals.append(Deer())
        animals.append(Human())
    
        for animal in animals:
            animal.eat()

    code 2-1을 보면 기본 클래스인 Animal에 eat() 메서드가 있다. 모든 동물은 반드시 무언가를 먹어야 한다. 이처럼 모든 파생 클래스가 공통으로 가질 메서드인 eat()은 기본 클래스에 둔다.

     

    모든 동물이 무언가를 먹지만 종류는 다르다. 고기만 먹는 육식 동물도 있고 초식 동물도 있지만 잡식 동물도 있다. Lion객체는 고기를 먹고, Deer 객체는 풀을 먹는다. Human 객체는 고기와 풀을 모두 먹는다. animals라는 리스트를 만든 다음 육식 동물, 초식 동물, 잡식 동물을 모두 담는다.

     

    animal.eat()은 다형성을 구현한 부분이다. animals 리스트에서 객체를 하나씩 불러와 eat() 메서드를 호출할 때, 메서드를 호출한 쪽에는 객체가 육식 동물인지 초식 동물인지 신경 쓸 필요가 없다. 각자의 객체는 자신의 클래스에 오버라이딩된 메서드를 호출한다. 실행 결과를 보면 각 객체가 자신이 가진 메서드를 호출하였음을 알 수 있다(결과가 모두 다르다).


    생각해보면 이 세상에 그냥 뭔가(something)를 먹는 동물은 없다. 고기를 먹는 동물이거나 풀을 먹는 동물이거나 풀과 고기를 모두 먹는 동물만 있을 뿐이다. 이런 생각에 도달한 클래스 설계자는 유저 프로그래머가 Animal 클래스의 인스턴스를 애초에 만들지 못하게 하고 싶을 수 있다. 이럴 때는 Animal 클래스를 추상 클래스(abstract class)로 만들면 된다.

     

    추상 클래스는 독자적으로 인스턴스를 만들 수 없고 함수의 몸체(body)가 없는 추상 메서드를 하나 이상 가지고 있어야 한다. 또한 추상 클래스를 상속받는 파생 클래스에서는 추상 메서드를 반드시 오버라이딩해야 한다. 그렇지 않으면 파생 클래스도 추상 클래스가 되어 인스턴스를 만들 수 없다.

    #code 3-1
    from abc import * #abc(abcsract base class의 약자)
    
    class Animal(metaclass = ABCMeta):
        @abstractmethod
        def eat(self):
            pass

    먼저 abc 모듈을 가져오고 class 선언을 해준다. 그런 다음 추상 메서드로 만들고 싶은 메서드 위에 데코레이터@abstractmethod를 붙여 준다. 메서드 구현부를 pass로 해 함수 몸체를 비워 두면 eat() 메서드는 추상 메서드가 된다. 앞으로 Animal 클래스를 상속받는 모든 파생 클래스는 내부에 eat() 메서드를 반드시 오버라이딩해야 한다.

    eat meat
    eat grass
    eat meat and grass
    >>> a = Animal()
    Traceback (most recent call last):
      File "<pyshell#1>", line 1, in <mondule>
        a = Animal()
    TypeError: Can't instantiate abstract class Animal with abstract methods eat

    Animal 클래스의 인스턴스를 만들려고 시도하면 오류가 난다.

    'python' 카테고리의 다른 글

    파이썬 - 람다 함수  (0) 2021.03.16
    파이썬 - 인수(argument)  (0) 2021.03.16
    클래스 - 클래스 관계  (0) 2021.03.11
    객체 지향 프로그래밍  (0) 2021.03.05
    파이썬 - 절차지향 프로그래밍  (0) 2021.03.04
Designed by Tistory.