3 minute read


SOLID 디자인 원칙

  • 단일 책임 원칙(Single Responsibility Principle, SRP)
  • 열림-닫힘 원칙(Open-Closed Principle, OCP)
  • 리스코프 치환 원칙(Liskov Substitution Principle, LSP)
  • 인터페이스 분리 원칙(Interface Segregation Principle, ISP)
  • 의존성 역전 원칙(Dependency Invension Principle, DIP)




1. 단일 책임 원칙(SRP)


단일 책임 원칙(Single Responsibility Principle, SRP)은 개별 클래스가 단 한 가지의 책임을 부여받아 수정할 이유가 단 한 가지여야 한다는 원칙을 말한다.


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
struct Journal
{
    string title;
    vector<string> entries;
    
    explicit Journal(const string& title) : title {}
};

void Journal::add(const string& entry)
{
    static int count = 1;
    entries.push_back(boost::lexical_cast<string>(count++) + ": " + entry);
}


struct PersistenceManager
{
    static void save(const Journal& j, const string& filename)
    {
        ofstream ofs(filename);
        for (auto& s : j.entries)
        {
            ofs << s << endl;
        }
    }
};


// 저널 이용 시
// Journal j{"Dear Diary"};
// j.add("I cried today");
// j.add("I ate a bug");
  • Add: Journal 클래스에 포함 ⭕

    • 각 항목을 기록할 수 있게 하고 관리할 책임이 메모장에 있음
  • save: Jounal 클래스에 포함 ❌

    • 메모장의 책임은 메모 항목을 디스크에 쓰는 것이 아님

      • 데이터 저장 방식(eg.로컬/클라우드)이 바뀔 때마다 일일이 클래스들을 수정해야 함
    • 작은 수정을 위해 여러 클래스를 건드린다면 아키텍쳐가 문제일 수 있음

    잘못된 예시
    ```c++ void Journal::save(const string& filename) { ofstream ofs(filename); for (auto& s : entries) { ofs << s << endl; } } ```


    => 파일 저장 기능은 메모장과 별도인 클래스로 만드는 것이 적합하다! (단일 책임 원칙)



2. 열림-닫힘 원칙(OCP)

열림-닫힘 원칙은 타임이 확장에는 열려 있지만 수정에는 닫혀 있도록 강제하는 것을 의미한다.

따라서 기존 코드의 수정 없이 필터링을 확장할 수 있는 방법이 필요하다.


데이터베이스에 저장된 제품을 색상과 크기로 검색하는 기능을 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum class Color { Red, Green, Blue };
enum class Size { Small, Medium, Large };

struct Product
{
	string name;
    Color color;
    Size size;
};

struct ProductFilter
{
    typedef vector<Product*> Items;
};


제품 필터링 기능 (열림-닫힘 원칙을 따르지 않을 경우)
각각 색상, 크기, 색상과 크기를 필터링하는 함수다. 코드가 반복되어 사용된다는 문제가 있다.
범용적으로 임의의 조건을 지정받는 함수를 만들지 못하는 이유는 아래와 같다. * 필터 조건마다 처리 형태가 다를 수 있음 * 인덱싱으로 접근해야 하는 탐색해야 하는 경우, 병렬로 탐색이 가능한 경우 등 ```c++ // 색상을 기준으로 제품을 구분하는 멤버 함수 ProductFilter::Items ProductFilter::by_color(Items items, Color color) { Items result; for (auto& i : items) { if (i->color == color) { result.push_back(i); } return result; } } // 크기를 기준으로 제품을 구분하는 멤버 함수 ProductFilter::Items ProductFilter::by_size(Items items, Size size) { Items result; for (auto& i : items) { if (i->size == size) { result.push_back(i); } return result; } } // 크기와 색상을 기준으로 제품을 구분하는 함수 ProductFilter::Items ProductFilter::by_color_and_size(Items items, Size size, Color color) { Items result; for (auto& i : items) { if (i->color == color && (i->size == size) { result.push_back(i); } return result; } } ```


열림-닫힘 원칙을 따라 기존 코드의 수정 없이 필터링을 확장할 수 있는 방법은 아래와 같다.

  • 필터링 절차를 개념적으로 두 개의 부분으로 분리 (SRP)

    1. 필터: 항목 집합을 넘겨받아 그 일부를 리턴

    2. 명세: 데이터 항목을 구분하기 위한 조건 정의


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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 필터 정의
template <typename T> struct Filter
{
    virtual vector<T*> filter(vector<T*> items, Specification<T>& spec) = 0;
};

// 명세 정의
template <typename T> struct Specification
{
    virtual bool is_satisfied(T* item) = 0;
};


// 개선된 필터 구현
struct BetterFilter : Filter<Product>
{
    vector<Product*> filter(vector<Product*> items, Specification<Product>& spec) override
    {
        vector<Product*> result;
        for (auto& p : items)
        {
            if (spec.is_satisfied(p))
            {
                result.push_back(p);
            }
        }
        return result;
    }
};

// 색상 필터에 대한 명세
struct ColorSpecification : Specification<Product>
{
    Color color;
    
    explicit ColorSpecification(const Color color) : color{color} {}
    
    bool is_satisfied(Product* item) override 
    {
    	return item->color == color;    
    }
};

// 위 명세를 활용한 제품 목록 필터링
// Product apple{ "Apple", Color::Green, Size::Small };
// Product tree{ "Tree", Color::Green, Size::Large };

  • 정의
    • 필터
      • Specification<T>에 기반해 필터링 수행
      • 전체 항목 집합과 명세를 인자로 받아 명세에 합치되는 항목들을 리턴
        • 항목들은 vector<T*>에 저장
    • 명세
      • 전체적인 접근 방법을 재사용 가능하게 만듦
  • 구현
    • 개선된 필터
      • Specification<T> 타입이 강하게 규정된 함수

Leave a comment