[python] 추상화, 인터페이스?

About_work·2024년 7월 3일
0

python 기초

목록 보기
59/65

1. 추상화? 인터페이스?

  • 추상화(Abstraction)와 인터페이스(Interface)는 객체 지향 프로그래밍(OOP)의 핵심 개념으로, 코드의 유연성과 재사용성을 높이기 위해 사용
  • 이 두 개념은 서로 밀접하게 연관되어 있지만, 각기 다른 역할과 목적을 가지고 있습니다.

1.1. 추상화(Abstraction)

  • 추상화는 복잡한 시스템에서 중요한 부분만을 추출하여 단순화하는 것을 의미
  • 추상화는 객체 지향 프로그래밍에서 클래스와 메서드를 통해 구현됩니다.
    • public과 private을 의미하는 것 같기도?
  • 이를 통해 세부 구현을 숨기고, 사용자에게 중요한 기능만을 노출합니다.

1.1.1. 예시

  • 추상화의 예로, 자동차를 생각해볼 수 있습니다.
  • 우리는 자동차의 내부 엔진 구조를 알 필요 없이, 운전대, 가속 페달, 브레이크 페달 등의 인터페이스를 통해 자동차를 조작할 수 있습니다.

코드 예시:

class Car:
    def start_engine(self):
        pass
    
    def stop_engine(self):
        pass
    
    def drive(self):
        pass

1.2. 인터페이스(Interface)

  • 인터페이스는 클래스가 구현해야 하는 메서드의 집합을 정의
  • 인터페이스는 구체적인 구현을 제공하지 않으며, 클래스가 특정 기능을 수행하도록 강제
  • 이는 코드의 일관성을 유지하고, 다양한 구현체를 쉽게 교체할 수 있게 함

1.2.1. 예시

  • 여러 종류의 결제 수단을 처리하는 시스템에서, 인터페이스를 사용하여 결제 수단의 공통 메서드를 정의할 수 있음
from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

1.3. 추상화와 인터페이스의 관계

  • 추상화와 인터페이스는 다음과 같은 방식으로 서로 관계를 맺고 있습니다:
  1. 추상 클래스와 인터페이스:

    • 추상 클래스는 추상화의 한 형태로, 공통된 기능을 가지는 여러 클래스의 기본 클래스로 사용
    • 추상 클래스는 일부 구현을 가질 수 있으며, 공통된 기능을 하위 클래스에서 재사용할 수 있음
    • 인터페이스는 구현되지 않은 메서드의 집합을 정의하여, 클래스가 반드시 구현해야 하는 기능을 명확히 합니다.
  2. 공통된 목적:

    • 추상화와 인터페이스 모두 코드의 재사용성과 확장성을 높이는 것을 목표로 합니다.
    • 이들은 코드의 중복을 줄이고, 다양한 구현체를 쉽게 교체할 수 있게 합니다.
  3. 구현 강제:

    • 인터페이스는 클래스가 특정 메서드를 반드시 구현하도록 강제
      • 이는 시스템의 일관성을 유지하고, 예상치 못한 오류를 방지합니다.
    • 추상 클래스는 하위 클래스가 구현해야 하는 공통된 기능을 정의하면서도, 일부 공통 기능을 직접 구현할 수 있게 합니다.

1.3.1. 통합 예시

from abc import ABC, abstractmethod

# 인터페이스 정의
class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

# 추상 클래스 정의
class OnlinePaymentProcessor(PaymentProcessor):
    def authenticate(self):
        print("Authenticating online payment")

# 구체 클래스 정의
class CreditCardProcessor(OnlinePaymentProcessor):
    def process_payment(self, amount):
        self.authenticate()
        print(f"Processing credit card payment of {amount}")

# 사용 예시
processor = CreditCardProcessor()
processor.process_payment(100)
  • 위 예시에서 PaymentProcessor인터페이스
    • 결제 프로세서가 반드시 구현해야 하는 메서드를 정의
  • OnlinePaymentProcessor추상 클래스
    • 공통된 인증 기능을 구현
    • ABC를 쓰더라도, 인터페이스가 아닌 추상 클래스를 만들 수 있다.!!
  • CreditCardProcessor구체 클래스: 실제 결제 처리 로직을 구현

