본문 바로가기

컴퓨터공학

[Clean Architecture] - SOLID 원칙 OCP (2)

반응형

이번 장에서는 SOILD 원칙 2번째, OCP: 개방-폐쇄 원칙을 알아본다. 

 

SOLID 원칙 시리즈

해당 시리즈는 로버트 C.마틴의 Clean Architecture 책을 보며 쉬운 이해를 위해 ChatGPT와 함께 공부한 내용을 정리해 놓은 글입니다.

 

SRP: 단일 책임 원칙

OCP: 개방-폐쇄 원칙

LSP: 리스코프 치환 원칙

ISP: 인터페이스 분리 원칙

DIP: 의존성 역전 원칙


 

 

OCP: 개방-폐쇄 원칙

- 소프트웨어 개체의 행위는 확장할 수 있어야 하나, 개체를 변경해서는 안된다. 
- 살짝 확장하는데 소프트웨어를 엄청나게 수정해야 하는 비효율을 줄이자. 
 
하나의 소프트웨어가 여러 책임을 가지고 있다면, 이를 확실하게 조직화하는 과정이 필요하다. 아래 그림을 보면서 생각해보자. 

 
위의 조직도는 재무재표를 웹뷰 / PDF 뷰로 보여주는 시스템을 보여준다. 만약 웹뷰만 지원하는 소프트웨어에 PDF 프린터 뷰에 대한 코드를 추가하고자 할때, 위와 같이 Interactor와 Controller가 변경으로부터 보호받을 수 있는 구조여야 한다. 
 
아래의 과정을 체크하면서 조직도를 생각해보자. 

  1. 소프트웨어의 처리 과정을 클래스 단위로 분할한다.
  2. 클래스를 컴포넌트 단위로 구분한다.
    • 위의 예제에서는 Interactor, Controller, Presenter, View, 그리고 Database로 구분한다. 
  3. 컴포넌트 사이의 관계를 정의한다. 이는 오직 한 방향으로 이루어진다. 
    • 보호하려는 컴포넌트가 화살표를 받는다.
  4. Interactor는 애플리케이션의 가장 높은 수준의 정책을 포함한다. 가장 중요.
    • 화살표의 관계를 생각해보면, 어느 컴포넌트의 변경도 Interactor에 영향을 줄 수 없다. 
    • 컴포넌트의 계층구조를 조직화하여 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 지키자. 

 

OCP의 활용

이번에는 다른 예제를 살펴보자. 아래와 같이 커피를 만드는 CoffeMaker 클래스가 있다고 하자. 

class CoffeeMaker:
    def make_coffee(self, coffee_type):
        if coffee_type == "espresso":
            print("Making an espresso!")
        elif coffee_type == "latte":
            print("Making a latte!")
        else:
            print("Unknown coffee type!")

# 사용 예시
coffee_maker = CoffeeMaker()
coffee_maker.make_coffee("espresso")  # Making an espresso!
coffee_maker.make_coffee("latte")     # Making a latte!
coffee_maker.make_coffee("americano") # Unknown coffee type!

 
위의 코드는 새로운 커피를 추가할 때 마다 CoffeMaker 클래스 자체를 수정해야 한다. 
 
 
이번에는 인터페이스를 사용해서 새로운 커피 종류가 추가될 때 기존 코드를 수정하지 않도록 해보자.  

from abc import ABC, abstractmethod

# 1. 커피를 만드는 방법을 정의하는 추상 클래스
class Coffee(ABC):
    @abstractmethod
    def make(self):
        pass

# 2. Espresso 커피 만들기
class Espresso(Coffee):
    def make(self):
    	# Espresso 레시피
        print("Making an espresso!")

# 3. Latte 커피 만들기
class Latte(Coffee):
    def make(self):
    	# Latte 레시피
        print("Making a latte!")

# 4. CoffeeMaker는 커피 종류를 알 필요 없음
class CoffeeMaker:
    def make_coffee(self, coffee: Coffee):
        coffee.make()

# 사용 예시
coffee_maker = CoffeeMaker()

# 새로운 커피 종류가 추가되더라도 기존 코드는 수정할 필요가 없음
espresso = Espresso()
latte = Latte()

coffee_maker.make_coffee(espresso)  # Making an espresso!
coffee_maker.make_coffee(latte)     # Making a latte!

 
이 예제를 통해 OCP의 2가지 이점을 확인할 수 있다. 

  1. 방향성 제어: CoffeMaker는 Coffee 인터페이스만 알고 있고, 새로운 커피 클래스도 Coffee만 상속받아 구현하여 사용할 수 있으니, 방향성 제어가 가능하다.
  2. 정보 은닉: CoffeeMaker 클래스는 커피 종류가 어떻게 만들어지는지에 대해 알지 못하고, Coffee 인터페이스만 알고있다. 구체적인 커피 레시피는 각 커피 클래스에 숨겨져 있으므로, 정보 은닉의 목적으로 사용할 수 있다. 
반응형