SOLID 디자인 원칙4 : ISP
전체 SOLID 디자인 원칙의 링크들
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는 인터페이스 모든 항목에 대한 구현을 강제하지 않고 실제 필요한 인터페이스만 구현할 수 있도록 하는 것이다.
이를 위해 목적(인터페이스)에 맞게 클래스를 구현하고 다중 상속을 사용해서 메인 클래스를 만든다