개발 공부/Java

ISP (인터페이스 분리 원칙) in 스프링 프레임워크

gmelon 2022. 4. 17. 00:03

제목이 너무 거창한가

스프링 공부 중 아래와 같이 스프링 컨테이너인 ApplicationContext 의 구현체로 AnnotationConfigApplicationContext 를 설정하고 BeanDefinition을 반환하는 ac.getBeanDefinition() 메서드를 호출하려는데 계속 메서드를 찾을 수 없다는 오류가 발생했다.

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

오류 메시지

알아보니 ac의 타입을 인터페이스인 ApplicationContext가 아닌 구현체 자체로 설정해주어야 했고, 이러한 일이 발생하는 이유는 객체 지향 설계 원칙인 SOLID 중 ISP 때문이라고 한다. ORP나 DIP, SRP에 대해서는 접해본 적이 있었지만 ISP는 어떤 것인지 정확하게 확인을 못 해본 것 같아 이번 기회에 찾아보았다.

ISP (Interface Segregation Principle)

ISP란 Interface Segregation Principle 즉, 인터페이스 분리의 원칙으로 클라이언트가 자신이 이용하지 않는 메서드에 의존하면 안 된다는 객체 지향 설계의 원칙 중 하나이다. 이렇게만 들으니 도대체가 뭔말인지 이해가 되질 않았다ㅎ 조금 더 찾아보니 이러한 설명이 있었다.

인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다.

 

인텔리제이를 통해 확인한 AnnotationConfigApplicationContext 클래스의 상속 구조를 보고 위 문장을 곱씹어보니 어떤 뜻인지 알 것도 같았다.

 

이 클래스는 위 그림처럼 뭔가 엄청나게 복잡한 계층 구조로 상속이 이뤄지고 있었는데 앞서 ApplicationContext 타입으로 사용하려고 했던 getBeanDefinition 함수는 ApplicationContext가 아닌 우측의 BeanDefinitionRegistry에서 최초로 선언이 되어있었다. BeanDefinitionRegistry는 AliasRegistry 인터페이스를 상속받으며 ApplicationContext와는 관계가 없으므로 당연히 getBeanDefinition 함수는 ApplicationContext 타입으로 사용할 수 없었던 것이다.

 

즉, 이러한 현상이 ISP를 지키기 위해 발생했다는 말의 의미는 스프링에서 각 인터페이스가 단일 책임 원칙을 지키게 하기 위해 인터페이스를 구체적이고 작은 단위로 쪼개 놓았고 그로 인해 클라이언트인 AnnotationConfigApplicationContext 입장에서는 계층 구조를 통해 자연스럽게 여러 인터페이스에 각기 선언된 메서드를 가져다 정의하여 사용하게 되고 하나의 인터페이스 타입으로는 전체 메서드를 호출할 수 없게 되는 것이다.

 

문제의 원인은 파악했고,, 그럼 ISP가 왜 필요한 것인지 궁금해졌다.

ISP가 필요한 이유?

다른 블로그들 처럼 간단한 예시를 들어 설명해보았다.

먼저 아래와 같이 TooMuch라는 인터페이스가 있고 A, B, C 메서드를 선언하고 있다.

interface TooMuch {
    void A();
    void B();
    void C();
}

그리고 각각 ClientA, ClientB, ClientC이라는 이름의 구현체들이 위 인터페이스를 구현하려고 하는데 ClientA는 A 메서드만, ClientB는 B만, ClientC는 C만 사용하는 것으로 충분하다고 해보자.

public class ClientA implements TooMuch {

    @Override
    public void A() {
        // Do Something
    }

    @Override
    public void B() {
        // 불필요한 Override
    }

    @Override
    public void C() {
        // 불필요한 Override
    }
}

그럼 위와 같이 B와 C 메서드는 사용하지 않음에도 굳이 재정의해서 배포해야 한다. 당연히 이상한 상황이므로 이럴 경우 인터페이스를 분리하여 인터페이스가 실제 클라이언트가 사용하는 A 메서드만 정의하도록 하는 것이 좋다.

 

이렇게 클라이언트가 이용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리하는 것을 ISP라고 한다.