좋은 객체지향설계의 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받는 클래스들에서 구현한다.
쉬운 예제였지만 예제가 어려웠거나 해답이 근거리에 없었다면 힘들었을만한 예제였다.
'자바' 카테고리의 다른 글
break; continue; return; 무슨 차이일까? (0) | 2023.09.23 |
---|---|
자바 성능 튜닝 이야기(1) (0) | 2023.07.28 |
TIL - 좋은 객체지향적 설계원칙, SOLID(3) : LSP (0) | 2023.01.27 |
TIL - OOP 요약 (0) | 2023.01.25 |
TIL - Math.Random은 정말 난수를 리턴할까? (0) | 2023.01.24 |