Java

[객체지향설계원칙] ISP

shininghyunho 2024. 5. 31. 16:17

ISP

Interface Segregation Principle.

클라이언트에서 인터페이스를 사용할때

해당 클라이언트에서 사용할 메서드만 의존해야한다.

즉 인터페이스를 범용으로 만들지 말라는 원칙이다.

 

넓게 보면 SRP(단일 책임 원칙) 범주에 속한다고도 볼 수 있습니다.

ISP 적용 전

예를들어 다음과 같이 일꾼이 될수있는 인터페이스가 존재합니다.

// Workable 인터페이스: 모든 메서드를 포함
public interface Workable {
    void startWork();
    void stopWork();
    void focus();
    void sleep();
    void wakeUp();
    void eat();
    void drink();
}

 

일꾼으로서 필요한 다양한 메서드가 있죠.

 

그리고 이를 구현한 인간과 로봇이 있습니다.

먼저 인간은 모든 기능이 필요하므로 딱히 큰 문제는 없어보입니다.

// Human 클래스
public class Human implements Workable {
    @Override
    public void startWork() {
        System.out.println("Human starts working.");
    }

    @Override
    public void stopWork() {
        System.out.println("Human stops working.");
    }

    @Override
    public void focus() {
        System.out.println("Human is focusing.");
    }

    @Override
    public void sleep() {
        System.out.println("Human is sleeping.");
    }

    @Override
    public void wakeUp() {
        System.out.println("Human wakes up.");
    }

    @Override
    public void eat() {
        System.out.println("Human is eating.");
    }

    @Override
    public void drink() {
        System.out.println("Human is drinking.");
    }
}

 

문제는 로봇입니다.

로봇은 잠을 자지도 먹지도 못하기 때문입니다.

// Robot 클래스
public class Robot implements Workable {
    @Override
    public void startWork() {
        System.out.println("Robot starts working.");
    }

    @Override
    public void stopWork() {
        System.out.println("Robot stops working.");
    }

    @Override
    public void focus() {
        System.out.println("Robot is focusing.");
    }

    // 예외를 발생시킴
    @Override
    public void sleep() {
        throw new UnsupportedOperationException("Robot doesn't need to sleep.");
    }

    @Override
    public void wakeUp() {
        throw new UnsupportedOperationException("Robot doesn't need to wake up.");
    }

    @Override
    public void eat() {
        throw new UnsupportedOperationException("Robot doesn't need to eat.");
    }

    @Override
    public void drink() {
        throw new UnsupportedOperationException("Robot doesn't need to drink.");
    }
}

 

그래서 불필요한 메서드에대한 예외처리를 추가해줘야합니다.

ISP 적용 후

위에서 문제가 되었던 커다란 인터페이스를

각각의 책임을 갖도록 분리해줘야합니다.

 

// Workable 인터페이스
public interface Workable {
    void startWork();
    void stopWork();
    void focus();
}

// Sleepable 인터페이스
public interface Sleepable {
    void sleep();
    void wakeUp();
}

// Eatable 인터페이스
public interface Eatable {
    void eat();
    void drink();
}

 

일꾼으로서 필요했던 메소드들을 각각의 의도에 맞게 분리해줬습니다.

그리고 클라이언트는 필요한 기능만 구현하면 됩니다.

 

인간은 일하고 잠자고 먹을 수 있어 모두 구현해줍니다.

// Human 클래스
public class Human implements Workable, Sleepable, Eatable {
    @Override
    public void startWork() {
        System.out.println("Human starts working.");
    }

    @Override
    public void stopWork() {
        System.out.println("Human stops working.");
    }

    @Override
    public void focus() {
        System.out.println("Human is focusing.");
    }

    @Override
    public void sleep() {
        System.out.println("Human is sleeping.");
    }

    @Override
    public void wakeUp() {
        System.out.println("Human wakes up.");
    }

    @Override
    public void eat() {
        System.out.println("Human is eating.");
    }

    @Override
    public void drink() {
        System.out.println("Human is drinking.");
    }
}

 

로봇은 자지도 먹지도 않으니 일하는 기능만 구현해줍니다.

