제어 역전(Inversion of Control)이란 무엇일까요?

2024-07-27

제어 역전(Inversion of Control)이란 무엇일까요?

전통적인 프로그래밍 방식에서는 프로그램 코드가 직접 라이브러리나 프레임워크의 기능을 호출하여 사용합니다. 이 방식은 코드의 의존성이 높아지고 유지 관리가 어려워지는 단점이 있습니다.

제어 역전에서는 프로그램 코드가 직접 기능을 호출하는 대신, 외부 컨테이너가 프로그램 코드에 필요한 기능을 주입(injection)합니다. 이렇게 하면 프로그램 코드는 컨테이너에 의존하게 되고, 컨테이너는 필요에 따라 다양한 기능을 제공할 수 있습니다.

제어 역전의 장점은 다음과 같습니다.

  • 의존성 감소: 프로그램 코드는 컨테이너에만 의존하게 되고, 구체적인 기능 구현에 대한 의존성이 낮아집니다.
  • 유지 관리 용이: 코드의 변경 없이 컨테이너를 통해 새로운 기능을 추가하거나 기존 기능을 변경할 수 있습니다.
  • 테스트 용이: 컨테이너를 통해 모의 객체(mock object)를 주입하여 프로그램 코드를 쉽게 테스트할 수 있습니다.

제어 역전을 구현하는 대표적인 방법으로는 다음과 같은 것들이 있습니다.

  • 의존성 주입(Dependency Injection, DI): 컨테이너가 프로그램 코드에 필요한 객체를 생성하고 주입합니다.
  • 서비스 로케이터(Service Locator): 프로그램 코드가 필요한 서비스를 직접 찾는 것이 아니라, 서비스 로케이터를 통해 서비스를 찾습니다.



제어 역전(Inversion of Control) 예제 코드

의존성 주입(Dependency Injection) 예시

class Car:
    def __init__(self, engine):
        self.engine = engine

    def drive(self):
        self.engine.start()
        print("차가 운행 중입니다.")
        self.engine.stop()


class Engine:
    def start(self):
        print("엔진 시동")

    def stop(self):
        print("엔진 정지")


# 컨테이너
class Container:
    def __init__(self):
        self.engine = Engine()

    def get_car(self):
        return Car(self.engine)


# 컨테이너를 사용하여 Car 객체 생성
container = Container()
car = container.get_car()

# Car 객체 사용
car.drive()

설명:

  • Car 클래스는 Engine 클래스에 의존합니다.
  • Container 클래스는 Engine 객체를 생성하고 관리합니다.
  • get_car() 메서드는 Engine 객체를 사용하여 Car 객체를 생성합니다.
  • main() 함수는 Container 객체를 사용하여 Car 객체를 생성하고 drive() 메서드를 호출합니다.

서비스 로케이터(Service Locator) 예시

from abc import ABC, abstractmethod


class Engine(ABC):
    @abstractmethod
    def start(self):
        pass

    @abstractmethod
    def stop(self):
        pass


class Car:
    def __init__(self):
        self.engine = None

    def drive(self):
        self.engine.start()
        print("차가 운행 중입니다.")
        self.engine.stop()


class ServiceLocator:
    def __init__(self):
        self.services = {}

    def register_service(self, name, service):
        self.services[name] = service

    def get_service(self, name):
        return self.services[name]


# 엔진 구현
class ElectricEngine(Engine):
    def start(self):
        print("전기 엔진 시동")

    def stop(self):
        print("전기 엔진 정지")


class GasolineEngine(Engine):
    def start(self):
        print("휘발유 엔진 시동")

    def stop(self):
        print("휘발유 엔진 정지")


# 서비스 로케이터 설정
service_locator = ServiceLocator()
service_locator.register_service("electric_engine", ElectricEngine())
service_locator.register_service("gasoline_engine", GasolineEngine())


# Car 객체 생성 및 사용
car = Car()
car.engine = service_locator.get_service("electric_engine")
car.drive()

