프로그래밍 언어/Python

파이썬(PYTHON) 클린코드 #6_ SOLID, 단일 책임 원칙(SRP)

Tigercow.Door 2020. 4. 26. 19:14


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

이번 포스팅에서는 SOLID 원칙에 대해서 알아봅니다. SOLID는 5가지 원칙을 줄여서 말하는 내용인데, 한번에 5가지 모두를 알아보지 않고, 각 원칙에 대해서 코드로 함께 살펴보며 총 5개의 포스팅으로 나누어 설명드릴 예정입니다.



0. SOLID란?


우선 솔리드, SOLID는 객체 지향 프로그래밍 및 설계의 다섯가지 기본 원칙을 이야기한다. 프로그램이 시간이 지나도 유지 보수 및 확장이 쉬울 수 있도록 하기 위한 원칙이며, 우리가 학습하고자 했던 클린코드를 위한 원칙이기도 하다. 


조금 더 구체적으로, SOLID는 아래와 같은 5가지 원칙을 이야기 한다.


1. Single Responsibility Principle(단일 책임 원칙)


2. Open/Closed Principle(개방/폐쇄 원칙)


3. Liskov Substitution Principle(리스코프 치환 원칙)


4. Interface Segregation Principle(인터페이스 분리 원칙)


5. Dependency Inversion Principle(의존관계 역전 원칙)


이제 우리는 각 원칙에서 강조하는 것과, 실제로 그것을 코드로서 간단하게 살펴보며 이해를 해보도록 하겠다.




1. SRP(Single Responsibility Principle)


단일 책임 원칙(SRP)이란 하나의 클래스는 하나의 책임만 가지며, 그 책임에 대해 완전히 캡슐화 해야 함을 말한다. 

클래스가 하나의 책임만 가진다는 것은, 하나의 클래스는 하나의 일을 담당하고 있으며, 클래스를 변경해야 할 이유는 오직 한가지가 존재한다는 것으로 생각할 수 있다. 이러한 원칙은, 코드의 응집력을 높이는데 도움을 준다. 


간혹 하나의 클래스가 다양한 책임을 지고 있는 경우가 있는데, 이러한 클래스(객체)를 일컬어 신(god) 객체라고 이야기 한다. 이러한 신 객체가 존재하면 시스템에 대한 유지보수가 어려워진다.


어떤 면에서 단일 책임 원칙은, 관계형 데이터베이스 설계에서의 정규화 개념과 유사하다고 생각할 수 있다. 만약 객체의 속성이나 메서드 중에서 서로 특성이 다른 요소(그룹)가 발견되면 그러한 것들을 적절히 분리시켜야 한다.


이제 실제로 코드를 통해 너무 많은 책임을 가진 클래스와, 이를 단일 책임 원칙에 따라 분리한 예제를 살펴보도록 하자.


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
class Course:
    """너무 많은 책임을 가진 Course 클래스"""
    
    def __init__(self, code, name, schedule, pf):
        self.code = code
        self.name = name
        self.schedule = schedule
        self.pf = pf
    
    def connect(self, con)->bool:
        """DB connecting"""
        pass
    
    def close(self)->bool:
        """DB close"""
        pass
    
    def get_course_by_pf(self, professor)->list:
        """professor로 강의 찾기 함수"""
        pass
    
    def save_course(self, Course)->bool:
        """DB에 course 저장하기"""
        pass
    
    def update_course(self, code)->bool:
        """특정 code를 가진 course를 DB update 하기"""
        pass
cs

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


먼저 위의 Course 클래스를 살펴보자.

해당 Course 클래스는 너무 많은 책임을 가지고 있다. 클래스의 생성자 __init__함수가 정의되어 있으면서 DB접근과 관련된 함수들도 함께 정의되어 있다.

