객체지향 프로그래밍에서 클래스를 정의하는 중요하게 적용되는 특징 4가지가 있다. 캡슐화(Encapsulation), 정보은닉(Information Hiding), 상속(Inheritance), 그리고 다형성(Polymorphism) 이다.

 

이번 장에서는 세번째 특징인 상속 개념과 상속을 기반으로 메소드 재정의(Method Overriding), 다중 상속, 추상클래스와 인터페이스의 확장 개념을 이해한다.

상속(Inheritance)의 개념

객체지향에서의 상속은 일반적으로 아래와 같은 정의로 설명이 된다. 
상속 정의 1. Inheritance is a mechanism that permits a class to acquire the properties and behavior of another class[1]. 즉, ­상속이란 한 클래스가 다른 클래스의 속성과 동작을 가질 수 있도록 하는 메커니즘으로 정의한다. 지금까지 클래스 하나에 대한 특징을 봤다면, 상속은 두개의 클래스간의 관계를 설명하는데 하나의 클래스의 속성과 동작을 다른 클래스에서 재사용 하는 관계를 의미한다. 
상속 정의 2. Inheritance is a relationship that defines one class in terms of another. It is the mechanism that permits the incorporation of the structure and behavior of a parent class into a child class.[2] 이 정의에서 ­상속은 한 클래스를 다른 클래스를 기반으로 정의하는 관계이며, 부모 클래스의 구조와 동작을 자식 클래스에 통합할 수 있게 하는 메커니즘으로 설명된다. 정의 1에서 두 클래스 간의 재상용 관계를 얘기했다면, 정의 2에서는 두 클래스 간의 의미를 parent class와 child 클래스의 관계로 상세화하여 Parent 클래스의 속성과 기능을 자식 클래스에서 재사용 하는 것으로 이해할 수 있다. 
상속 정의 3. Inheritance is the ability for a class to define a generalization-specialization relationship, by reusing and refining the features of a parent class.[3]  상속은 부모 클래스의 기능을 재사용하고 다듬으면서 일반화-특수화 관계를 정의하게 한다. 라고 설명한다. 정의 2에서 Parent 클래스와 Child 클래스를 정의 했다면 정의 3에서는 Parent-Child 클래스의 관계가 Parent 클래스는 Child 클래스보다 일반화된 클래스, Child 클래스는 Parent 클래스에서 특수화 된 클래스로 설명한다. 
이 정의들을 정리하면, 
  1. Child class는 Parent class의 속성과 메서드를 가진다.
  2. Parent class와 Child class의 관계는 일반화(generalization) – 특수화(specialization)의 관계로 아래와 같이 설명 할 수 있다. 
    • ­Child class is a Parent class.
    • ­Parent class의 속성과 메서드를 child에 맞게 재정의할 수 있다.

다음은 정의에서 설명한 상속관계를 UML의 클래스 다이어그램으로 나타낸 예 이다. 일반적으로 포유류 중에 고양이, 개, 원숭이... 등 다양한 종류가 있다. 여기서 포유류와 고양이의 관계를 생각하면, 포유류에서 고양이로 갈 수록 특수한 형태, Specialized 된 형태  중 한 종류가 Cat 이다. 그리고, 고양이, 개, 원숭이 등 다양한 종류를 한 단귀 상위로 추상화 하면 포유류 라는 개념이 생긴다. 이를 표현한 관계가 아래 그림의 오른쪽 클래스 다이어그램의 관계이다. 

 

 

[참고] 클래스 다이어그램 읽기

UML의 클래스 다이어그램은 7주차에 객체지향 설계를 학습하면서 조금더 자세히 정리할 예정이다. 여기에서는 지금까지 이해한 개념을 표현하는 정도만 UML 클래스 다이어그램의 표현을 정리합니다. 

 

1. 클래스 표현의 종류 - 캡슐화 과정을 통해 도출된 클래스는 아래와 같이 최소화된 형태부터 상세한 정보를 표현하는 확장된 형태로 표현할 수 있다. 핵심 정보는 클래스 명, 속성명, 메소드명, 속성과 메소드의 접근지정자 형태, type, 메소드의 입력 파라메터를 표현할 수 있다. 

 

