SRP 적용 전
SRP 적용 후
OCP 원칙이 적용된 JDBC의 관계
확장
- 새로운 타입을 추가함으로써 기능을 추가(구현)할 수 있다.
- 따라서 확장이 열려있다는 말은 새로운 타입(클래스, 모듈, 함수)을 추가함으로써 기능을 확장시키는 것을 말한다.
변경- 확장이 발생했을 떄 상위 레벨이 영향을 미치지 않아야 하는 것을 말한다.
- 따라서 변경에는 닫혀있어야 한다는 말은 확장이 발생했을 떄 해당 코드를 호출 하는 쪽에서 변경이 발생하지 않는 것을 말한다.
이 원칙을 지키기 위한 구현방법
1. 확장 될 것과 변경을 엄격히 구분한다.
2. 이 두 모듈이 만나는 지점에 인터페이스를 정의 한다.
3. 구현에 대한 의존보다는 정의된 인터페이스를 의존 하도록 코드를 작성한다.
4. 변경이 발생하는 부분을 추상화하여 분리한다.
예시
public interface Animal{ void eat(String food); void sleep(); } public class Whale implements Animal{ @Override public eat(String food){ System.out.println(food + "을/를 먹다"); } @Override public sleep(){ System.out.println("잔다"); } }
고래(Whale)은 동물(Animal)을 상속받아 사용하는 것이 위의 예시와 같다고 보면 된다. 상위 클래스에서 동물이라면 해야하는 부분을 지정해주고 그 동물에 속하는 종들은 해당 행동(메서드)들을 반드시 이행 해야한다.
ISP 적용전
public interface Car { String autoDrive(); String autoParking(); String drive(); String break(); } public class Telsa implements Car { @Override public String autoDrive() { return "autoDrive"; } @Override public String autoParking() { return "autoParking"; } @Override public String drive() { return "drive"; } @Override public String break() { return "break"; } }
Tesla 클래스는 Car라는 상위 클래스의 인터페이스를 상속받아 오버라이딩 하여서 해당 기능을 사용하고 있다. 만약 autoParing, autoDrive기능을 지원하지 않는 차가 있으면 어떻게 해야할까 ?
public class Ray implements Car { //해당 기능을 사용 안하고싶음!!!!! @Override public String autoDrive() { return ""; } //해당 기능을 사용 안하고싶음!!!!! @Override public String autoParking() { return "autoParking"; } @Override public String drive() { return "drive"; } @Override public String break() { return "break"; } }
위와 같이 autoparking, 과 autoDrive를 사용하고 싶지 않을떄는 isp의 인터페이스 분리 원칙을 적용시키면 된다.
[ISP 적용 후]
Car (Interface)
public interface Car { String drive(); String break(); }
ElectricCar (Interface)
public interface ElectricCar { String autoDrive(); String autoParking(); }
Tesla (Class)
public class Telsa implements Car,ElectricCar { @Override public String autoDrive() { return "autoDrive"; } @Override public String autoParking() { return "autoParking"; } @Override public String drive() { return "drive"; } @Override public String break() { return "break"; } }
Ray(Class)
public class Ray implements Car { @Override public String drive() { return "drive"; } @Override public String break() { return "break"; } }
위의 예제와 같이 인터페이스를 클래스(객체)의 기능에 맞게 SRP의 개념을 생각해서 잘개 쪼개어 사용하는 것을 ISP라 한다.
고수준 모듈은 저수준 모듈에 의존하며 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야한다. 즉, 자신보다 변하기 쉬운것에 의존하지 마라
다시 말하면 의존 관계를 맺을 떄 변화하기 쉬운 것과 자주 변화하는 것 보다는 변화하기 어려운것과 거의 변화가 없는것에 의존 하라는 원칙을 말한다.
여기서 변화하기 어려운 것을 말하는 것은 보통 추상적인 객체(Interface, abstract)를 말한다.
현재는 겨울이기 떄문에 스노우 타이어를 구매하여 자동차에 끼도록 설계하였습니다. 즉, 고수준 모듈인 자동차가 저수준 모듈인 스노우 타이어에 의존하는 상태입니다.
하지만, 날씨가 따뜻해지면서 더 이상 스노우 타이어를 사용할 필요가 없어졌습니다. 그래서 일반 타이어로 교체하기로 결정하였습니다. 그런데, 단순히 스노우 타이어를 일반 타이어로 바꾼다고 코드가 끝나는 것이 아닙니다. 이것에 의존하고 있던 자동차의 코드도 연쇄적으로 영향을 끼치게 됩니다.
이것은 개방-폐쇄 원칙을 위반하는 것이므로 추상화나 다형성을 통해 문제를 고쳐야 합니다. 의존 역전 원칙은 그 중에서도 추상화를 이용합니다. 바로, 스노우 타이어나 일반 타이어를 '타이어' 자체로 추상화하는 것이죠.
고수준 모듈이 저수준 모듈에 의존했던 상황이 역전되어 저수준 모듈이 고수준 모듈에 의존하게 된다.
이렇게 SOLID 원칙에 근거하여 작성을 하게되면 클래스의 책임을 덜어주고 기능에 의존하지 않게 하는 것이 확장성과 다른 클라이언트에 미치는 영향을 최소화 하는 것을 알 수 있다.