본문 바로가기
프로그래밍

SOLID 디자인 원칙 - 인터페이스 분리 원칙

by bantomak 2023. 11. 20.

SOLID 디자인 원칙

SOLID는 다음과 같은 디자인 원칙들을 아우르는 약어이다.

 

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

 

인터페이스 원칙(Interface Segregation Principle, ISP)

복합기능 프린터를 만들기로 했다고 하자. 이 프린터는 프린트, 스캔, 팩스 기능이 합쳐져 있다. 따라서 다음과 같이 프린터를 정의한다.

 

struct MyFavouritePrinter : IMachine
{
   void print(vector<Document*> docs) override;
   void fax(vector<Document*> docs) override;
   void scan(vector<Document*> docs) override;
}

 

 

여기까지는 나쁠 것이 없다. 이제 프린터의 구현을 하청 업체에 맡기려 한다. 하청 업체는 여러 곳이 될 수 있고, 각기 제품 라인업에 따라 기능 조합을 달리 할 수 있다고 하자. 각 업체가 복합 기능 프린터를 구현할 수 있도록 아래와 같이 인터페이스를 추출한다. (많은 IDE가 클래스의 인터페이스를 자동으로 추출하는 기능을 제공한다.) 

 

 

struct IMachine
{
   virtual void print(vector<Document*> docs) = 0;
   virtual void fax(vector<Document*> docs) = 0;
   virtual void scan(vector<Document*> docs) = 0;
}

 

여기서 문제가 발생한다. 어떤 업체는 스캔 기능이나 팩스 기능이 필요하지 않을 수 있다. 단지 프린터만 만들고 싶을 수 있다. 하지만 이 인터페이스는 모든 기능을 구현하도록 강제한다. 물론 업체에서 빈 함수를 만들어서 대응할 수도 있다. 그럼 무엇이 문제일까?

 

인터페이스 분리 원칙이 의미하는 바는 필요에 따라 구현할 대상은 선별할 수 있도록 인터페이스를 별개로 두어야 한다는 것이다. 프린트와 스캔은 서로 다른 동작이므로(예를 들어 스캐너는 프린트를 할 수 없다.) 인터페이스를 구분하여 나눈다.

 

struct IPrinter
{
   virtual void print(vector<Document*> doc) = 0;
}

struct IScanner
{
   virtual void scan(vector<Document*> doc) = 0;
}

 

이제 프린터와 스캐너를 기능적인 필요에 따라서 따로따로 구현할 수 있다.

 

struct Printer : IPrinter
{
   void print(vector<Document*> docs) override;
}

struct scanner : IScanner
{
   void scan(vector<Document*> docs) override;
}

 

그러면, 복합기 전체를 나타내는 IMachine 인터페이스는 어떻게 되나? 아래와 같이 아서의 인터페이스를 조합하여 만들수 있다.

 

struct IMachine : Printer, IScanner
{
}

 

이 인터페이스로 복합기를 구현한다. 예를 들어 다음과 같이 아무 인터페이스 IPrinter와 IScanner의 구현을 재활용하여 각각에 동작을 위임하는 방식으로 구현할 수도 있다.

 

struct Machine : IMachine
{
   IPrinter& printer;
   IScanner& scanner;
   
   Machine(IPrinter& printer, IScanner& scanner)
      :printer(printer), scanner(scanner)
   {
   }
   
   void print(vector<Document*> docs) override
   {
      printer.print(docs);
   }
   
   void scan(vector<Document*> docs) override
   {
      scanner.scan(docs);
   }
}

 

다시 한번 이 아이디어를 정리하자면, 한 덩어리의 복잡한 인터페이스를 목적에 따라 구분하여 나눔으로써, 인터페이스 모든 항목에 대한 구현을 강제하지 않고 실제 필요한 인터페이스만 구현할 수 있도록 하는 것이다. 만약 어떤 애플리케이션의 플러그인 모듈을 개발할 때 뭐가 뭔지 알 수 없는 혼란스럽기만 한 수십 개의 함수를 빈 껍데기 또는 null 리턴으로 구현하고 있다면 그 애플리케이션의 플러그인 인터페이스 설계자가 인터페이스 분리 원칙을 위반한 것이다.

 

출처

 

모던 C++ 디자인 패턴 - 예스24

새로운 기능으로 풍부해진 C++로 다시 배운다. C++는 C++11/14/17을 거치면서 강력한 언어로 발전했으며, 표현력이 풍부해졌다. GoF의 전통적인 디자인 패턴을 표현력이 풍부해진 모던 C++로 새롭게 학

www.yes24.com

댓글