2. 상속관계의 표현 - 클래스 간의 관계 중 상속관계는 속이 비어있는 삼각형에 직선으로 관계를 표시한다. 아래 그림의 가운데와 우측을 보면, Mammal 클래스와 Cat 클래스는 상속관계로 정의되는데 삼각형이 가리키는 방향이 Parent 클래스 직선에 연결된 클래스가 Child 클래스이다. 우측 그림에서는 name이라는 속성앞에는 #을, speak( )이라는 오퍼레이션에는 +를 붙였다. +는 public으로 Mammal 클래스 외부에서 해당 오퍼레이션을 호출할 수 있다는 뜻이고, #은 protected로 Mammal을 상속받는 클래스, 그림에서는 Cat 클래스 에서만 사용할 수 있다는 뜻이다. 

 

 

상속 개념 1. Child class는 Parent class의 속성과 오퍼레이션(메서드)를 가진다.

 

자식 클래스는 부모 클래스의 속성과 오퍼레이션을 가진다는 표현을 부모 클래스로부터 속성과 오퍼레이션을 "상속받는다" 라고 다시 표현 할 수 있다. 이 "상속받는다" 라는 개념을  Mammal 클래스와 Cat 클래스의 관계로 다음과 같이 모델(클래스 다이어그램)과 코드 나타낼 수 있다. 

 

개념 모델 (클래스 다이어그램)과 코드 

부모 클래스인 Mammal 클래스와 자식 클래스인 Cat 클래스가 상속관계에 있고, 부모클래스인 Mammal 클래스는 name 속성을 가지고, speak( ) 이라는 오퍼레이션(메소드)을 가진다. 그리고 Cat 클래스는 이 name 속성과 speak( ) 오퍼레이션을 상속받는다. (= 가진다. 재사용한다.)

이 모델을 파이썬 코드로 보면, 아래와 같이 Mammal 클래스와 Cat 클래스로 정의된다. 그리고 9라인의 Cat 클래스의 정의에서 class Cat(Mammal): 로 Mammal 클래스를 상속받는 Cat 클래스를 정의한다. 이는 Cat 클래스에서 Mammal 클래스의 속성과 오퍼레이션을 상속받는다는 뜻이다.

 

Mammal 클래스는 _name이라는 속성을 정의하고 생성자에서 이름을 파라메터로 받아 초기화 한다. 그리고 speak( )에서는 자기 인스턴스의 name을  "I am an aninmal. My name is ... "의 형태로 출력한다. 반면, Cat 클래스에서는 상속 외에 다른 코드를 정의 하지 않았다. 

 

그리고, 12번 라인에서는 Cat 클래스 타입의 인스턴스를 "Kitty"라는 입력값(_name에 저장)을 파라메터로 생성하여 cat 변수에 인스턴스를 넣고, 13번 라인에서 cat.speak( )을 실행시킨다. 그러면, Cat 타입의 cat 인스턴스는 Mammal 클래스로부터 상속받은 _name 속성에 "Kitty"를 할당하고, cat.speak()을 실행했을때 Mammal 클래스의 speak( ) 오퍼레이션이 해당 인스턴스의 _name 속성값과 함께 실행된다.   

class Mammal():
    def __init__(self, name):
        self._name = name
    
    def speak(self):
        return f"I am an animal. My name is {self._name}!"


class Cat(Mammal):
    pass

cat = Cat("Kitty")
print(f"cat.speak() -> {cat.speak()}")

 

즉, "Child class는 Parent class의 속성과 오퍼레이션(메서드)를 가진다." 라는 뜻은 "부모 클래스에서 정의한 속성와 오퍼레이션을 자식클래스에서 다시 정의하지 않아도 똑같이 사용할 수 있다" 라는 의미이다. 

 

상속개념 2. 일반화(generalization) – 특수화(specialization)    

상속의 구현 측면에서의 의미를 이해했다면, 이제는 두 클래스간의 상속관계로 정할때 두 클래스의 의미적 관계를 이해해야 한다. 여기서 언급되는 개념이 일반화-특수화의 관계 다른 표현으로 Is-A의 관계이다. 
 
“Is-a” 관계 (Child is a Parent ) 
위 그림을 기반으로 아래 문장을 도출 할 수 있다.
  • ­Cat is a Mammal.
  • Truck is a Vehicle.
  • Circle is a Shape.
