좋은 객체지향설계의 5가지 원칙 : SOLID

  • SRP : 단일 책임 원칙(single responsibility principle)
  • OCP : 개방-폐쇄 원칙(Open/closed principle)
  • LSP : 리스코프 치환 원칙(Liskov substitution principle)
  • ISP : 인터페이스 분리 원칙(Interface segregation principle)
  • DIP : 의존관계 역전 원칙(Dependency inversion principle)

ISP : 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.
로버트 C. 마틴(밥아저씨)
  • 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다.
  • 예를 들어 자동차 인터페이스 → 운전 인터페이스, 정비 인터페이스 등으로 분리
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.
  • 인터페이스는 여러개를 상속받을 수 있기 때문에 좋은 코드를 만들 수 있다.
    • 그러나 너무 분리하면 코드를 다 까봐야해서 어느정도 선까지 구체화할지, 추상화할지를 정해야 한다.

 

ISP 원칙을 지키지 못한 계산기를 살펴보자.

public class Calculator {
    public int calculate(AbstractOperation operation, int firstNumber, int secondNumber){
        return operation.operate(firstNumber, secondNumber);
    }
}

계산기 클래스

// 연산 결과만 출력
public class DisplayTypeA extends Calculator implements Display {

    @Override
    public void displayResult(AbstractOperation operation, int firstNumber, int secondNumber) {
        int answer = operation.operate(firstNumber, secondNumber);
        System.out.println(answer);
    }

    @Override
    public void displayResultWithOperator(AbstractOperation operation, int firstNumber, int secondNumber) throws Exception {
        throw new Exception("동작 불가");
    }
}

DisplayA 클래스 : 결과만 출력하고 싶다.

// 연산 과정을 포함한 출력
public class DisplayTypeB extends Calculator implements Display {

    @Override
    public void displayResult(AbstractOperation operation, int firstNumber, int secondNumber) throws Exception {
        throw new Exception("동작 불가");
    }

    @Override
    public void displayResultWithOperator(AbstractOperation operation, int firstNumber, int secondNumber) {
        int answer = operation.operate(firstNumber, secondNumber);
        String operator = operation.getOperator();
        System.out.println(firstNumber + " " + operator + " " + secondNumber + " = " + answer);
    }
}

DisplayB 클래스 : 연산 과정까지 출력하고 싶다.

public class Client {
    public static void main(String[] args) throws Exception {
        // 연산 결과만 출력
        DisplayTypeA displayTypeA = new DisplayTypeA();
        displayTypeA.displayResult(new AddOperation(), firNum, secNum);
        // displayTypeA.displayResultWithOperator(new AddOperation(), firNum, secNum); // Error 발생

        // 연산 과정까지 출력
        DisplayTypeB displayTypeB = new DisplayTypeB();
        displayTypeB.displayResultWithOperator(new AddOperation(), firNum, secNum);
        // displayTypeB.displayResult(new AddOperation(), firNum, secNum); // Error 발생
    }
}

클라이언트 클래스

public abstract class AbstractOperation {
    public abstract int operate(int firstNumber, int secondNumber);
    public abstract String getOperator();
}

AbstractOperation 클래스

public class AddOperation extends AbstractOperation {
    @Override
    public int operate(int firstNumber, int secondNumber) {
        return firstNumber + secondNumber;
    }

    @Override
    public String getOperator() {
        return "+";
    }
}

AddOperation 클래스

 

문제점

1. 필요하지 않은 기능을 강제로 구현해야 한다.

  -> DisplayType 클래스들은 전부 Calculator의 기능을 이용하지 않는데도 Calculator를 상속받고, Display Interface 내부의 두가지 추상 메서드를 구현해야만 한다.

2. 필요하지 않은 혹은 사용 못 하는 기능이 강제로 구현되어 사용하지 못하도록 예외처리를 했다.

 -> Display 인터페이스에 DisplayResult와 DisplayWithoutResult 메서드가 공존하다보니 Display 메서드를 implement하면 쓰고싶지 않은 기능에다 Exception을 추가해줬다.

 

 

다시 돌아가서

ISP를 위반한 계산기에서는 무엇을 해 줘야 할까? 답은 다음과 같다.

  • 필요하지 않은 기능을 강제로 구현하지 않도록 인터페이스를 분리한다.
  • 연산 결과를 보여주는 방법마다 인터페이스를 구현합니다.

 

어떻게?

사용하지 않는 Calculator 클래스는 지운다.

Display 인터페이스를 DisplayWithResult, DisplayWithoutResult로 쪼갠 뒤 그 안에 각각 추상 메서드를 작성하고

WithResult, WithoutResult를 각각 Impl받는 클래스들에서 구현한다.

 

쉬운 예제였지만 예제가 어려웠거나 해답이 근거리에 없었다면 힘들었을만한 예제였다.

 

+ Recent posts