car.engine = service_locator.get_service("gasoline_engine")
car.drive()
  • Engine 클래스는 추상 기본 클래스이며 start()stop() 메서드를 추상 메서드로 정의합니다.
  • Car 클래스는 Engine 인터페이스를 구현하는 객체를 engine 속성으로 가지고 있습니다.
  • ServiceLocator 클래스는 서비스 이름과 서비스 객체를 매핑하는 딕셔너리를 관리합니다.
  • register_service() 메서드는 서비스 이름과 서비스 객체를 등록합니다.
  • get_service() 메서드는 서비스 이름을 사용하여 서비스 객체를 반환합니다.



제어 역전(IoC) 대체 방법

서비스 로케이터(Service Locator)

서비스 로케이터는 IoC를 구현하는 가장 간단한 방법 중 하나입니다. 서비스 로케이터는 서비스 이름과 서비스 객체를 매핑하는 딕셔너리 역할을 합니다. 프로그램 코드는 필요한 서비스를 직접 생성하지 않고, 서비스 로케이터를 통해 서비스 객체를 얻습니다.

장점:

  • 간단하고 구현하기 쉽습니다.
  • 코드의 의존성을 낮출 수 있습니다.

단점:

  • 서비스 로케이터 자체가 의존성이 되어 코드의 유연성을 떨어뜨릴 수 있습니다.
  • 서비스 로케이터에 대한 테스트가 어려울 수 있습니다.

의존성 주입(Dependency Injection)

의존성 주입(DI)은 IoC를 구현하는 또 다른 방법입니다. DI는 컨테이너가 프로그램 코드에 필요한 객체를 생성하고 주입하는 방식으로 작동합니다. 프로그램 코드는 컨테이너에 의존하며, 컨테이너는 필요에 따라 다양한 객체를 제공할 수 있습니다.

  • 서비스 로케이터보다 유연하고 확장 가능합니다.
  • 코드의 테스트가 용이합니다.
  • 컨테이너 구현이 복잡할 수 있습니다.
  • DI 프레임워크를 배우는 데 시간이 걸릴 수 있습니다.

팩토리 메서드(Factory Method)

팩토리 메서드는 객체 생성을 위한 코드를 추상화하는 디자인 패턴입니다. 프로그램 코드는 직접 객체를 생성하지 않고, 팩토리 메서드를 통해 객체를 생성합니다.

  • 객체 생성 코드를 추상화하여 코드의 재사용성을 높일 수 있습니다.
  • 객체 생성 과정을 변경하기 쉽습니다.
  • IoC 패턴만큼 강력하지는 않습니다.
  • 모든 상황에 적합한 것은 아닙니다.

생성자 주입(Constructor Injection)

생성자 주입은 DI의 한 형태로, 객체 생성 시 필요한 의존성을 생성자를 통해 주입하는 방식입니다.

  • 코드가 간결하고 명확해집니다.
  • 의존성을 명확하게 드러낼 수 있습니다.
  • 생성자의 역할이 복잡해질 수 있습니다.
  • 불변 객체(immutable object)에 의존성을 주입할 수 없습니다.

필드 주입(Field Injection)

필드 주입은 DI의 또 다른 형태로, 객체의 필드에 직접 의존성을 주입하는 방식입니다.

  • 생성자 주입보다 간단합니다.
  • 불변 객체에도 의존성을 주입할 수 있습니다.
  • 코드가 지저분해질 수 있습니다.
  • 테스트하기 어려울 수 있습니다.

세터 주입(Setter Injection)

  • 필드 주입보다 유연합니다.
  • 테스트하기 쉽습니다.
  • 코드가 조금 더 복잡해집니다.
  • 생성자 주입만큼 효율적이지 않습니다.

결론


oop design-patterns language-agnostic

oop design patterns language agnostic

MVP와 MVC 패턴 비교

MVC와 MVP 패턴은 각자 장단점을 가지고 있으며, 프로젝트의 특성에 따라 적합한 패턴을 선택해야 합니다.MVC는 다양한 프레임워크 지원, 유연한 디자인 등의 장점이 있지만, 뷰와 모델의 의존성이 높고 테스트가 어려울 수 있다는 단점이 있습니다