프로그래밍 언어/Python

파이썬(PYTHON) 클린코드 #10_ SOLID, 의존성 역전 원칙(DIP)

Tigercow.Door 2020. 11. 10. 22:44
728x90


안녕하세요. 문범우입니다.

이번 포스팅에서는 SOLID 원칙 중 마지막 원칙인, 의존성 역전 원칙(Dependency Inversion Principle)에 대해서 알아보도록 하겠습니다.



1. DIP(Dependency Inversion Principle)


우선 의존성 역전 원칙(DIP)의 정의에 대해서 알아보자.

의존성 역전 원칙은, 추상화를 통해 세부 사항에 의존하지 않도록 해야 하지만, 반대로 세부 사항(구체적인 구현)은 추상화에 의존해야 한다는 원칙이다.


쉽게 말해서, 보다 고수준 모듈(클래스)은 보다 저수준 모듈(클래스)에 대해 의존하면 안된다는 것이고, 의존하지 않는다는 것은 저수준 모듈의 변경이나 추가 등에 있어서 변경점이 없어야 한다는 것이다.


파이썬이라는 언어의 특성상 융통성이 있으며, 동적 언어이기 때문에 예제들을 살펴볼때 이것이 왜 필요한지 의문이 들 수 있다. 이에 대해 본인이 참고하고 있는 "파이썬 클린코드"의 저자는 다음과 같이 이야기한다.


실제로 꼭 이렇게 할 필요는 없으며 프로그램은 똑같이 동작한다.

...

그러나 클린 디자인을 위해서 바람직하다. 이것이 이 책이 있는 이유 중 하나로 단지 파이썬이 너무 유연하여 자주 발생하는 실수를 줄이기 위함이다.


- 파이썬 클린코드


본인은 예제에서 assert를 이용해 조금 더 강제성을 부여하였다.

실제로 예제코드와 같은 상황은 드물겠지만, DIP를 조금 더 잘 느끼기 위함으로 이해해주기를 바란다.

그럼 바로 예제를 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# DIP를 지키지 못한 예제
from abc import *
 
class EventStreamer():
    def __init__(self, parsed_data: str, client: Syslog):
        self.parsed_data = parsed_data
        assert client is Syslog, "Client is not Syslog"
        self.client = client
        
    def stream(self):
        self.client.send(self.parsed_data)    
        
class Syslog():
    def send(data: str):
        print(f"Syslog send: {data}")
        pass
    
class OtherClient():
    def send(data: str):
        print(f"OtherClient send: {data}")
        pass
 
 
streamer1 = EventStreamer("for Syslog data!", Syslog)
streamer1.stream()
streamer2 = EventStreamer("for OtherClient data!", OtherClient)
streamer2.stream()
cs

code: https://github.com/doorBW/python_clean_code


위의 예제를 살펴보면, EventStreamer() 라는 고수준의 모듈이 직접 Syslog를 참조함으로써, Syslog에 대해 의존하고 있다.

위의 코드를 실행시켜보면 assert에 다음과 같이 에러가 발생한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Syslog send: for Syslog data!
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-46-d6c168be4001> in <module>()
     24 streamer1 = EventStreamer("for Syslog data!", Syslog)
     25 streamer1.stream()
---> 26 streamer2 = EventStreamer("for OtherClient data!", OtherClient)
     27 streamer2.stream()
 
<ipython-input-46-d6c168be4001> in __init__(self, parsed_data, client)
      5     def __init__(self, parsed_data: str, client: Syslog):
      6         self.parsed_data = parsed_data
----> 7         assert client is Syslog, "Client is not Syslog"
      8         self.client = client
      9 
 
AssertionError: Client is not Syslog
cs

code: https://github.com/doorBW/python_clean_code


물론 위의 에러는 본인이 assert를 통해 강제성을 부여하였기에 발생된 에러지만, 실제로 Java와 같은 정적 언어에서는 타입이 지정되기 때문에 에러가 발생할 것이다.

또한 예제이기 때문에 각 함수들에 대해 구체적인 행동을 만들어주지 않았지만, 만약 Syslog의 send() 함수가 변경되었을 때에는 EventStreamer 클래스도 변경해야 할 가능성이 생긴다.


위와 같은 상황에서 Syslog 클래스와 OtherClient 클래스를 추상화하는 DataTargerClient 인터페이스를 만들어 줌으로써 DIP를 만족시킬 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# DIP를 적용한 예제
from abc import *
 
class EventStreamer():
    def __init__(self, parsed_data: str, client):
        self.parsed_data = parsed_data
        assert client in DataTargetClient.__subclasses__(), "Client is not DataTargetClient"
        self.client = client
        
    def stream(self):
        self.client.send(self.parsed_data)
 
class DataTargetClient(metaclass=ABCMeta):
    """Interface: DataTargetClient class"""
    @abstractmethod
    def send(self, data: str):
        pass            
        
class Syslog(DataTargetClient):
    def send(data: str):
        print(f"Syslog send: {data}")
        pass
    
class OtherClient(DataTargetClient):
    def send(data: str):
        print(f"OtherClient send: {data}")
        pass
 
 
streamer1 = EventStreamer("for Syslog data!", Syslog)
streamer1.stream()
streamer2 = EventStreamer("for OtherClient data!", OtherClient)
streamer2.stream()
cs

code: https://github.com/doorBW/python_clean_code


위와 같이 기존의 Syslog 클래스와 OtherClient 클래스가 DataTargetClient를 구현하고 있으며, EventStreamer 클래스는 단지 DataTargetClient 인터페이스와 관계를 가질뿐, 실제 구체적인 구현과는 의존성이 사라지게 된다.

위의 코드를 실행시키면 다음과 같이 잘 동작함을 볼 수 있다.


1
2
Syslog send: for Syslog data!
OtherClient send: for OtherClient data!
cs

code: https://github.com/doorBW/python_clean_code