사실 ISP는 SRP와 동일한 문제를 해결하는 다른 방식이라고 생각한다. ISP는 그 해결책으로 인터페이스를 분리하는 것을 택한 것이다.
사용하지 않는, 불필요한 메서드를 가진 인터페이스는 오염되었다고 할 수 있다. 그리고 이런 오염된 인터페이스는 불필요한 복잡성과 불필요한 중복성의 악취를 풍길 수 있다. 인터페이스가 오염되었다고 판단되었음에도 그 구조를 계속 유지할 경우 파생 클래스가 새로운 메서드를 필요로 할 때마다 그 메서드가 기반 클래스에도 추가되어야하는 경우가 생기고, 그러면 기반 클래스의 인터페이스는 더욱 오염되고 비대해진다.
클라이언트는 자신이 사용하는 인터페이스에 영향을 끼친다. 즉 하나의 인터페이스를 완전히 다른 클라이언트가 같이 사용한다면 그 인터페이스는 분리되어야 한다. 인터페이스에 변경도 클라이언트에 영향을 끼치지만 클라이언트도 인터페이스의 변경을 불러일으킬 수 있기 때문이다. 인터페이스 분리가 되어있지 않은 상태로 클라이언트에 의해 인터페이스의 변경이 야기될 경우 경직성과 점착성의 악취를 풍길 수 있다. 특히 이 인터페이스의 사용자가 완전히 관련이 없는 클라이언트들인 경우, 그 위험성은 급격히 증가한다.
인터페이스 분리 원칙이란 "클라이언트가 자신이 사용하지 않는 메서드에 의존하도록 강제되어서는 안 된다."는 원칙이다. 이러한 원칙이 지켜지지 않았을 때, 클라이언트는 메서드의 변경에 취약해진다. 즉 A 클라이언트가 자신은 사용하지 않지만 B 클라이언트가 사용하는 메서드를 포함하는 클래스 C를 의존할 경우, A는 B가 C에 가하는 변경에 영향을 받게 된다. 이런 경우 인터페이스를 분리해줄 필요가 있다.
만약 개별적인 클라이언트를 가지고 있는 2개의 개별적인 인터페이스가 있다고 가정하자. 근데 이 두 인터페이스가 같은 데이터를 조작해야해서 같은 객체에서 구현되어야할 경우, 어떻게 함께 있어야하는 상태에서 인터페이스를 분리할 수 있을까? 아래와 같은 2가지 방법이 대표적이다.
- 위임을 통한 분리 -> 어댑터 패턴을 통해 구현하는 것과 유사해보인다.
- 다중 상속을 통한 분리 -> 구현하는 객체가 인터페이스를 모두 구현하게 하고, 각 클라이언트에 따라서 필요한 인터페이스로 사용하게 하는 것
- 여러 클래스가 ISP를 지키지 않은 인터페이스에 의존하는 경우, 경직성, 취약성 등 코드 악취를 풍길 수 있다.
- 인자를 각 타입마다 여러개 주는 것을 복합체, 각 타입의 공통된 기반 타입으로 주는 것을 단일체라고 한다.
- 일반적인 경우 복합체로 인자를 주는 것이 바람직하다. 단일 형태일 경우 해당 함수 f는 그 기반 타입에서 파생된 모든 인터페이스에 의존하게 되기 때문이다. 모든 인터페이스가 그 단일체 하나의 인터페이스로 결합되어 있다는 사실은 함수 f가 알 필요 없는 정보이다.
- 각각의 클라이언트가 아닌 유사한 서비스를 호출하는 경우 클라이언트 그룹으로 묶을 수 있다. 그리고 각 그룹에 대해 분리된 인터페이스를 만들 수 있다. 이렇게 하면 구현해야 하는 인터페이스 수도 훨씬 줄고, 그 서비스가 각 클라이언트의 형에 의존하게 되는 일을 방지할 수 있다.
- 서로 다른 클라이언트 그룹이 호출하는 메서드가 겹칠 때가 있는데, 겹치는 부분이 작으면 이 그룹들의 인터페이스는 분리된 상태로 남아야 한다.
- 유지보수 시 인터페이스가 변경되야하는 경우, 거기다가 이러한 변경이 큰 영향을 미치는 경우, 충격을 완화하기 위해서는 기존 인터페이스를 변경하는 것이 아닌 기존 객체에 새로운 인터페이스를 추가하는 방법을 쓸 수 있다.
- 모든 원칙들은 너무 지나치지 않아야 한다.
비대한 클래스가 존재할 경우, 이 비대한 클래스에 한 클라이언트가 변경을 가하면 나머지 모든 클래스에게 영향이 퍼져나가게 된다. 그렇기에 클라이언트는 자신이 실제로 호출하는 메서드에만 의존해야 한다. 이것을 실천하기 위해 인터페이스를 클라이언트 고유의 인터페이스 여러 개로 분해하는 방법을 쓸 수 있다. 결과적으로 호출하지 않는 메서드에 대한 클라이언트의 의존성은 끊기게 될 것이고, 클라이언트가 서로에 대해 독립적이게 될 것이다.