ISP: Interface Segregation Principle
- 한 줄 요약 - 인터페이스도 작게 쪼개자
1. What 인터페이스 분리?
객체는 자신이 사용하는 기능(메서드)에만 의존해야 한다는 원칙이다.
다른 말로, 인터페이스는 그 인터페이스를 사용하는 클라이언트(객체)를 기준으로 분리해야 한다.
2. Why 중요한 이유?
클래스(인터페이스)가 소유하고 있는 책임의 크기를 줄일 수 있고 이를 통해 관심사의 분리라는 이점과 확장성에 용이함을 얻을 수 있기 때문에 중요하다.
위 사진을 보면 우리는 이동수단 객체들의 AutoPilot 기능을 컨트롤 하기 위해서 IAutoPilot 인터페이스를 정의하였고 left, right, backward, forward 메서드를 선언 하였다.
Car 클래스가 IAutoPilot를 상속하는 것은 문제가 되지 않는다. 왜냐하면 모든 메서드를 사용하기 때문이다. 그런데 Train 클래스에서 문제가 발생한다. Train 객체는 left,right로 이동할 수 없기 때문에 해당 메서드를 사용하지 않는다. 그럼 Train은 사용하지도 않는 method와 의존성을 맺게 되는 것이다.
이렇게 되면 Train은 left()와 right()를 사용하지 않음에도 불구하고 만약 left()에 변경이 일어나면 함께 변경되고 재컴파일 및 재배포 과정을 거쳐야 하는 문제가 발생한다.
이 원칙을 위반하게 되는 이유는 상속 받은 IAutoPilot인터페이스의 규모가 필요보다 크기 때문에 발생한 문제이다.
3. How 적용
목표
객체에 불필요한 메서드를 구현하지 않게 하자!
예제 코드 - 원칙을 위반한 케이스
모든 이동 수단에 AutoPilot 기능을 추가하기 위해서 우리는 AutoPilot 관련 기능을 인터페이스화 시켰다.
public interface AutoPilot {
public void left(int distance, int perMile);
public void right(int distance);
public void backward(int distance);
public void forward(int distance);
}
앞뒤좌우로 갈 수 있는 자동차 객체는 AutoPilot 인터페이스에서 정의한 모든 기능을 사용하기 때문에 모든 메서드에 대해서 의존해도 문제가 없다.
package com.wanted.preonboarding.clean.code.solid.isp;
public class Car implements AutoPilot {
@Override
public void left(int distance, int perMile) {
// todo
}
@Override
public void right(int distance) {
// todo
}
@Override
public void backward(int distance) {
// todo
}
@Override
public void forward(int distance) {
// todo
}
}
하지만, 아래 코드처럼 Train은 사용하지 않는 left()와 right()를 의존하고 있는 상태이다. 이런 경우, 인터페이스의 책임을 분산해서 좀 더 나은 코드로 만들어볼 수 있다.
package com.wanted.preonboarding.clean.code.solid.isp;
public class Train implements AutoPilot{
@Override
public void left(int distance, int perMile) {
// Train can't do it
// 인터페이스 구현으로 불필요한 메서드의 의존하고 있음.
}
@Override
public void right(int distance) {
// Train can't do it
// 인터페이스 구현으로 불필요한 메서드의 의존하고 있음.
}
@Override
public void backward(int distance) {
}
@Override
public void forward(int distance) {
}
}
예제 코드 - 원칙을 지킨 케이스
혼자 가지고 있던 인터페이스의 책임을 쪼개서 작은 단위로 책임을 할당한다.
package com.wanted.preonboarding.clean.code.solid.isp_correct;
public interface IGoforward {
void goForward(int d);
}
package com.wanted.preonboarding.clean.code.solid.isp_correct;
public interface IGobackward {
void goBackward(int d);
}
그래서 책임이 쪼개진 인터페이스을 통해서 필요한 기능만 구현 받을 수 있도록 적용한다.
package com.wanted.preonboarding.clean.code.solid.isp;
public class Train implements AutoPilot{
@Override
public void left(int distance) {
// Train can't do it
// 인터페이스 구현으로 불필요한 메서드의 의존하고 있음.
}
@Override
public void right(int distance) {
// Train can't do it
// 인터페이스 구현으로 불필요한 메서드의 의존하고 있음.
}
@Override
public void backward(int distance) {
}
@Override
public void forward(int distance) {
}
}
DIP: Dependency Inversion Principle
- 한 줄 요약 - 추상화 or 인터페이스에 의존해라(feat. OCP)
1. What 의존관계 역전이란?
A객체에서 기능 구현을 위해서 B객체의 C메서드를 사용해야 하는 경우, A객체는 B객체에 대한 정보를 알고 있어야 하기 때문에 A객체는 내부적으로 B클래스를 의존하게 되고 이때 의존 관계를 맺는데 구체화된 객체와 의존관계를 맺는 것이 아니라, 추상화된 클래스와 의존 관계를 맺어야 한다는 원칙이다.
의존 관계를 맺을 때 변하기 쉬운 것에 의존하기 보다는 변하지 않는 것에 의존하라!
그렇다면 변하기 쉬운 것이란? 무엇이고 변하지 않는 것이란? 무엇일까
예를 들어, 아이가 장난감을 가지고 노는 경우를 생각해본다면 이 경우, 아이는 다양한 “장난감”을 가지고 놀 수 있다. 어떤 경우에는 로봇 장난감, 어떤 경우에는 인형, 또 다른 경우에는 퍼즐을 가지고 놀 수 있다. 이때 실제 가지고 노는 구체적인 장난감(ex. 로봇, 인형, 퍼즐)이 쉽게 변하기 쉬운 대상이고, 아이가 장난감을 가지고 노는 사실은 변하기 어려운 것이다.
그래서 아이(Kid) 객체는 구체적인 장난감(하위 타입, Robot)에 의존하면 안되고 쉽게 변화지 않는 장난감(추상화, 상위타입)을 의존 해야한다는 것을 주장하는 원칙이다.
// 인터페이스
public interface Toy {}
class Robot implements Toy {}
class Lego implements Toy {}
class Doll implements Toy {}
// Client: 장난감을 사용하는 사람
public class Kid {
Toy toy;
public void setToy(Toy toy) {
this.toy = toy;
}
public void play(){
// Let's show time
}
}
// Main
public class Main {
public static void main(String[] args) {
Kid boy = new Kid();
Toy toy = new Robot();
boy.setToy(toy);
boy.play();
// 2. 아이가 레고를 가지고 놀 때
Toy toy = new Lego();
boy.setToy(toy);
boy.play();
}
}
2. Why 중요한 이유?
첫번째로, 변화에 유연하게 대처할 수 있는 구조방식을 만들 수 있게 해주는 원칙이며 OCP를 지킬 수 있도록 뒷받침 해주는 원칙이다.
두번째로, 하위 타입의 구체적인 내용에 클라이언트가 의존하게 되어 하위 타입에 변화가 있을 때마다 클라이언트나 상위 모듈의 코드를 자주 수정해야하는 사항이 발생하기 때문이다.
3. How 적용?
규칙
- 상위 타입은 하위 타입에 의존해서는 안된다.
- 추상화는 세부 사항에 의존해서 안된다.
예제 코드 - 원칙을 위반한 케이스
간편결제 서비스를 간략하게 표현한 PayService이다. 간편결제 종류는 삼성페이, 애플페이, 페이코가 있지만 할인 혜택을 받기 위해서 서비스를 하나만 사용하지 않는다. 그렇기 때문에 결제 수단을 적절하게 변경해줘야 한다.
그러나, PayService 클래스는 객체를 생성하는 시점에 SamsungPay에만 의존하도록 되어있기 때문에 변경이 쉽지 않다. DIP을 위반함으로써 생기고 있는 문제이다.
package com.wanted.preonboarding.clean.code.solid.dip;
public class PayService {
SamsungPay samsungPay; // 의존 저수준 객체
ApplyPay applyPay; // 의존 저수준 객체
Payco payco; // 의존 저수준 객체
Naverpay naverPay;
public PayService(SamsungPay s, ApplyPay a, Payco p, Naverpay n){
samsungPay = s;
applyPay = a;
payco = p;
naverPay = n;
}
public void pay(String type, int toAmount){
System.out.println("간편결제 서비스 호출");
switch(type){
case "samsung":
if(samsungPay != null)
samsungPay.pay(toAmount);
break;
case "apply":
applyPay.pay(toAmount);
break;
case "payco":
payco.pay(toAmount);
break;
case "naverpay":
naverPay.pay(toAmount);
break;
default:
throwsa NotfoundException("지원하지 않는 결제수단 입니다");
}
}
package com.wanted.preonboarding.clean.code.solid.dip;
public class SamsungPay {
public boolean connect(){
if(!auth())
return false;
// To Do Connection;
return true;
}
private boolean auth(){
return true;
}
public void pay(int amount){
System.out.println("PAY");
}
}
예제 코드 - 원칙을 지킨 케이스
PayService 클래스의 기존 SamsungPay 타입의 인스턴스 변수 보다 더 고수준 모듈인 Payment 인터페이스 타입으로 변경하였다. 이렇게 변경함으로써 PayService는 적절하게 간편 결제수단을 변경할 수 있게 되었다.
package com.wanted.preonboarding.clean.code.solid.dip;
public interface Payment {
public boolean pay(int amount);
}
package com.wanted.preonboarding.clean.code.solid.dip;
public class PayService {
Payment payment; // 의존 저수준 객체
public PayService(Payment p){ // 구현체 생성하고 결정할 때는 switch 써!
payment = p;
}
public void pay(int toAmount){
System.out.print("간편결제 서비스 호출");
payment.pay(toAmount);
}
}
package com.wanted.preonboarding.clean.code.solid.dip;
public class SamsungPay implements Payment {
public boolean connect(){
if(!auth())
return false;
// To Do Connection;
return true;
}
private boolean auth(){
return true;
}
@Override
public boolean pay(int amount){
System.out.println("PAY");
return true;
}
}
'CS' 카테고리의 다른 글
디자인패턴 기반으로 알아보는 SOLID (2) | 2023.10.17 |
---|---|
컴퓨터특강 2주차 : OOP (2) | 2023.10.03 |
컴퓨터특강 1주차 : Clean Code (1) | 2023.09.18 |
[가상면접 사례로 배우는 대규모 시스템 설계 기초]사용자 수에 따른 규모 확장성사용자 수에 따른 규모 확장성 (0) | 2023.09.04 |