다메다메의 기술블로그

인터페이스 분리 원칙 본문

CS 지식/프로그래밍

인터페이스 분리 원칙

다메 2022. 11. 3. 09:22

인터페이스 분리 원칙 (Interface Segregation Principle) 

인터페이스 분리 원칙이란 객체는 자신이 호출하지 않는 메소드에 의존하지 않아야한다는 원칙이다.

구현할 객체에게 무의미한 메소드의 구현을 방지하기 위해 반드시 필요한 메소드만을 상속/구현하도록 권고한다. 만약 상속할 객체의 규모가 너무 크다면, 해당 객체의 메소드를 작은 인터페이스로 나누는 것이 좋다.

 

코드로 보는 인터페이스 분리 원칙 

예시를 통해 인터페이스 분리 원칙을 준수하지 않은 경우와 준수한 경우 어떠한 차이가 있는지 코드를 통해 알아보자.

 

인터페이스 분리 원칙을 준수하지 않은 코드

예를 들어, 스마트폰이라는 객체가 있다고 가정하자. 이 스마트폰 객체는 비교적 최신에 나온 덕분에 일반적인 스마트폰 기능 외에도 무선 충전, AR 뷰어, 생체인식 등의 다채로운 기능을 포함하고 있다.

이를 가지고 S20을 구현하면 스마트폰 객체의 동작 모두가 필요하므로 ISP를 만족한다. 그러나 S2를 구현할 경우, 무선 충전, 생체인식과 같은 기능을 제공하지 않는다. 그럼에도 불구하고 부모 객체인 스마트폰에 이러한 인터페이스가 포함되어 있으므로, S2 입장에서는 필요하지도 않은 기능을 구현해야하는 낭비가 발생한다.

 

abstract public class SmartPhone
{

    public void call(String number) {}
    
    public void message(String number, String text) {}
    
    abstract public void wirelessCharge() {}
    
    abstract public void ar() {}
    
    abstract public void biometrics();
}

 

 

public class S20 extends SmartPhone
{
    @Override
    public void biometrics()
    {
        System.out.println("S20 생체인식 기능");
    }
}

S20은 모든 기능이 필요하기 때문에, SmartPhone의 모든 메소드를 사용해야하므로, 불필요한 메소드가 없는 상태다.

 

public class S2 extends SmartPhone
{
    @Override
    public void wirelessCharge()
    {
        System.out.println("지원 불가능한 기기");
    }
    
    @Override
    public void ar()
    {
        System.out.println("지원 불가능한 기기");
    }
    
    @Override
    public void biometrics()
    {
        System.out.println("지원 불가능한 기기");
    }
}

S2는 무선충전, AR, 생체인식이 지원되지 않는 기기다. 그럼에도 불구하고 SmartPhone의 상속으로 인해 해당 기능의 메소드를 강제로 상속받게 된다. 더군다나 biometrics()의 경우 추상 메소드이므로 필요하지도 않은 기능을 구현까지 해야한다. 이러한 상속의 특징은 부모 객체의 규모가 매우 클 경우, 개발 편의성의 극심한 저하로 이뤄진다. 필요하지도 않은 수십개의 메소드를 일일히 오버라이딩하여 적절한 처리를 해준다고 생각해보자.

 

인터페이스 분리 원칙을 준수한 코드

객체의 특성을 확장을 통해 다른 객체를 편하게 구현하기 위한 것이 상속인데, 위와 같은 상황은 전혀 편하지 않다. 이는 부모 객체의 설계가 잘 못 됐을 수도 있고, 취지에 맞지 않는 객체를 상속했을 수도 있다. 이유야 어찌됐든 해결해야한다는 사실엔 변함이 없다.

그렇다면 이 현상은 어떻게 해결할 수 있을까? 객체의 메소드를 각각 인터페이스로 만들면 된다. 각 객체는 필요한 인스턴스만 상속하면 되므로 필요한 동작만을 상속/구현할 수 있을 것이다.

 

public class SmartPhone
{
    public void call(String number)
    {
        System.out.println(number + " 통화 연결");
    }
    
    public void message(String number, String text)
    {
        System.out.println(number + ": " + text);
    }
}

SmartPhone 객체는 모든 스마트폰에 적용되는 보편적인 동작만을 가지도록 변경했다.

 

public interface WirelessChargable
{
    void wirelessCharge();
}

public interface ARable
{
    void ar();
}

public interface Biometricsable
{
    void biometrics();
}

각 기능의 인터페이스는 위와 같다. 원래 SmartPhone의 객체의 메소드였던 각 기능은 인터페이스 단위로 나뉘어졌음에 주목하자.

이를 통해 S20과 S2 모두 필요한 객체만을 상속받아 구현할 수 있을 것이다.

 

public class S20 extends SmartPhone implements WirelessChargable, ARable, Biometricsable
{
    @Override
    public void wirelessCharge()
    {
        System.out.println("무선충전 기능");
    }
    
    @Override
    public void ar()
    {
        System.out.println("AR 기능");
    }
    
    @Override
    public void biometrics()
    {
        System.out.println("생체인식 기능");
    }
}

S20 객체의 코드다. SmartPhone을 상속받았으며, 인터페이스로 WirelessChargable, ARable, Biometricsable을 모두 상속받아 구현하고 있다.

 

public class S2 extends SmartPhone
{
    @Override
    public void message(String number, String text)
    {
        System.out.println("In S2");
        
        super.message(number, text);
    }
}

S2는 특수 기능이 구현되어있지 않으므로, 기본적인 SmartPhone 객체만을 상속받아 구현된다.

인터페이스는 다중 상속을 지원하므로, 필요한 기능을 인터페이스로 나누면 해당 기능만을 상속받을 수 있다. 그 밖에 추후 업데이트 등을 통해 추가적인 기능이 탑재된다면, 같은 원리로 인터페이스를 설계해서 사용하면 필요한 객체에 필요한 기능을 쉽게 추가할 수 있다.

 

정리

인터페이스 분리 원칙은 객체가 반드시 필요한 기능만을 가지도록 제한하는 원칙이다. 불필요한 기능의 상속/구현을 최대한 방지함으로써 객체의 불필요한 책임을 제가한다. 큰 규모의 객체는 필요에 따라 인터페이스로 잘게 나누어 확장성을 향상시킨다.

객체를 상속할 땐 해당 객체가 상속 받는 객체에 적합한 객체인지, 의존적인 기능이 없는 지 판단하여 올바른 객체를 구현, 상속하도록 하자.

 

📆 작성일

2021-08-16 Mon 19:24:57

Comments