// Robot 클래스
public class Robot implements Workable {
    @Override
    public void startWork() {
        System.out.println("Robot starts working.");
    }

    @Override
    public void stopWork() {
        System.out.println("Robot stops working.");
    }

    @Override
    public void focus() {
        System.out.println("Robot is focusing.");
    }
}

 

그럼 Human과 Robot의 업무를 한눈에 살펴보기 좋아졌습니다.

 

ISP를 통해 얻는 이점

ISP를 적용하기전 예제를 보면 Workable 인터페이스에 괴장히 많은 메서드들이 존재했습니다.

그래서 구체적으로 어떤 일을 하는지가 모호했습니다.

그러나 인터페이스를 분리하니 해당 인터페이스의 목적성이 뚜렸해졌습니다.

'Workable은 일만하고 Sleepable은 자기만 Eatable은 먹기만 하겠군'하고 기능 유추가 가능해집니다.

 

필요없던 메서드가 사라져 코드의 중복이 줄어들고,

예외처리도 필요하지 않아 유지보수성도 좋아졌습니다.

 

다른 일꾼 클래스가 추가되더라도 필요한 기능만 구현하게되어

OCP 원칙도 준수할 수 있습니다.

 

ISP를 사용할 때 주의점

그렇다고 인터페이스를 무작정 나누면 안됩니다.

 

위에서 사용하던 Workable 인터페이스입니다.

// Workable 인터페이스
public interface Workable {
    void startWork();
    void stopWork();
    void focus();
}

 

해당 인터페이스를 더 상세하게 나누면 각각의 메서드들만 포함할 수도 있습니다.

// StartWorkable 인터페이스: 작업 시작과 관련된 메서드를 포함
public interface StartWorkable {
    void startWork();
}

// StopWorkable 인터페이스: 작업 종료와 관련된 메서드를 포함
public interface StopWorkable {
    void stopWork();
}

// Focusable 인터페이스: 집중과 관련된 메서드를 포함
public interface Focusable {
    void focus();
}

 

이렇게 인터페이스를 너무 과도하게 나누면 배보다 배꼽이 더 커집니다.

인터페이스가 너무 과도하게 많아지고 설계가 복잡해집니다.

 

어차피 모두 같이 쓰이는 기능이고 비슷한 기능이면 상황에 맞게

하나의 인터페이스로 합쳐야합니다.

 

근데 혹시 따로 필요한 기능이 맞지 않나? 하고 고민이 될 수도 있습니다.

그래서 처음 설계할때부터 분리를 해놔야하기에

해당 과정이 난도가 높습니다.

 

또한 클라이언트에 맞게 인터페이스를 분리하다보면

비슷한 기능을 가진 인터페이스가 있음에도

새로운 인터페이스를 만드는 상황이 생길 수 있어 복잡성이 증가됩니다.

 

결론

ISP 원칙은 마치 인터페이스에 SRP 원칙을 적용하듯,

커다란 인터페이스를 클라이언트에 맞게 분리합니다.

(SRP를 준수하더라도 클라이언트에서 필요하지 않다면

인터페이스를 다시 분리해야할 수도 있습니다.)

 

그리고 과도한 ISP 분리의 부작용은 DB 정규화 원칙과 비슷합니다.

(분리해야 의미적으로 타당하나, 과도하면 시스템이 복잡해지고 오히려 느려진다.)

 

그래서 인터페이스를 설계할때 어떤 의미와 단위로 사용될지를 분명히하거나,

인터페이스를 리펙토링한다면 사용 목적을 생각하고 분리해야합니다.

(하지만 이미 널리 사용되고 있는 인터페이스라면 변화를 최소화해야합니다.)

'Java' 카테고리의 다른 글

[객체지향설계원칙] DIP  (0) 2024.05.31
[객체지향설계원칙] LSP  (0) 2024.05.29
[객체지향설계원칙] OCP  (0) 2024.05.23
추상클래스, 인터페이스의 문법적,의미적 차이  (0) 2024.05.22
[객체지향설계원칙] SRP  (0) 2024.05.14