DINO NET

(23.02.06) 객체 지향 프로그래밍 기본 개념 4. 다형성(Polymorphism) 본문

program()/파이썬

(23.02.06) 객체 지향 프로그래밍 기본 개념 4. 다형성(Polymorphism)

2023. 2. 6. 23:27

다형성(Polymorphism)

하나의 변수가 서로 다른 클래스의 인스턴스들을 가리킬 수 있는 성질

* 각 클래스들은 동일한 이름의 메소드를 전부 포함해야 한다.

 

example)

from math import pi

class Rectangle:
    """직사각형 클래스"""
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        """직사각형의 넓이를 리턴"""
        return self.width * self.height
    
    def perimeter(self):
        """직사각형의 둘레를 리턴"""
        return 2*self.width + 2*self.height
    
    def __str__(self):
        """직사각형의 정보를 문자열로 리턴"""
        return "밑변 {}, 높이 {}인 직사각형".format(self.width, self.height)

class Circle:
    """원 클래스"""
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        """원의 넓이를 리턴"""
        return pi * self.radius * self.radius
    
    def perimeter(self):
        """원의 둘레를 리턴"""
        return 2 * pi * self.radius
    
    def __str__(self):
        """원의 정보를 문자열로 리턴"""
        return "반지름 {}인 원".format(self.radius)

class Paint:
    """그림판 프로그램 클래스"""
    def __init__(self):
        self.shapes = []
        
    def add_shape(self, shape):
        """그림판에 도형을 추가한다"""
        self.shapes.append(shape)
        
    def total_area_of_shapes(self):
        """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
        return sum([shape.area for shape in self.shapes])
        
    def total_perimeter_of_shapes(self):
        """그림판에 있는 모든 도형의 둘레의 합을 구한다"""
        return sum([shape.perimeter for shape in self.shapes])
    
    def __str__(self):
        """그림판에 있는 각 도형들의 정보를 출력한다"""
        res_str = "그림판 안에 있는 도형들:\n\n"
        for shape in self.shapes:
            res_str += str(shape) + "\n"
        return res_str
        

rectangle = Rectangle(3, 7)
circle = Circle(4)

paint_program = Paint()
paint_program.add_shape(rectangle)
paint_program.add_shape(circle)

print(paint_program.total_area_of_shapes())
print(paint_program.total_perimeter_of_shapes())
  • 클래스 Rectangle과 Circle은 공통적으로 area와 perimeter라는 메소드를 가지고 있다.
  • Paint 프로그램 속 shapes 배열에 circle과 rectangle을 추가하였다.
  • Rectangle과 Circle 모두 공통적인 메소드 area와 perimeter를 가지고 있기 때문에 shape.area for shape in self.shapes / shape.perimeter for shape in self.shapes 구문 속 shape에 rectangle이 들어가든, circle이 들어가든 오류가 발생하지 않는 것이다.

* list comprehension

[변수 / 변수를 이용한 값 for 변수 in 순회 가능한 값]

 

위 예제와 같이 클래스마다 공통되는 메소드를 넣어 사용할 수 있지만, 코드의 길이가 길어짐에 따라 클래스가 다양해지면 중복되는 양도 많아지고 오류도 발생할 뿐더러 유지 보수가 쉽지 않게 된다. 추상 클래스는 그런 단점들이 보완 가능하다.

 

추상 클래스

여러 클래스들의 공통점을 추상화해서 모아놓은 클래스

 

1. ABC, abstractmethod를 import 후 ABC 상속

ABC는 Abstract Base Class의 줄임말로 추상 클래스에 상속을 시킨다.

from abc import ABC, abstractmethod

class 추상_클래스(ABC):

 

2. 추상 메소드가 최소 1개 이상 포함 되어야 한다.

 

추상 메소드

자식 클래스가 반드시 오버라이딩 해야하는 메소드

추상 클래스를 자식 클래스들이 상속받는 점을 이용해 공통되는 메소드들을 추상 메소드로 만드는 것이다.

 

@abstractmethod
def 추상_메소드(self) -> int:
	#type hinting을 이용해 return값을 언질해주면 유용하다
	pass

 

추상 메소드의 내용 추가는 가능하다. (super()를 이용하면 되므로) 오버라이딩이 필수라는 것만 기억하면 된다.

 

3. 추상 클래스를 이용한 인스턴스 생성은 불가능하다.

4. 일반 메소드 또한 추가 가능하다. -> 일반 메소드의 경우에는 오버라이딩이 필수가 아니다.

5. getter, setter를 이용해 자식 class가 특정 변수를 가지도록 유도 가능하다. getter, setter를 이용해 오버라이딩을 강제 함으로써 변수를 취하게 하는 것이다.

 

위 예제를 추상 클래스를 이용하여 정리 하면 다음과 같다.

from math import pi
from abc import ABC, abstractmethod

class Shape(ABC):
    """도형 클래스"""
    @abstractmethod
    def area(self) -> float:
        """도형의 넓이 리턴"""
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        """도형의 둘레를 리턴"""
        pass
    

class Rectangle(Shape):
    """직사각형 클래스"""
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        """직사각형의 넓이를 리턴"""
        return self.width * self.height
    
    def perimeter(self):
        """직사각형의 둘레를 리턴"""
        return self.width*2 + self.height*2
    
    def __str__(self):
        """직사각형의 정보를 문자열로 리턴하는 메소드"""
        return "높이 {} 밑변 {}인 직사각형".format(self.height, self.width)


class Circle(Shape):
    """원 클래스"""
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        """원의 넓이를 리턴"""
        return pi * (self.radius**2)
    
    def perimeter(self):
        """원의 둘레를 리턴"""
        return 2 * pi * self.radius
    
    def __str__(self):
        """원의 정보를 문자열로 리턴하는 메소드"""
        return "반지름 {}인 원".format(self.radius)


class Paint:
    """그림판 프로그램 클래스"""
    def __init__(self):
        self.shapes = []
        
    def add_shape(self, shape):
        """그림판에 도형을 추가한다"""
        self.shapes.append(shape)
        
    def total_area_of_shapes(self):
        """그림판에 있는 모든 도형의 넓이의 합을 구한다"""
        return sum([shape.area for shape in self.shapes])
        
    def total_perimeter_of_shapes(self):
        """그림판에 있는 모든 도형의 둘레의 합을 구한다"""
        return sum([shape.perimeter for shape in self.shapes])
    
    def __str__(self):
        """그림판에 있는 각 도형들의 정보를 출력한다"""
        res_str = "그림판 안에 있는 도형들:\n\n"
        for shape in self.shapes:
            res_str += str(shape) + "\n"
        return res_str

 

* 추상 클래스의 다중 상속

일반 클래스는 모호함 때문에 다중 상속을 지양해야 하지만 추상 클래스는 자주 사용한다. 추상 메소드는 사용하려면 무조건 오버라이딩 해야하므로 상속 순서가 의미가 없기 때문이다.

 

 

0206