즉, Course 객체의 속성들에 대한 관리를 하고 있으면서, Course의 DB 내용에 대한 관리도 하고 있다. 이러한 경우 DB에 대한 시스템 변경이 있을 때나, Course의 속성에 대한 변경이 있을 때 동일한 클래스를 수정하고 서로 영향이 없도록 신경써야 한다.

따라서 위와 같은 클래스는 아래와 같이 분리할 수 있다.


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
class Course:
    """SRP원칙을 준수한 Course 클래스"""
    
    def __init__(self, code, name, schedule, pf):
        self.code = code
        self.name = name
        self.schedule = schedule
        self.pf = pf
 
class CourseDB:
    """SRP원칙을 준수한 CourseDB 클래스"""
    def __init__(self, con):
        self.con = con
    
    def connect(self)->bool:
        """DB connecting"""
        pass
    
    def close(self)->bool:
        """DB closing"""
        pass
    
    def get_course_by_pf(self, professor)->list:
        """professor로 강의 찾기 함수"""
        pass
    
    def save_course(self, Course)->bool:
        """DB에 course 저장하기"""
        pass
    
    def update_course(self, code)->bool:
        """특정 code를 가진 course를 DB update 하기"""
        pass
cs

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


이전과 달리 Course 클래스와 CourseDB 클래스로 나누어, DB를 관리하는 클래스를 따로 분리하였다. 

이렇게 됨으로써 각 클래스가 자신이 담당한 하나의 일에 대해서만 책임을 지게 된다.


이번에는 다른 예제를 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Human:
    """추상화 과정이 필요한 Human 클래스"""
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        
    def go_restroom(self):
        """화장실 가는 함수"""
        if(self.sex == '남자'):
            print("남자화장실로 간다.")
        elif(self.sex == '여자'):
            print("여자화장실로 간다.")
        else:
            print("성별을 지정해주세요.")
 
cs

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


위의 Human 클래스에서는 객체 생성시에 성별을 정의하게 되어있다. 그리고 go_restroom에서는 성별에 따라 분기처리하여 로직을 처리한다. 즉, Human클래스는 성별이 다른 두가지 케이스에 대한 책임을 동시에 가지고 있다. 이러한 경우 Human 클래스를 다음과 같이 추상화시킴으로써 해결할 수 있다.


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
from abc import *
 
class HumanBase(metaclass=ABCMeta):
    def __init__(self, name):
        self.name = name
    
    @abstractmethod
    def go_restroom(self):
        pass
    
class Male(HumanBase):
    def __init__(self, name):
        super().__init__(name)
        self.sex = "남자"
    
    def go_restroom(self):
        print("남자화장실로 간다.")
        
class Female(HumanBase):
    def __init__(self, name):
        super().__init__(name)
        self.sex = "여자"
        
    def go_restroom(self):
        print("여자화장실로 간다.")
cs

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


위와 같이 추상클래스, HumanBase 클래스를 만들어 주었다. 기본적으로 생성시에는 이름만 받고 이를 상속받은 클래스들은 go_restroom을 구현하게끔 하였다.

그리고 HumanBase를 상속받는 Male 클래스와 Female 클래스를 정의함으로써, 성별이 다른 경우에 대해 각 클래스가 유일한 책임을 가지도록 하였다.


이렇게 우리가 SOLID에서 단일 책임 원칙에 대해 알아보면서 대표적인 두가지 예제를 살펴보았다.

첫번째, Course 클래스에서는 단일 클래스에서 너무 많은 액션, 업무를 담당하고 있어 이를 분리함으로써 단일 클래스가 하나의 책임만 가지도록 하였다.

두번째, Human 클래스에서는 보다 높은 추상과 과정을 통해서 각 클래스가 하나의 책임만 가지도록 설정하였다.


이렇게 단일 책임 원칙을 지킨 경우에 클래스에 대한 외부 영향도를 최소화 할 수 있으며, 결국 유지보수나 확장 면에서 보다 효율적일 수 있다.


728x90