1.4. 결론

  • 추상화와 인터페이스는 객체 지향 프로그래밍에서 중요한 개념으로, 서로 밀접하게 연관
  • 추상화는 복잡성을 줄이고 중요한 기능만을 노출하며,
  • 인터페이스는 특정 기능을 클래스가 반드시 구현하도록 강제
  • 이 둘을 함께 사용하면 코드의 유연성, 재사용성, 유지보수성을 크게 향상시킬 수 있음

2. 추상화를 써야할 때와 아닐때?

  • 파이썬에서 추상화를 사용하는 경우와 그렇지 않은 경우를 구분하여 설명하겠습니다. 이를 이해하기 쉽게 예시를 통해 설명드리겠습니다.

2.1. 추상화를 사용해야 하는 경우

  • 추상화는 코드의 유연성, 재사용성, 유지보수성을 높이기 위해 사용됩니다.
  • 특히, 여러 구현체가 동일한 인터페이스를 통해 교체 가능하거나, 특정 행동을 표준화할 필요가 있을 때 유용

2.1.1. 예시 1: 다양한 결제 수단 처리

  • 여러 가지 결제 수단을 처리하는 애플리케이션을 생각해봅시다.
  • 각 결제 수단은 서로 다른 로직을 가지고 있지만, 결제를 처리하는 동일한 인터페이스를 제공해야 합니다.
from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):
        print(f"Processing credit card payment of {amount}")

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):
        print(f"Processing PayPal payment of {amount}")

def process_transaction(processor: PaymentProcessor, amount):
    processor.process_payment(amount)

# 사용 예시
credit_card_processor = CreditCardProcessor()
paypal_processor = PayPalProcessor()

process_transaction(credit_card_processor, 100)
process_transaction(paypal_processor, 200)
  • 이 예제에서 PaymentProcessor는 추상 클래스이며, 모든 결제 프로세서는 이를 상속받아야 합니다.
    • ABC를 쓰더라도, 인터페이스가 아닌 추상 클래스를 만들 수 있다.!!
  • process_payment 메서드는 모든 하위 클래스에서 구현되어야 함.
  • 이렇게 하면 새로운 결제 수단이 추가되더라도 process_transaction 함수는 변경할 필요가 없습니다.

2.2. 추상화를 사용하지 않아도 되는 경우

  • 작고 단순한 프로젝트나, 특정 구현이 하나뿐인 경우에는 굳이 추상화를 사용할 필요가 없습니다.
  • 추상화가 오히려 불필요한 복잡성을 초래할 수 있습니다.

2.2.1. 예시 2: 간단한 로그 시스템

  • 작은 프로젝트에서 간단한 로그 기능이 필요하다고 가정해봅시다.
  • 이 경우, 추상화 없이 단일 클래스만으로도 충분히 구현할 수 있습니다.
class Logger:
    def log(self, message):
        print(f"Log: {message}")

# 사용 예시
logger = Logger()
logger.log("This is a log message.")

2.3. 추상화를 단계적으로 도입하는 경우

  • 초기 개발 단계에서는 추상화를 생략하고, 필요할 때 추상화를 도입하는 것이 좋습니다.
  • 이렇게 하면 코드의 복잡성을 줄이면서도 유연성을 확보할 수 있습니다.

2.3.1. 예시 3: 기능 확장 시 추상화 도입

  • 초기에는 간단한 이메일 알림 기능만 필요했지만, 나중에 SMS 알림 기능이 추가된다고 가정해봅시다.
# 초기 구현
class EmailNotifier:
    def send_notification(self, message):
        print(f"Sending email notification: {message}")

# 확장 후 추상화 도입
from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send_notification(self, message):
        pass

class EmailNotifier(Notifier):
    def send_notification(self, message):
        print(f"Sending email notification: {message}")

class SMSNotifier(Notifier):
    def send_notification(self, message):
        print(f"Sending SMS notification: {message}")

def notify_user(notifier: Notifier, message):
    notifier.send_notification(message)

# 사용 예시
email_notifier = EmailNotifier()
sms_notifier = SMSNotifier()

notify_user(email_notifier, "Hello via Email!")
notify_user(sms_notifier, "Hello via SMS!")
  • 초기에는 EmailNotifier 클래스만 존재했지만, 나중에 Notifier 추상 클래스를 도입하고 SMSNotifier를 추가하여 확장성을 높였습니다.