­고양이는 포유류의 한 종류이다. 트럭은 차량의 한 종류이다. 원은 모양의 한 종류이다. 이 관계에서는 ­부모 클래스는 아래 레벨의 자식 클래스들을 일반화하는 형태이고, 자식 클래스는 부모 클래의 종류를 특수화 또는 상세화된 형태가 된다. 

 

이 관계를 코드로 표현하면 아래와 같다.  1~6라인까지는 위에서 언급한 코드와 내용이 같다. 그러나 8라인 부터는 Cat 클래스에 speak( ) 이라는 오퍼레이션을 다시 정의 했다. 이렇게 해서 Cat 클래스는 클래스 명으로도 Mammal클래스의 specialized 된 하위 클래스 이고, 클래스의 정의 측면에서도 _name이라는 속성과 speak( )이라는 오퍼레이션을 상속받고, 특히 speak() 오퍼레이션은 Cat 클래스에 맞게 재정의하여 상세화 한다. 

class Mammal(): 
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"
    

cat = Cat("Kitty")
print(f"cat.speak() -> {cat.speak()}")

 

이해를 돕기 위해 하나의 예를 더 추가하자면, 위의 "Cat is a Mammal." 에서 "Dog is a Mammal."로 Mammal 클래스에서 상속받는 Dog 클래스를 아래와 같이 정의 할 수 있다. 

 

이를 코드로 구현하면 12번 라인과 같이 Dog 클래스가 추가된다. 그리고 Dog 클래스의 speak() 이라는 오퍼레이션을 Mammal로부터 상속 받았고 그 내부를 "return f"{self._name} says Woof!" 와 같이 재정의 할 수 있다. 

class Mammal(): 
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"
    
class Dog(Mammal):
    def speak(self):
        return f"{self._name} says Woof!"
    

cat = Cat("Kitty")
dog = Dog("Buddy")
print(f"cat.speak() -> {cat.speak()}")
print(f"dog.speak() -> {dog.speak()}")

 

이와 같이, 자식 클래스는 부모 클래스의 특수화된 또는 상세화된 형태의 클래스이고, 이를 구현하기 위해 오퍼레이션의 body가 재정의 될 수 있다. 이는 메소드 오버라이딩(Method Overriding)이라고도 하며 아래의 확장 개념에서 다시 설명한다. 

 

추가적으로 메소드 오버라이딩과 함께, 자식 클래스는 부모클래스를 상속받은 후 새로운 속성과 오퍼레이션을 추가 할 수 있다. 그러나 부모클래스에서 정의된 속성과 오퍼레이션을 삭제 할 수는 없다. 이는 Is-A 관계를 훼손하기 때문이다. 

 

[확장 개념]  ­LSP (Liskov Substitution Principle) 

객체지향 설계원칙 중 하나 (SOLID) 중 하나인 LSP는 " 자식 클래스는 부모 클래스를 대체할 수 있어야 한다." 는 뜻이다. Is-a 관계가 만족하면 자식클래스는 부모클래스의 한 예로 대체가능하다. 즉, Cat은 하나의 포유류로 포유류의 다양 의미를 모두 만족시켜 대체 가능 하다. 
SOLID 원칙
원칙핵심  키워드
SRP 책임 분리
OCP 확장 가능 구조
LSP 대체 가능성
ISP 인터페이스 분리
DIP 추상에 의존

 

상속 관계에서 인스턴스 생성 순서

클래스의 상속관계와 그 구현에 대해 알아봤으면 실제로 실행되는 매커니즘을 이해하기 위해 객체(인스턴스)가 생성되는 순서를 살펴보려한다. 부모 클래스와 자식 클래스가 상속 관계일 때, 생성자(__init__)를 부모클래스에서만 정의하는지, 자식클래스에서도 정의하는지에 따라 실행 흐름이 달라진다.

 

1. 자식 클래스에 생성자가 없는 경우 

예를 들어 Dog 클래스가 Mammal을 상속받았지만, Dog에 별도의 __init__()가 없다면 어떻게 될까? 이 경우, 9라인과 같이 Dog 인스턴스를 생성하면, Parent 클래스인 Mammal의 __init__()가 자동으로 실행된다. 즉, 자식 클래스에 생성자가 없으면 부모 생성자를 그대로 사용한다.

class Mammal():
    def __init__(self, name):
        self._name = name
        print(f"{self._name} is a mammal.")        

