2023년 11월 5일 작성

ISP - Interface 분리 원칙

client는 자신이 이용하지 않는 method에 의존하지 않아야 합니다.

ISP (Interface Segregation Principle) : Interface 분리 원칙

client가 자신이 이용하지 않는 method에 의존하지 않아야 합니다.
  • client는 객체를 사용하는 소비자를 의미합니다.
    • 예를 들어, method, class 등.
  • 기능이 많은 큰 덩어리의 interface를 구현하는 대신, 구체적이고 작은 단위들로 분리시켜 사용합니다.
    • client들이 꼭 필요한 method들만 이용할 수 있도록 합니다.
  • ISP를 통해 system의 내부 의존성을 약화시켜 refactoring, 수정, 재배포를 쉽게 할 수 있습니다.
    • 결합도를 낮춘다는 말과 같습니다.
  • Interface 분리 원칙은 범용적인 interface보다는 client(사용자)가 실제로 사용하는 interface를 만들어야 한다는 원칙입니다.
    • interface를 잘게 분리함으로써, client의 목적과 용도에 적합한 interface만을 제공할 수 있습니다.
    • ISP를 적용하면 class의 기능을 쉽게 파악할 수 있고, 객체의 기능을 유연하게 확장/수정할 수 있게 됩니다.
  • 만약 interface의 추상 method들을 범용적으로 여러 가지를 구현하면, 그 interface를 상속받는 class는 자신이 사용하지 않는 interface도 억지고 구현해야 합니다.
    • 또한 사용하지도 않는 interface의 추상 method가 변경된다면, class에서도 수정이 필요하게 됩니다.

ISP(Interface 분리 원칙)와 SRP(단일 책임 원칙)

 

  • Interface 분리 원칙은 단일 책임 원칙과 비슷합니다.
    • SRP는 class의 단일 책임을 강조합니다.
    • ISP는 interface의 단일 책임을 강조합니다.
  • 다만, interface는 class와 달리 추상화이기 때문에 여러 역할을 가지는 데에 제약이 없습니다.

  • SRP의 목표가 class 분리를 통해 이루어진다면, ISP의 목표는 interface 분리를 통해 이루어집니다.
    • SRP를 적용할 때 class 책임 범위를 분리하는 기준이 다르듯이, interface를 분리하는 기준도 상황에 따라 다릅니다.
    • ISP 적용의 핵심은 관련된 기능을 하나의 interface에 모으되, 지나치게 커지지 않도록 크기를 제한하라는 점입니다.

Example : Interface 분리하기

Interface 분리 전

  • 고양이는 “멍멍” 짖지 않기 때문에, IPets interface에 bark() method가 있는 것은 적합하지 않습니다.
classDiagram

class IPets {
    <<interface>>
    isFriendly()
    getName()
    bark()
}
class Dog {
    isFriendly()
    getName()
    bark()
}
class Cat {
    isFriendly()
    getName()
    bark()
}

IPets <|.. Dog
IPets <|.. Cat

Interface 분리 후

  • IPets interface에서 고양이와 개의 특징을 분리하여 IPetDogsIPetCats interface를 따로 만들었기 때문에, IPets interface는 토끼를 만들 때 사용할 수도 있습니다.
classDiagram

class IPets {
    <<interface>>
    isFriendly()
    getName()
}
class IPetDogs {
    <<interface>>
    bark()
}
class IPetCats {
    <<interface>>
    meow()
    purr()
}
class Dog 
class Cat
class Rabbit

IPets <|.. IPetDogs
IPets <|.. IPetCats
IPetDogs <|.. Dog
IPetCats <|.. Cat
IPets <|.. Rabbit

ISP 적용해보기

적용 전

  • 스마트폰 종류에 대한 class를 구현하기 위해 interface로 스마트폰을 추상화합니다.
  • 스마트폰 interface는 통화나 message 기능 외에도, 무선 충전, AR viewer, 생체 인식 등의 최신 기능을 포함하고 있습니다.

  • 만일 Galaxy S20이나 S21 class를 구현한다면, 최신 기종이기 때문에 객체의 동작 모두가 필요하므로 ISP 원칙을 만족하게 됩니다.

  • 그러나 Galaxy S8 등의 구형 기종 class에는 최신 기능(무선 충전, AR viewer, 생체인식)이 들어가지 않습니다.
  • Galaxy S8 class는 추상 method 구현 규칙에 따라 반드시 overriding하여 구현해야 합니다.
    • 보통 method 내부를 빈 공간으로 두거나, 혹은 예외(Exception)을 발생시키도록 구현합니다.
  • 필요하지도 않은 기능을 어쩔 수 없이 구현해야 하기 때문에, 이는 ISP를 위반합니다.
classDiagram

class ISmartPhone {
    <<interface>>
    call(String) void
    message(String, String) void
    wirelessCharge() void
    AR() void
    biometrics() void
}

class GalaxyS8 {
    call(String) void
    message(String, String) void
    wirelessCharge() void
    AR() void
    biometrics() void
}
class GalaxyS20 {
    call(String) void
    message(String, String) void
    wirelessCharge() void
    AR() void
    biometrics() void
}
class GalaxyS21 {
    call(String) void
    message(String, String) void
    wirelessCharge() void
    AR() void
    biometrics() void
}