2.4. 결론

  • 파이썬에서 추상화는 필요에 따라 유연하게 도입하는 것이 좋습니다.
  • 초기에는 간단하게 시작하고, 기능이 확장되거나 복잡해질 때 추상화를 도입하여 코드의 유지보수성과 유연성을 높이는 것이 이상적

3. 인터페이스를 써야할 때와 아닐 때

3.1. 인터페이스를 사용해야 하는 경우

3.1.a. 여러 구현체가 동일한 기능을 수행해야 하는 경우

  • 여러 종류의 객체가 동일한 메서드를 통해 다양한 방식으로 동작해야 할 때, 인터페이스를 사용하면 유리
  • 예를 들어, 여러 결제 수단을 처리하는 시스템에서 각 결제 수단이 동일한 process_payment 메서드를 구현해야 할 때 인터페이스를 사용할 수 있음
from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):
        print(f"Processing credit card payment of {amount}")

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):
        print(f"Processing PayPal payment of {amount}")

def process_transaction(processor: PaymentProcessor, amount):
    processor.process_payment(amount)

# 사용 예시
credit_card_processor = CreditCardProcessor()
paypal_processor = PayPalProcessor()

process_transaction(credit_card_processor, 100)
process_transaction(paypal_processor, 200)

여기서 PaymentProcessor는 인터페이스 역할을 하며, CreditCardProcessorPayPalProcessor는 이를 구현합니다. process_transaction 함수는 어떤 결제 수단이 오더라도 동일한 방식으로 결제를 처리할 수 있습니다.

3.1.b. 시스템의 일관성을 유지해야 하는 경우

  • 시스템의 여러 부분에서 동일한 동작을 보장해야 할 때 인터페이스를 사용하면 좋습니다.
  • 예를 들어, 다양한 데이터 저장소를 사용하는 시스템에서 데이터 저장 및 로드 방식을 표준화하고자 할 때 인터페이스를 사용할 수 있습니다.
class Storage(ABC):
    @abstractmethod
    def save(self, data):
        pass

    @abstractmethod
    def load(self):
        pass

class FileStorage(Storage):
    def save(self, data):
        with open('data.txt', 'w') as file:
            file.write(data)
    
    def load(self):
        with open('data.txt', 'r') as file:
            return file.read()

class DatabaseStorage(Storage):
    def save(self, data):
        # 데이터베이스에 저장하는 로직
        pass
    
    def load(self):
        # 데이터베이스에서 로드하는 로직
        pass

def store_data(storage: Storage, data):
    storage.save(data)

# 사용 예시
file_storage = FileStorage()
store_data(file_storage, "Example data")

이 예제에서 Storage 인터페이스는 파일 저장소와 데이터베이스 저장소가 동일한 메서드를 구현하도록 강제합니다. 이를 통해 시스템의 일관성을 유지할 수 있습니다.

3.2. 인터페이스를 사용하지 않아도 되는 경우

3.2.a. 단일 구현체만 필요한 경우

  • 특정 기능에 대해 하나의 구현체만 필요한 경우 굳이 인터페이스를 사용할 필요가 없습니다. 예를 들어, 단일 파일 형식으로 로깅을 처리하는 경우입니다.
class Logger:
    def log(self, message):
        print(f"Log: {message}")

# 사용 예시
logger = Logger()
logger.log("This is a log message.")

여기서는 Logger 클래스 하나만으로 충분하며, 인터페이스를 사용하지 않아도 됩니다.

3.2.b. 간단한 스크립트나 작은 프로젝트

  • 작고 단순한 프로젝트에서는 인터페이스를 사용하지 않아도 됩니다. 코드의 복잡성을 줄이고 빠른 개발을 위해 인터페이스 없이 구현할 수 있습니다.
def calculate_sum(a, b):
    return a + b

# 사용 예시
result = calculate_sum(3, 4)
print(result)

이 예제에서는 간단한 덧셈 함수를 구현하는 데 인터페이스가 필요하지 않습니다.

3.3. 결론

  • 인터페이스는 여러 구현체가 동일한 기능을 수행해야 하거나 시스템의 일관성을 유지할 필요가 있을 때 유용합니다.
  • 반면, 단일 구현체만 필요한 경우나 작은 프로젝트에서는 굳이 인터페이스를 사용할 필요가 없습니다.
  • 필요에 따라 유연하게 인터페이스를 도입하는 것이 중요합니다.
profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글