인터페이스 분리 원칙(Interface Segregation Principle, ISP)
정의
- "클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다."
- 이 원칙은 범용(General-Purpose) 인터페이스가 아닌 좁고 세분화된(Narrow and Targeted) 인터페이스를 만드는 것을 독려하고 있습니다.
적용
첫번째 시나리오
아무개 회사
의 운영 팀 사무실에는 2016년도 형 제록스 복합기 가 있습니다.- 덩치가 조금 큰 걸 봐선 많은 기능을 가지고 있을 것 같은데요, 저희는 크게 인쇄 와 스캔 , 그리고 팩스 기능을 가지고 있는 복합기라고 가정하겠습니다.
설계
자 이제 이 복합기의 인터페이스를 설계해봅시다.
public interface IMultiFunctionDevice {
public void print();
public void getPrintSpoolDetails();
public void scan();
public void scanPhoto();
public void fax();
public void internetFax();
}
getPrintSpoolDetails()
메서드는 인쇄 대기 목록 을 반환하는 메서드입니다.
다음으로 이 인터페이스를 구현하는 제록스 복합기 를 설계해봅시다.
public class XeroxWorkCentre implements IMultiFunctionDevice {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
@Override
public void scan() {
System.out.println("스캔 중...");
}
@Override
public void scanPhoto() {
System.out.println("사진 스캔 중...");
}
@Override
public void fax() {
System.out.println("팩스 중...");
}
@Override
public void internetFax() {
System.out.println("인터넷 팩스 중...");
}
}
XeroxWorkCentre
클래스는IMultiFunctionDevice
인터페이스를 구현하고 있습니다.IMultiFunctionDevice
인터페이스에는 총 6개의 메서드가 있습니다. 그리고XeroxWorkCentre
클래스는 이 6개의 메서드를 모두 구현하고 있습니다.
두번째 시나리오
아무개 회사
의 개발 팀 사무실에는 복합기 대신에 레이저 프린터 가 있습니다.- 레이저 프린터는 인쇄 와 스캔 기능만 가지고 있다고 가정해보죠, 개발 팀에서는 팩스 기능을 사용할 일이 없나 보군요.
설계
자 이제 이 레이저 프린터의 인터페이스를 설계해봅시다. 기존의 IMultiFunctionDevice
인터페이스에 해당 기능이 모두 존재하므로 그대로 사용하면 되겠죠?
public class LaserPrinter implements IMultiFunctionDevice {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
@Override
public void scan() {
System.out.println("스캔 중...");
}
@Override
public void scanPhoto() {
System.out.println("사진 스캔 중...");
}
@Override
public void fax() {
}
@Override
public void internetFax() {
}
}
LaserPrinter
클래스는IMultiFunctionDevice
인터페이스를 구현하고 있습니다.- 레이저 프린터에는 팩스 기능이 존재하지 않지만
IMultiFunctionDevice
인터페이스를 구현했으므로 팩스 기능은 빈 메서드 로 남겨놓았습니다.
만약 다음 아무개 회사
의 다른 팀에서는 인쇄 기능만 가지고 있는 프린터기를 사용한다면 어떻게 될까요?
public class InkjetPrinter implements IMultiFunctionDevice {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
@Override
public void scan() {
}
@Override
public void scanPhoto() {
}
@Override
public void fax() {
}
@Override
public void internetFax() {
}
}
아마도 이와 같이 IMultiFunctionDevice
인터페이스를 구현하면서 사용하지 않는 메서드들을 빈 메서드로 남겨놓을 것입니다. 구현되지 않은 메서드들은 빈 메서드로 남겨놓는 것이 좋은 방법일까요? 아닙니다, 이는 좋은 디자인이 아니며 인터페이스 분리 원칙 을 위반하는 것입니다.
위의 예시에서 인터페이스 분리 원칙 을 위반하였을 때, 어떤 문제가 발생할 수 있을까요? 예를 들어서 레이저 프린터 를 사용하려고 하는 개발 팀 직원이 IMultiFunctionDevice
인터페이스를 보고 팩스 기능이 존재한다고 착각 하고 팩스 기능을 사용하려고 한다면 어떻게 될까요? 이와 같이 인터페이스 분리 원칙을 위반하면 잘못된 사용자의 이해 를 유도할 수 있습니다.
해결
저희는 위에서 IMultiFunctionDevice
를 구현하고도 사용하지 않는 메서드들을 빈 메서드로 남겨놓은 LasertPrinter
클래스와 InkjetPrinter
클래스를 보았습니다. 이와 같은 문제를 해결하기 위해서는 어떻게 해야할까요? 인터페이스 분리 원칙 을 지키기 위해서는 뚱뚱한 인터페이스 를 설계하는 것을 피해야 합니다.
public interface IMultiFunctionDevice {
public void print();
public void getPrintSpoolDetails();
public void scan();
public void scanPhoto();
public void fax();
public void internetFax();
}
자 그럼 위와 같은 뚱뚱한 인터페이스 를 잘게 쪼개어 보겠습니다.
public interface IPrinter {
public void print();
public void getPrintSpoolDetails();
}
public interface IScanner {
public void scan();
public void scanPhoto();
}
public interface IFax {
public void fax();
public void internetFax();
}
위와 같이 기존의 IMultiFunctionDevice
인터페이스를 인쇄, 스캔, 팩스 기능을 가지는 3개의 인터페이스로 분리하였습니다. 이제 이 3개의 인터페이스로 ZeroxWorkCentre
클래스, LaserPrinter
클래스와 InkjetPrinter
클래스를 설계해보겠습니다.
public class ZeroxWorkCentre implements IPrinter, IScanner, IFax {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
@Override
public void scan() {
System.out.println("스캔 중...");
}
@Override
public void scanPhoto() {
System.out.println("사진 스캔 중...");
}
@Override
public void fax() {
System.out.println("팩스 중...");
}
@Override
public void internetFax() {
System.out.println("인터넷 팩스 중...");
}
}
public class LaserPrinter implements IPrinter, IScanner {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
@Override
public void scan() {
System.out.println("스캔 중...");
}
@Override
public void scanPhoto() {
System.out.println("사진 스캔 중...");
}
}
public class InkjetPrinter implements IPrinter {
@Override
public void print() {
System.out.println("인쇄 중...");
}
@Override
public void getPrintSpoolDetails() {
System.out.println("인쇄 대기 목록을 반환합니다.");
}
}
ZeroxWorkCentre
클래스는 인쇄, 스캔, 팩스 기능을 가지므로IPrinter
,IScanner
,IFax
인터페이스를 구현합니다.LaserPrinter
클래스는 인쇄, 스캔 기능을 가지므로IPrinter
,IScanner
인터페이스를 구현합니다.InkjetPrinter
클래스는 인쇄 기능을 가지므로IPrinter
인터페이스를 구현합니다.
위와 같이 인터페이스 분리 원칙 을 지키면서 인터페이스를 설계하면 사용하지 않는 메서드들을 빈 메서드로 남겨놓을 필요가 없습니다. 또한 이전과 같이 개발 팀의 직원이 IPrinter
와 IScanner
인터페이스를 보고 팩스 기능이 존재한다고 착각할 일도 없습니다.
인터페이스 분리 원칙의 위반을 판단하기
- 인터페이스가 뚱뚱한 인터페이스 인가?
- 설계한 인터페이스의 응집도 가 낮은가? (메서드들이 서로 다른 목적을 가지고 있는가?)
- 인터페이스를 구현하는 클래스에서 빈 메서드 가 존재하는가?