[모던 c++ 디자인 패턴] 1장

hyng·2023년 4월 16일
0

모던 c++ 디자인 패턴 을 읽고 인상 깊었던 내용을 정리합니다.

  • 특정한 문제에 일반적이고 포괄적인 해법을 적용하려 들면 오버 엔지니어링이 되기 쉽다는 점을 알아야 한다.
    • 패턴 자체에 집착하면 안된다.

SOLID 원칙

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

  • 기록을 위한 메모장 클래스가 있다고 하자. 이 클래스는 vector에 파라미터로 주어지는 값을 추가하는 함수가 존재한다.
  • 이번에는 영구적으로 파일에 저장하는 기능을 만든다고 할 때, 디스크에 파일을 쓰는 기능 또한 메모장 클래스의 역할이라고 할 수 있을까?
  • 작은 수정을 여러 클래스에 걸쳐서 해야 한다면 아키텍처에 뭔가 문제가 있다는 징조이다.
  • 전지 전능한 객체(여러 역할을 담당하는)는 SRP를 위배한다.

1-2. 열림-닫힘 원칙(Open-Closed Principle, OCP)

  • 색상과 크기로 구별되는 상품들이 존재하고 이를 필터링하는 기능을 만든다고 하자.
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;
    Items by_color(Items items, Color color);
};

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_color_and_size(Items items, Size size, Color color)
      {
          Items result;
          for (auto& i : items)
          {
              if (i->size == size && i->color == color)
              {
                  result.push_back(i);
              }
          }
          return result;
      }
    • 요구사항은 언제든 변경될 수 있기 때문에 기존의 코드 수정없이 필터링을 확장할 수 있는 방법이 필요하다.
      • 필터링 절차를 두 개의 부분으로 나눈다. (필터와 명세)

        template <typename T>
        struct Specification
        {
            virtual bool is_satisfied(T* item) = 0;
        };
        
        template <typename T>
        struct Filter
        {
            virtual vector<T*> filter(
                    vector<T*> items,
                    Specification<T>& spec) = 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;
            }
        };

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

  • 자식 객체에 접근할 때 그 부모 객체의 인터페이스로 접근 하더라도 아무런 문제가 없어야 한다는 것을 의미한다.

해당 코드를 사용하는 클라이언트 입장에서 충분히 예측 가능한 처리를 하도록 코드를 작성해야 한다.
당연한 소리지만 코드를 짤 때 잊게 되는 것 같다.

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

  • 인터페이스 분리 원칙이 의미하는 바는 필요에 따라 구현할 대상을 선별할 수 있도록 인터페이스를 별개로 두어야 한다는 것이다.
  • 복합 기능 프린터는 프린트, 스캔, 팩스 기능이 합쳐져 있다.
    struct IMachine
    {
    	virtual void print(vector<Document*> docs) = 0;
    	virtual void fax(vector<Document*> docs) = 0;
    	virtual void scan(vector<Document*> docs) = 0;
    }
  • 만약 프린트 기능만 하는 프린터를 만들려고 한다면?
    • fax, scan을 빈 함수로 구현해야 한다.

1-5. 의존성 역전 원칙(Dependency Inversion Principle)

  • 상위 모듈이 하위 모듈에 종속성을 가져서는 안된다. 양쪽 모두 추상화에 의존해야 한다.
  • 추상화가 세부 사항에 의존해서는 안 된다. 세부 사항이 추상화에 의존해야 한다.
profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글