Corgi Dog Bark

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인 패턴] SOLID 의 Single Responsibility
    알고리즘/디자인 패턴(Design Patterns) 2022. 11. 30. 01:14
    반응형

    디자인 패턴에 대한 정리 내용 중 SOLID 원칙의 첫 번째 원칙인 Single Responsibility 즉, 단일 책임 원칙에 대해 적어보겠습니다. SOLID 설계 원칙은 유지 보수가 쉽고 재사용 가능하도록 하고, 확장 가능한 개발을 위해 지켜지고 있습니다. 제 첫 번째 디자인 패턴 글에서는 C ++의 단일 책임 원칙과 이를 사용하였을 때, 장점 등을 보겠습니다.

     

    이 글 뒤에서는 총 SOLID의 5가지 원칙에 대해 살펴보겠습니다.

    1. SRP - Single Responsibility Principle
    2. OCP - Open/Closed Principle
    3. LSP - Liskov Substitution Principle
    4. ISP - Interface Segregation Principle
    5. DIP - Dependency Inversion Principle

     


     

    단일 책임 원칙 정리

    하나의 객체는 반드시 하나의 동작만의 책임을 갖는다는 원칙.

    좀 더 쉬운 말로 하자면, 단일 책임 원칙이란, 특정 객체의 책임을 최대한 분산시켜, 모듈의 결합도를 느슨하게 하기 위한 원칙입니다. 가장 첫번째 나온 원칙인 만큼 중요한 의미를 내포하고 있습니다. 단일 책임 원칙에 따라, 객체의 책임을 분산시킬수록 객체의 변경에 따른 영향도의 양과 범위가 작아지기 때문입니다.

     

     


     

    단일 책임 원칙(SRP) 의 위배 예시

    제일 먼저, 단일 책임 원칙에 벗어나는 예제를 보이도록 하겠습니다. Journal 이란 객체를 생성해줄 것인데, 이때, 저널 객체는 해당 저널이 가입되어 있는 목록과 파일에 저장할 수 있는 시스템을 구축한다고 가정해봅시다.

    class Journal {
    private:
    	string mTitle;
    	vector<string> mEntry;
    
    public:
    	explicit Journal(const string& title):mTitle(title){}
    
    	void AddEntry(const string& entry) {
    		static int count = 1;
    		mEntry.push_back(to_string(count++) + ": " + entry);
    	}
    
    	auto GetEntry() const {return mEntry;}
        
    	void Save(const string& filename)	{
    		ofstream ofs(filename);
    		for(auto &s : mEntry) {
    			ofs << s << endl;
    		}
    	}
    };

    이때,  이 코드에 대해 다음과 같이 정리할 수 있습니다.

    1. 위의 C ++ 예제는 단일 객체인 Journal에 대해서는 작동이 그럭저럭 괜찮아 보입니다. 그러나 기능을 추가하거나 실제 상황에서는 문제 발생의 확률이 매우 큽니다.
    2. 예를 들어, Journal 뿐만 아니라 Book 또는 File 등 여러 저장 매체를 추가한 다 가정 했을 때, 각 도메인(책, 파일)에 관해서 어떻게 저장할지 모두 객체 안에서 구현해야한다는 단점이 존재합니다.
    3. 하지만 이보다 더 큰 문제는 저장 기능을 변경 할 때 발생합니다. 예를 들어, 특정 경우에는 저널의 데이터를 파일에 저장하지 않다고 해봅시다. 이 경우 모든 Save 코드를 변경해야 하며, 효율적이지 못하게 됩니다.
    4. 그러면 Journal 클래스를 단일 책임 원칙에 따라 변경해야 하는 두 가지를 살펴보겠습니다
      1. Journal과 관련된 객체들
      2. Journal 의 Saving 기능

    위의 예제에서 보았듯이, Journal 객체는 Journal을 구성하는 부분과 저장 기능을 분리함으로써, SRP을 달성할 수 있습니다.

     


    반응형

     

    단일책임원칙의 해결책 제시

    저장 기능을 따로 객체화시켜, 단일 책임원칙을 지킬 수 있도록 분리하겠습니다.

    class Journal {
    private:
    	string mTitle;
    	vector<string> mEntry;
    
    public:
    	explicit Journal(const string& title):mTitle(title){}
    
    	void AddEntry(const string& entry) {
    		static int count = 1;
    		mEntry.push_back(to_string(count++) + ": " + entry);
    	}
    
    	auto GetEntry() const {return mEntry;}
    };
    
    // save 함수 분리
    class SavingManager{
    	static void Save(const Journal& j, const string& filename) {
    			ofstream ofs(filename);
    			for(auto& s: j.GetEntry()) {
    				ofs << s << endl;
    			}
    	}
    };
    • 저장 작업을 수행하는 별도의 객체를 두어 관리해주었습니다.
    • 저장 관리자가 확장됨에 따라서, 저장 관련 코드를 분리할 수 있다는 장점이 생기게 됩니다. 또한 더 많은 도메인 객체(여기서는 Journal, book) 등을 허용하도록 템플릿 화할 수 도 있습니다.

     

    단일 책임 원칙의 장점

    1. 간결한 표현
      • 클래스가 한 가지 역할만 수행할 때, 인터페이스에는 일반적으로 표현력이 직관적인 적은 수의 메서드가 있습니다. 이는 적은 수의 데이터 멤버를 가지게 되고 이해하기 쉬운 인터페이스로 직결됩니다.
      • 이렇게 하면 개발 속도가 향상되고 개발의 용이성을 길러주게 됩니다.
    2. 유지 보수 측면
      • 요구 사항이 시간이 지남에 따라 변하는 것은 당연한 이치입니다. 이는 설계 및 아키텍처에서도 마찬가지입니다. 클래스가 더 많은 책임을 질수록, 변경 사항이 생길 때마다 의존성에 의해서 더 많은 변경을 진행해주어야 합니다. 이는 유지보수 측면에서 개발의 속도를 더디게 합니다.
      • 서로 분리된 설계들은 변경의 연결고리를 제거해주어, 생산성을 향상해 줍니다.

     


    결론

    SRP는 리팩토링 책을 읽다 보면 제일 먼저 나오게 되는 널리 사용되는 정당성입니다. 저 또한 코드를 설계하다 보면 하나의 객체가 너무 많은 동작을 담당하여, 최소화의 원칙이 잘 지켜지지 않을 때가 있습니다. 단일 책임 원칙은 이러한 개발 방향을 올바르게 잡아주어, 유지 보수 및 효율적인 개발이 가능하게 해 준다는 기본적이지만 가장 핵심적인 원칙을 가지고 있다 할 수 있겠습니다.

     

    반응형

    댓글

Designed by Tistory.