class Dog(Mammal):
    pass

dog = Dog("Buddy")
 
 

 

2. 자식 클래스에 생성자가 있는 경우

아래 코드의 Cat 클래스처럼 자식 클래스에 __init__()가 정의되어 있다면 실행 순서는 다음과 같다.

1) 21번 라인 Cat("Kitty") 호출

2) 10번 라인 Cat.__init__() 실행
    11번 라인 print

3) 12번 라인 super().__init__(name) 호출 ,
     여기서 super()는 해당 클래스의 부모클래스를 리턴한다.
      즉 Mammal 클래스의 __inti__()을 호출한다. 
      [중요] super().__init__()을 하지 않으면
       Mammal 클래스의 생성자 호출을 하지 않는다
.

4) 2번 라인 Mammal.__init__() 실행
    3번 라인 print
    4번 라인 Mammal의 _name 속성 초기화 완료 후 반환
   12번 라인으로 돌아와 Cat의 __inti__ 완료 후 반환

즉, 자식 생성자 → 부모 생성자 → 자식 생성자 종료 의 순서로 실행된다.

 

예제 코드

class Mammal():
    def __init__(self, name):
        print("Here is Mammal.__init__()")
        self._name = name
        
    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def __init__(self, name):
        print("Here is Cat.__init__()")
        super().__init__(name)

    def speak(self):
        return f"{self._name} says Meow!"

class Dog(Mammal):
    def speak(self):
        return f"{self._name} says Woof!"

cat = Cat("Kitty")
dog = Dog("Buddy")
 
 

상속의 개념의 확장

상속의 기본 개념을 이해했다면, 이제 그 확장 개념을 살펴보자.

1. 메서드 오버라이딩 (Method Overriding)

메서드 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 다시 정의하여 자식 클래스에 맞게 동작을 재정의하는 것이다. 

아래의 예는 Mammal 클래스에서 speak( )을 정의하고, 이를 상속받는 Cat, Dog, Tiger 클래스에서 speak() 오퍼레이션을 메서드 오버라이딩, 즉 재정의 하는 모델이다. 

이를 코드로 표현아면 아래와 같다. 아래의 코드에서 24,25, 218번 라인의  speak( )을 각각 실행하면 그 결과가 어떻게 다른가 비교 해 보자. Cat 클래스의 speak( )은 Mammal 클래스의 speak()을 재정의 한 경우이다. Dog클래스의 speak은 Mammal 클래스의 speak()을 재정의하였으나 그 내부는 구현하지 않는것으로 재정의 하였다. 마지막으로 Tiger 클래스는 내부를 구현하지 않고 상속만 받은 경우이다. 
class Mammal():
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"


class Dog(Mammal):
    def speak(self):
        pass
    
class Tiger(Mammal):
    pass

cat = Cat("Kitty")
dog = Dog("Buddy")
tiger = Tiger("Tony")

print(cat.speak())
print(dog.speak())
print(tiger.speak())
 
이 코드를 실행시켜보면 그 결과는 아래와 같다. 
Kitty says Meow!
None
I am a Mammal. My name is Tony!
 
따라서1) Child 클래스에서 메소드 오버라이딩을 했으면, 그 메소드 실행하고 2) ­Child overriding 없으면, Parent class 메소드 실행 하는 것으로 정리해 볼 수 있다. 
 
 

2. 다중 상속 (Multiple Inheritance)

클래스간의 상속관계에서 2개 이상의 부모 클래스로부터 상속받는 관계를 다중상속이라 한다.  아래 모델의 우측 다중상속의 경우를 보면, 자식 클래스인 Cat 클래스는 Mammal 클래스의 특성과 Pet 클래스의 특성을 상속 받았다. 따라서 Cat 클래스는 포유류의 특성과 애완동물이 갖는 특성을 모두 갖고 있는것으로 이해할 수 있다.  

 

 
class Mammal:
    def __init__(self, name):
        self._name = name
    def move(self):
        return f"{self._name}가 움직인다."

class Pet:
    def __init__(self, owner):
        self._owner = owner
    def show_owner(self):
        return f"주인은 {self._owner}입니다."

