Post

SOLID 디자인 원칙4 : ISP

전체 SOLID 디자인 원칙의 링크들

  1. SRP : 단일 책임 원칙
  2. OCP : 열림-닫힘 원칙
  3. LSP : 리스코프 치환 원칙
  4. ISP : 인터페이스 분리 원칙
  5. DIP : 의존성 역전 원칙

SOLID 디자인 원칙의 네 번째, 인터페이스 분리 원칙(Interface Segregation Principle, ISP)

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

1
2
3
4
5
class MyPrinter : IMachine {
	void print(vector<Document*> _docs) override;
	void fax(vector<Document*> _docs) override;
	void scan(vector<Document*> _docs) override;
};

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

1
2
3
4
5
class IMachine{
	void print(vector<Document*> _docs) abstract;
	void fax(vector<Document*> _docs) abstract;
	void scan(vector<Document*> _docs) abstract;
};

여기서 문제가 발생한다. 어떤 업체는 스캔 기능이나 팩스 기능이 필요하지 않을 수 있다. 단지 프린트만 만들고 싶을 수 있다. 하지만 이 인터페이스는 여하튼 모든 기능을 구현하도록 강제한다. 물론 업체에서 빈 함수를 만들어 대응할 수도 있다. 그럼 무엇이 문제일까?
인터페이스 분리 원칙이 의미하는 바는 필요에 따라 구현할 대상을 선별할 수 있도록 인터페이스를 별개로 두어야 한다는 것이다. 프린트와 스캔은 서로 다른 동작이므로(예를 들어 스캐너는 프린트를 할 수 없다.) 인터페이스를 구분하여 나눈다.

1
2
3
4
5
6
7
class IPrinter{
	virtual void print(vector<Document*> _docs) abstract;
};

class IScanner{
	virtual void scan(vector<Document*> _docs) abstract;
};

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

1
2
3
4
5
6
7
class Printer : IPrinter{
	void print(vector<Document*> _docs) override;
};

class Scanner : IScanner{
	void scan(vector<Document*> _docs) override;
};

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

1
2
3
class IMachine : IPrinter, IScanner{
	//To do
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Machine : IMachine{
	IPrinter& printer;
	IScanner& scanner;

	Machine(IPrinter& _printer, IScanner& _scanner)
		: printer(_printer)
		, scanner(_scanner)
	{}

	void print(vector<Document*> _docs) override{
	}

	void scan(vector<Document*> _docs) override{
		scanner.scan(_docs);
	}
};

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

요약

ISP는 인터페이스 모든 항목에 대한 구현을 강제하지 않고 실제 필요한 인터페이스만 구현할 수 있도록 하는 것이다.
이를 위해 목적(인터페이스)에 맞게 클래스를 구현하고 다중 상속을 사용해서 메인 클래스를 만든다

This post is licensed under CC BY 4.0 by the author.