프로그래밍/CS

[디자인패턴] Dependency Inversion (의존성 역전)

choar 2022. 7. 27. 14:22
반응형

Dependency Inversion

객체지향 설계 5원칙 중 하나인 SOLID 원칙의 D가 Dependency Inversion을 일컫는다.

Dependency Inversion을 포함해 모든 디자인 패턴의 핵심은 abstraction(추상화)이다.

이상적으로는 프로그래밍 언어가 추상화 메커니즘과 type을 모두 제공해야 한다.

파이썬에는 이 두 가지 모두 없다..!

그러나 여전히 Dependency Inversion을 구현할 수 있다.

Abstract base class(ABC)라는 모듈을 사용할 수 있고, type hint도 제공한다.

(⚠️ type hint는 언어 레벨에서 실질적으로 어떠한 제약 사항도 강요하지 않는다. 즉, type hint에 맞게 코드를 쓰지 않아도 에러를 발생시키지 않는다.)

 

Example

# BEFORE

class LightBulb:
    def turn_on(self):
        print("LightBulb: turned on...")

    def turn_off(self):
        print("LightBulb: turned off...")


class ElectricPowerSwitch:
    def __init__(self, l: LightBulb):
        self.lightBulb = l
        self.on = False

    def press(self):
        if self.on:
            self.lightBulb.turn_off()
            self.on = False
        else:
            self.lightBulb.turn_on()
            self.on = True

l = LightBulb()
switch = ElectricPowerSwitch(l)
switch.press()
switch.press()

 

첫째로 ABC를 상속받는 Switchable 클래스를 만든다.

abstract class의 인스턴스는 만들 수 없다.

from abc import ABC, abstractmethod

class Switchable(ABC):
    @abstractmethod
    def turn_on(self):
        pass

    @abstractmethod
    def turn_off(self):
        pass
        
s = Switchable() # 에러

 

LightBulb는 Switchable이라는 interface*를 implement*하는 클래스로 수정해준다.

 

* interface, implement : 인터페이스는 자신과 이를 구현하는 모든 클래스 간의 계약(contract)이다. 인터페이스에는 메서드, 프로퍼티 또는 이벤트가 있을 수 있다. 멤버 선언(declaration)만 포함하고 멤버 구현(implementation)은 인터페이스를 구현하는 클래스에서 제공한다.

class LightBulb(Switchable):
    def turn_on(self):
        print("LightBulb: turned on...")

    def turn_off(self):
        print("LightBulb: turned off...")

 

다음으로 ElectricPowerSwitch 클래스에서 전달받는 LightBulb의 타입을 Switchable로 변경해준다. (lightbulb → client)

class ElectricPowerSwitch:
    def __init__(self, c: Switchable):
        self.client = c
        self.on = False

    def press(self):
        if self.on:
            self.client.turn_off()
            self.on = False
        else:
            self.client.turn_on()
            self.on = True

이렇게 함으로써 LightBulb와 ElectricPowerSwitch 사이의 의존을 없앨 수 있다.

→ LightBulb 말고도 다른 Switchable을 구현한 클래스를 ElectricPowerSwitch에 연결할 수 있게 된다. (ex. Fan)

class Fan(Switchable):
    def turn_on(self):
        print("Fan: turned on...")

    def turn_off(self):
        print("Fan: turned off...")

 

수정 후 전체 코드는 다음과 같다.

# AFTER

from abc import ABC, abstractmethod

class Switchable(ABC):
    @abstractmethod
    def turn_on(self):
        pass

    @abstractmethod
    def turn_off(self):
        pass


class LightBulb(Switchable):
    def turn_on(self):
        print("LightBulb: turned on...")

    def turn_off(self):
        print("LightBulb: turned off...")


class Fan(Switchable):
    def turn_on(self):
        print("Fan: turned on...")

    def turn_off(self):
        print("Fan: turned off...")


class ElectricPowerSwitch:
    def __init__(self, c: Switchable):
        self.client = c
        self.on = False

    def press(self):
        if self.on:
            self.client.turn_off()
            self.on = False
        else:
            self.client.turn_on()
            self.on = True


l = LightBulb()
f = Fan()
switch = ElectricPowerSwitch(f)
switch.press()
switch.press()

 

Summary

직접적으로 coupled된 두 클래스를 Dependency Inversion을 통해 (예시의 경우 interface를 통해) decouple 할 수 있다.

loose coupling을 지향해야 하는 이유는 포스팅으로 작성한 바 있다.

 

 

[디자인패턴] Cohesion (응집도), Coupling (결합도)

최근 비전공자로서 부족한 부분을 채우고자 CS 공부의 일환으로 네트워크를 공부했다. (대학교 한 학기 수업 분량 수강 완료!) 그러다 프론트엔드 개발자라면 네트워크/운영체제는 알면 좋은 것,

choar816.tistory.com

 

🌟 내용에 오류가 있다면 댓글 달아주시면 감사하겠습니다.


References

https://www.youtube.com/watch?v=Kv5jhbSkqLE&list=PLC0nd42SBTaNuP4iB4L6SJlMaHE71FG6N&index=2

https://medium.com/@ubale.vikas9/interface-in-oops-6eae3731c242

반응형