class Cat(Mammal, Pet): #다중 상속 - Mammal과 Pet의 기능을 모두 갖는 Cat 클래스
    def __init__(self, name, owner):
        Mammal.__init__(self, name) 
        Pet.__init__(self, owner) 

    def speak(self):
        return f"{self._name} says Meow!"   

cat = Cat("Cookie", "Alice")
print(cat.speak()) 
print(cat.move()) 
print(cat.show_owner())
 
 
Parent Class가 같은 이름의 메소드를 가질 때
다중상속에서 한가지 고려해야 할 상황은 상속 받는 두 부모 클래스가 같은 이름의 오퍼레이션을 정의한 상황이다. 아래 모델에서와 같이 Mammal 클래스와 Pet 클래스가 move()오퍼레이션을 각각 정의했다면, Cat 클래스의 인스턴스 cat 에서 cat.move()를 호출했을 때, Mammal 클래스의 move()가 실행될까? 아니면 Pet 클래스의 move() 가 실행될까?
 

위 모델의 내용을 파이썬 코드로 구현하면 아래와 같다. 

class Mammal:
    def __init__(self, name):
        self._name = name
    
    def move(self):
        return f"{self._name}가 움직인다."
    
class Pet:
    def __init__(self, owner):
        self._owner = owner
    
    def move(self):
        return f"{self._owner}와 함께 움직인다."
    
class Cat(Pet, Mammal): 
    def __init__(self, name, owner):
        Mammal.__init__(self, name)
        Pet.__init__(self, owner)
    
    def speak(self):
        return f"{self._name} says Meow!"

cat = Cat("Cookie", "Alice")
print(cat.move())
 
 
이 코드를 실행시켜 24번 라인의 cat.move()의 결과를 보면 아래와 같다. 
Alice와 함께 움직인다.
 
 
즉, Mammal클래스의 move()가 아닌 Pet 클래스의 move( )가 실행되었다. 이유는 15번 라인의 Cat 클래스 정의시 상속받는 부모클래스의 순서에 있다. 
class Cat(Pet, Mammal):
 
 Pet, Mammal 순으로 상속의 순서가 정해져서 Pet클래스의 move()가  실행되는 것이다. 즉, 부모 클래스들이 같은 이름의 메서드를 가지고 있다면, 상속받은 순서대로 먼저 정의된 클래스의 메서드를 사용한다.
 
그러면 Mammal클래스의 move( )를 수행하려면 어떻게 할까? 
 
1. 상속의 순서를 아래와 같이 변경할 수 있다. 
class Cat(Mammal, Pet): # 순서가 Mammal, Pet으로 바뀜
 
 
2. 또는 Cat 클래스에서 move()를 재정의 한 후 Mammal 클래스의 move()를 직접 지정해 줄 수 있다. 
class Cat(Pet, Mammal):
    def __init__(self, name, owner):
        Mammal.__init__(self, name)
        Pet.__init__(self, owner)

    def move(self):
        return Mammal.move(self)  # 직접 지정
 
 
 이와 같이 다중 상속의 오퍼레이션 충돌의 경우 파이썬은 상속 순서대로 우선순위가 있다. 그러나 C++과 JAVA의 경우 그 대처방안은 아래와 같이 다르다. 
  • C++: 다중 상속을 허용하지만 동일한 메서드가 있으면 자동 선택하지 않고 개발자가 명시적으로 지정
  • Java: 클래스 다중 상속을 금지하여 구조적으로 충돌을 원천 차단하고, 인터페이스 충돌은 반드시 오버라이딩으로 해결.
 

3. 추상 클래스와 인터페이스

상속은 단순 재사용을 넘어 추상화(Abstraction) 개념과 연결된다. 추상화(Abstraction)이란 " 어떤 대상에서 핵심적인 부분만을 추출하고, 불필요한 세부 사항을 감추는 과정" 을 의미한다.  상속관게에서 추상화 했다는 것은 자식클래스의 특성들을 한단계 상위 레벨로 추상화 하여 부모클래스를 정의한 것이다. 즉, 부모클래스는 추상화 수준이 높고, 자식 클래스는 추상화 된 부모클래스를 한단계 상세화 한다. 이 상세화 하는 과정에서 추상 클래스와 인터페이스 라는 개념이 확장된다. 

  • 추상 클래스 – 기능의 일부만 구현하고 나머지는 미구현 한 "미완성 클래스"
  • 인터페이스 - 메서드 Prototype(이름, 파라메터)만 정의하고,  로직은 상속받는 클래스에서 구현 - 

 

