본문 바로가기

컴퓨터공학

[Clean Architecture] - SOLID 원칙 SRP (1)

해당 시리즈는 로버트 C.마틴의 Clean Architecture 책을 보며 공부한 내용을 정리해 놓은 글이다. 대부분의 파이썬 예제들은 ChatGPT와 함께 공부하며 정리하였다. 

 

오늘은 좋은 아키텍쳐를 정의하는 SOLID 원칙 중 SRP에 대해 다룬다. SOLID 원칙에 대한 다른 포스팅들은 아래 링크에서 확인할 수 있다. 

 

SOLID 원칙 시리즈

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

 

SRP: 단일 책임 원칙

OCP: 개방-폐쇄 원칙

LSP: 리스코프 치환 원칙

ISP: 인터페이스 분리 원칙

DIP: 의존성 역전 원칙


 

SRP: 단일 책임 원칙

- 하나의 모듈은 반드시 하나의 액터에 대해서만 책임져야 한다.

- 클래스는 하나의 기능을 수행하고, 해당 클래스의 변경 이유는 하나여야 한다. 

 

가령 다음과 같은 Employ 클래스가 있다고 하자. 

class Employ:
    def __init__(self, name, regular_hours, hourly_rate):
        self.name = name
        self.regular_hours = regular_hours
        self.hourly_rate = hourly_rate

    def calculate_pay(self):
        pay = self.regular_hours * self.hourly_rate
        print(f"{self.name}'s pay: ${pay}")
        return pay

    def report_hours(self):
        print(f"{self.name}'s regular hours worked: {self.regular_hours} hours")
        return self.regular_hours

    def regular_hours_algorithm(self):
        if self.regular_hours > 40:
            return 40  # 초과 근무가 있으면 40시간만 정산
        return self.regular_hours

 

위의 코드는 아래와 같은 다중 액터를 지닌다.

- 회계팀: calculate_pay()

- 인사팀: report_hours()

 

이렇게 다중 액터를 가지는 모듈의 경우 여러 문제들이 생길 수 있는데, 다음 2가지가 가장 대표적이다. 

 

우발적 중복

두 매서드가 동시에 접근하고 있는 regular_hours_algorithm을 한쪽 팀의 요청으로 수정할 일이 생겼다고 하자. 다른 한쪽에서는 해당 코드가 바뀌었는지에 대한 인지조차 어려운 상황이 발생할 수 있다.

 

병합

두 팀에서 각각 기능 수정을 위해 Employee 클래스를 체크아웃받은 후 코드를 변경할때, 충돌이 날 가능성이 농후하다. 

 

위의 코드를 액터에 따라 클래스를 분리하고, 퍼사드 패턴을 사용해서 관리해보자.

퍼사드 패턴: 복잡한 서브시스템을 간단한 인터페이스로 숨기는 디자인 패턴

class PayCalculator:
    def __init__(self, regular_hours, hourly_rate):
        self.regular_hours = regular_hours
        self.hourly_rate = hourly_rate

    def calculate_pay(self):
        pay = self.regular_hours * self.hourly_rate
        print(f"Pay: ${pay}")
        return pay


class HourReporter:
    def __init__(self, regular_hours):
        self.regular_hours = regular_hours

    def report_hours(self):
        print(f"Regular hours worked: {self.regular_hours} hours")
        return self.regular_hours


class RegularHours:
    def __init__(self, regular_hours):
        self.regular_hours = regular_hours

    def regular_hours_algorithm(self):
        if self.regular_hours > 40:
            return 40  # 초과 근무가 있으면 40시간만 정산
        return self.regular_hours


class EmployeeFacade:
    def __init__(self, name, regular_hours, hourly_rate):
        self.name = name
        self.pay_calculator = PayCalculator(regular_hours, hourly_rate)
        self.hour_reporter = HourReporter(regular_hours)
        self.regular_hours = RegularHours(regular_hours)

    def calculate_pay(self):
        regular_hours = self.regular_hours.regular_hours_algorithm()  # 정상 근무 시간 계산
        return self.pay_calculator.calculate_pay()  # 급여 계산

    def report_hours(self):
        self.hour_reporter.report_hours()  # 근무 시간 보고


# 사용 예시
employee = EmployeeFacade(name="John", regular_hours=45, hourly_rate=20)
employee.calculate_pay()  # 급여 계산
employee.report_hours()   # 근무 시간 보고