ISmartPhone <|.. GalaxyS8
ISmartPhone <|.. GalaxyS20
ISmartPhone <|.. GalaxyS21
interface ISmartPhone {
    void call(String number);    // 통화
    void message(String number, String text);    // message 전송
    void wirelessCharge();    // 무선 충전
    void AR();    // 증강 현실
    void biometrics();    // 생체 인식
}
class GalaxyS20 implements ISmartPhone {
    public void call(String number) {
        // ...
    }

    public void message(String number, String text) {
        // ...
    }

    public void wirelessCharge() {
        // ...
    }

    public void AR() {
        // ...
    }

    public void biometrics() {
        // ...
    }
}

class GalaxyS21 implements ISmartPhone {
    public void call(String number) {
        // ...
    }

    public void message(String number, String text) {
        // ...
    }

    public void wirelessCharge() {
        // ...
    }

    public void AR() {
        // ...
    }

    public void biometrics() {
        // ...
    }
}

// 옛날 기종은 최신 기능을 지원하지 않습니다.
class GalaxyS8 implements ISmartPhone {
    public void call(String number) {
        // ...
    }

    public void message(String number, String text) {
        // ...
    }

    public void wirelessCharge() {
        System.out.println("지원하지 않는 기능입니다.");
    }

    public void AR() {
        System.out.println("지원하지 않는 기능입니다.");
    }

    public void biometrics() {
        System.out.println("지원하지 않는 기능입니다.");
    }
}

적용 후

  • 각각의 기능에 맞게 interface를 잘게 분리합니다.
  • 그리고 해당 스마트폰에 지원되는 기능(interface)만을 구현(implements)합니다.
classDiagram

class IPhone {
    <<interface>>
    call(String) void
    message(String, String) void
}
class WirelessChargable {
    <<interface>>
    wirelessCharge() void
}
class ARable {
    <<interface>>
    AR() void
}
class Biometricsable {
    <<interface>>
    biometrics() void
}
class GalaxyS21 {
    call(String) void
    message(String, String) void
    wirelessCharge() void
    AR() void
    biometrics() void
}
class GalaxyS8 {
    call(String) void
    message(String, String) void
}

IPhone <|.. GalaxyS8
IPhone <|.. GalaxyS21
WirelessChargable <|.. GalaxyS21
ARable <|.. GalaxyS21
Biometricsable <|.. GalaxyS21
interface IPhone {
    void call(String number);    // 통화
    void message(String number, String text);    // message 전송
}

interface WirelessChargable {
    void wirelessCharge();    // 무선 충전
}

interface ARable {
    void AR();    // 증강 현실
}

interface Biometricsable {
    void biometrics();    // 생체 인식
}
class GalaxyS21 implements IPhone, WirelessChargable, ARable, Biometricsable {
    public void call(String number) {
        // ...
    }

    public void message(String number, String text) {
        // ...
    }

    public void wirelessCharge() {
        // ...
    }

    public void AR() {
        // ...
    }

    public void biometrics() {
        // ...
    }
}

class GalaxyS8 implements IPhone {
    public void call(String number) {
        // ...
    }

    public void message(String number, String text) {
        // ...
    }
}

ISP 원칙 적용 주의 사항

SRP와 구분하기

 

  • SRP가 class의 단일 책임 원칙이라면, ISP는 interface의 단일 책임 원칙입니다.

  • 하지만 SRP를 만족한다고 해서 반드시 ISP가 성립되는 것은 아닙니다.

    • 책임을 준수하여 SRP를 만족하더라도, ISP는 만족하지 않는 경우가 있습니다.

SRP는 만족하지만 ISP는 만족하지 않는 경우

classDiagram

class 게시판 {
    <<interface>>
    글쓰기()
    읽기()
    수정()
    삭제()
}
class 일반사용자 {
    글쓰기()
    읽기()
    수정()
}
class 관리자 {
    글쓰기()
    읽기()
    수정()
    삭제()
}
  • 게시판 interface에는 글쓰기, 읽기, 수정, 삭제라는 추상 method가 정의되어 있습니다.
  • 이 method들은 모두 게시판에 필요한 기능들이며, 단일 책임 원칙에 위배되지 않습니다.

  • SRP는 준수하지만 ISP는 위반하고 있습니다.
    • 일반 사용자는 게시글 삭제 기능를 사용할 수 없기 때문입니다.
  • 언뜻 보면 책임을 잘 구성해 놓은 것 같지만, 실제 적용되는 객체에겐 부합되지 않을 수 있기 때문에 책임을 더 분리해야 합니다.

Interface 분리는 한 번만 하기

  • 본래 interface라는 것은 변경이 잦으면 안 되는 정책과 같은 개념입니다.
  • 이미 interace를 분리하여 구현한 project에서 또 interface를 분리하면, 해당 interface를 구현하고 있는 class들과 interface를 사용하고 있는 client(사용자)에서 문제가 생길 수 있습니다.
  • 따라서 처음 설계부터 기능의 변화를 고려하고 interface를 설계해야 합니다.

Reference


목차