3.1 추상 클래스 (Abstract Class)

아래 모델은 Mammal 클래스의 speak( )은 API만 정의하고 그 구현은 자식 클래스에서 추가된다. 하지만 Mammal 클래스의 run() 은 그 내부 구현을 가지고 있다. 즉, 부모클래스에서 구현이 명확한 것은 추가하고, 자식 클래스에 따라 구현이 달라질 가능성이 있는 오퍼레이션은 함수의 prototype (return type, 함수명, 파라메터) 만 정의한한 클래스를 추상 클래스라 한다. 

 

 

이 모델을 파이썬으로 구현하면 아래와 같다. 파이썬에서 추상 클래스는 abc 모듈을 사용하여 추상 클래스를 구현한다. 따라서 1번라인과 같이 abc 표준 라이브러리에서 ABC 추상 클래의 베이스 클래스와 abstractmethod 데코레이터 함수를 import 받는다.  2번 라인과 같이 Mammal 클래스를 ABC 클래스로부터 상속받아 추상클래스를 정의한다. 그리고 speak()의 경우 Mammal 클래스에서는 내부를 구현하지 않고, Cat 클래스에서 구현할 예정이어서 Mammal 클래스에서는 @abstractmethod 데코레이터로 abstract method 임을 표현한다. 이경우 상속받는 Dog, Cat 클래스에서 내부 구현을 하지 않으면 인스턴스 생성시 에러가 발생한다. 

from abc import ABC, abstractmethod

class Mammal(ABC):
    def __init__(self, name):
        self._name = name

    def run(self):
        print(f"{self._name} is runing!!")

    @abstractmethod 
    def speak(self):
        pass

class Dog(Mammal):
    def speak(self):
        print(f"{self._name} says woof!!")

class Cat(Mammal):
    def speak(self):
        print(f"{self._name} says meow!!")


# 객체 생성
dog = Dog("바둑이")
cat = Cat("나비")

dog.speak() 
cat.speak()
dog.run() 
cat.run()
 
 

 

추상클래스의 실제 사용의 예는 아래와 같다. 쇼핑몰 시스템에서 결제 기능은 결제 방식에 따라 달라지지만 결제 정보를 출력하는 기능은 데이타가 같다. 따라서 Payement 클래스를 추상클래스로 정의하여 issu_receipt()기능은 Payment 클래스에서 구현하고, 실제로 pay() 하는 기능은 결제 방식에 따라 자식 클래스에서 구현할 수 있다.  

 

 

3.2 인터페이스 (Interface)

상속관계에서 인터페이스란 부모클래스에서는 기능이 무엇인지 즉, 메서드 Prototype(이름, 파라메터)만 정의하고, 내부 구현은 상속받는 클래스에서 구현하는 경우이다.  즉 추상클래스에서 내부 구현을 하나도 하지 않은 클래스 이다. 

 

아래는 특정 이벤트가 발생했을때 Notification 을 주는 Notifier 클래스의 예이다. 예를 들어 도서관 시스템에서 대출한 도서에 대한 반납일이 도래했을 경우 notification을 주는 기능이 자동 호출 된다면 아래 모델의 부모 클래스인 Notifier 클래스의 notify() 함수를 caller 모듈에서 호출 할 것이다. 그러나 실제로는 생성한 클래스 타입에 따라 EmailNotifier클래스와 SMSNotifier 클래스의 notify()오퍼레이션이 실행된다. 

 

 

 

모든 메서드를 @abstractmethod로 선언하면 인터페이스처럼 사용할 수 있다.

from abc import ABC, abstractmethod


class Notifier(ABC):

    @abstractmethod
    def notify(self, message):
        pass


class EmailNotifier(Notifier):
    def notify(self, message):
        print(f"Email sent: {message}")


class SMSNotifier(Notifier):
    def notify(self, message):
        print(f"SMS sent: {message}")

email = EmailNotifier()
sms = SMSNotifier()

email.notify("Hello World") // 실제로는 email 보내는 로직이 들어가게 된다. 
sms.notify("Hello World")   // 실제로는 sms를 보내는 로직이 들어가게 된다.
 
 
 

 

 

 

+ Recent posts