객체 지향 SOLID 원칙 - ISP
ISP 는 Interface Segregation Principle (인터페이스 분리 원칙) 준말로 아래와 같은 원칙을 가지고 있습니다.
클라이언트가 자신이 이용하지 않는 메서드에 의존하면 안된다.
클라이언트가 목적과 용도에 맞게 인터페이스를 제공하여 사용하지 않는 메소드를 제거해야 한다는 것입니다. 즉 사용하지 않는 메소드는 상속 받지도 말고 구현 하지도 말아라 입니다.
이는 인터페이스를 더 작고 특화된 단위로 분리함으로써 클라이언트의 의존성을 최소화하고 유연성을 높이는 것을 목표로 합니다.
하나의 큰 인터페이스 대신 여러 개의 작은 인터페이스로 나누어 각 클라이언트가 필요한 기능 만을 구현할 수 있도록 하는 것입니다. 이렇게 함으로써 시스템의 결합도를 낮추고 유지보수성을 향상시킬 수 있습니다.
차의 옵션 기능 예를 들어 설명해보겠습니다. 아래 사진은 K5 의 패키지 옵션입니다.
차량의 등급은 별로 기본으로 제공되는 패키지 옵션이 다릅니다. 다만 위 옵션은 너무 많으니 아래와 같이 추서 정리하겠습니다.
프레티지 | 노블레스 | 시그니처 | |
LED 헤드렘프 | O | O | O |
컴포트 | O | O | O |
빌트인캠 | X | O | O |
클러스터 팩 | X | O | O |
프리미엄사운드 | X | X | O |
이제 위의 예시를 바탕으로 ISP 원칙을 위반하는 코드와 이를 개선한 코드를 살펴보겠습니다. 먼저, ISP 원칙을 위반하는 코드는 다음과 같습니다:
public interface SmartCar {
// LED 헤드램프
void LEDHeadLamp();
// 컴포트
void comfort();
// 빌트인캠
void builtInCam();
// 클러스터팩
void clusterPack();
// 프리미엄 사운드
void premiumSound();
}
public class PretagePack implements SmartCar {
@Override
public void LEDHeadLamp() { System.out.println("LED헤드렘프 옵션 적용(0)"); }
@Override
public void comfort() { System.out.println("컴포트 옵션 적용(0)"); }
@Override
public void builtInCam() { System.out.println("빌트인탬을 적용할수가 없습니다.(X)"); }
@Override
public void clusterPack() {System.out.println("클러스터팩을 적용할수가 없습니다.(X)");}
@Override
public void driveWise() {System.out.println("드라이브와이즈를 적용할수가 없습니다.(X)");}
@Override
public void premiumSound() {System.out.println("프리미엄사운드를 적용할수가 없습니다.(X)");}
}
public class NoblessePack implements SmartCar{
@Override
public void LEDHeadLamp() { System.out.println("LED헤드렘프 옵션 적용(0)"); }
@Override
public void comfort() { System.out.println("컴포트 옵션 적용(0)"); }
@Override
public void builtInCam() { System.out.println("빌트인탬을 적용(O)"); }
@Override
public void clusterPack() {System.out.println("클러스터팩을 적용(O)");}
@Override
public void driveWise() {System.out.println("드라이브와이즈를 적용할수가 없습니다.(X)");}
@Override
public void premiumSound() {System.out.println("프리미엄사운드를 적용할수가 없습니다.(X)");}
}
public class SignaturePack implements SmartCar {
@Override
public void LEDHeadLamp() { System.out.println("LED헤드렘프 옵션 적용(0)"); }
@Override
public void comfort() { System.out.println("컴포트 옵션 적용(0)"); }
@Override
public void builtInCam() { System.out.println("빌트인탬을 적용(O)"); }
@Override
public void clusterPack() {System.out.println("클러스터팩을 적용(O)");}
@Override
public void driveWise() {System.out.println("드라이브와이즈를 적용할수가 없습니다.(O)");}
@Override
public void premiumSound() {System.out.println("프리미엄사운드를 적용할수가 없습니다.(O)");}
}
위의 코드에서 볼 수 있듯이, 각 패키지 클래스는 SmartCar 인터페이스의 모든 메서드를 구현해야 합니다. 그러나 이는 ISP 원칙을 위반하고 있습니다. 예를 들어, PretagePack 클래스는 실제로 사용하지 않는 builtInCam(), clusterPack() 등의 메서드를 구현해야 하는 상황입니다.
이러한 문제를 해결하기 위해 ISP 원칙을 적용하여 인터페이스를 더 작고 특화된 단위로 분리해 보겠습니다.
위 그림을 참고했을 때 각 기능 별로 인터페이스를 분리하고, 각 패키지 클래스가 필요한 인터페이스만 구현을 했습니다. 이렇게 함으로써 각 클래스는 자신이 실제로 사용하는 기능만을 구현하게 되어 불필요한 의존성을 제거할 수 있습니다. 아래는 ISP 원칙을 적용한 개선된 코드 예시입니다:
public interface LEDHeadLamp { void settingLEDHeadUpLamp(); }
public interface BuiltInCamOption { void builtInCamCharge(); }
public interface ComfortPackage { void settingComfort(); }
public interface ClusterPack { void clusterPackCharge(); }
public interface SoundOptions { void premiumSound(); }
public class PretagePack implements LEDHeadLamp,ComfortPackage {
@Override
public void settingComfort() {System.out.println("컴포트 옵션 적용(0)");}
@Override
public void settingLEDHeadUpLamp() {System.out.println("LEDHeadUpLamp 옵션 석용");}
}
public class NoblessePack implements LEDHeadLamp,ComfortPackage,BuiltInCamOption,ClusterPack {
@Override
public void settingComfort() {System.out.println("컴포트 옵션 적용(0)");}
@Override
public void settingLEDHeadUpLamp() {System.out.println("LEDHeadUpLamp 옵션 석용");}
@Override
public void builtInCamCharge() {System.out.println("builtInCamCharge 옵션 석용");}
@Override
public void clusterPackCharge() {System.out.println("clusterPackCharge 옵션 석용");}
}
public class SignaturePack implements LEDHeadLamp,ComfortPackage,BuiltInCamOption,ClusterPack,SoundOptions{
@Override
public void settingComfort() {System.out.println("컴포트 옵션 적용(0)");}
@Override
public void settingLEDHeadUpLamp() {System.out.println("LEDHeadUpLamp 옵션 석용");}
@Override
public void builtInCamCharge() {System.out.println("builtInCamCharge 옵션 석용");}
@Override
public void clusterPackCharge() {System.out.println("clusterPackCharge 옵션 석용");}
@Override
public void premiumSound() {System.out.println("premiumSound 옵션 석용");}
}
인터페이스를 분리함으로써, 각 패키지 클래스는 자신이 실제로 제공하는 기능만을 구현하게 됩니다. 이는 ISP 원칙을 준수하며, 코드의 유연성과 재사용성을 높이고 유지보수를 용이하게 합니다. 또한, 새로운 패키지나 기능을 추가할 때도 기존 코드의 변경 없이 쉽게 확장할 수 있게 됩니다.
이러한 ISP 원칙의 적용은 단순히 코드 구조를 개선하는 것을 넘어서, 시스템의 전반적인 설계 철학에도 영향을 미칩니다. 인터페이스를 더 작고 특화된 단위로 분리함으로써, 각 컴포넌트의 책임이 명확해지고 시스템의 모듈성이 향상됩니다. 이는 결과적으로 코드의 가독성을 높이고, 테스트와 디버깅을 용이하게 만들어 개발 프로세스 전반의 효율성을 증대시킵니다.