2022. 6. 20. 18:21ㆍSpring/개념
예시 - 원칙적으로 권장되지 않지만 때로는 서버가 운영중인 상태에서 서버를 재시작 하지 않고 긴급하게 애플리케이션이 사용중인 SQL을 변경해야 할 상황 운영중에 예상하지 못한 SQL의 오류를 발견했다거나, 특별한 이유로 SQL조건이나 참조 테이블을 급하게 변경해야 할 경우
기존 설계 - SqlService 구현 클래스들은 초기에 리소스부터 SQL 정보를 읽어오면 이를 메모리에 두고 그대로 사용한다. SQL 매핑정보 파일을 변경했다고 해서 메모리상의 SQL 정보가 갱신되지 않는다. 굳이 방법이 있다면 재시작이나 웹 애플리케이션을 리로딩해서 SqlService빈을 초기화 시키는것 뿐...
1. DI와 기능의 확장
DI의 가치를 제대로 얻으려면 먼저 DI에 적합한 오브젝트 설계가 필요
DI를 의식하는 설계
초기부터 SqlService의 내부 기능을 적절한 책임과 역할에 따라 분리하고, 인터페이스를 정의해 느슨하게 연결해주고, DI를 통해 유연하게 의존관계를 지정하도록 설계해뒀기 때문에 그 뒤의 작업은 매우 쉬워졌다. 오브젝트들이 서로의 세부적인 구현에 얽매이지 않고 유연한 방식으로 의존관계를 맺으며 독립적으로 발전할 수 있게 해주는 DI덖분이다. 결국 유연하고 확장 가능한 좋은 오브젝트 설계와 DI 프로그래밍 모들은 서로 상승작용을 한다.
스프링을 이용해 DI를 적용하는건 간단한 일이다. 하지만 DI의 가치를 제대로 누리기는 쉽지않다. DI에 필요한 유연하고 확장성 뛰어난 오브젝트를 설계하려면 많은 고민과 학습, 훈련, 경험이 필요하다. 객체지향 설계 방법 중 한가지는 DI를 의식하면서 설계하는 방식이다. DI를 적용하려면 커다란 오브젝트 하나만 존재해서는 안된다. 최소한 두 개 이상의, 의존관계를 가지고 서로 협력해서 일하는 오브젝트가 필요하다. 그래서 적절한 책임에 따라 오브젝트를 분리해줘야 한다. 그리고 항상 의존 오브젝트를 다이내믹하게 연결해줘서 유연한 확장을 꾀하는게 목적이기 때문에 항상 확장을 염두에 두고 오브젝트 사이의 관계를 생각해야 한다. 이렇게 DI를 잘 활용할 수 있는 방법을 생각하면서 오브젝트를 설계한다면 객체지향 기술이 약속하는 유연한 확장과 재사용이 가능한 설계를 만드는데 도움이 될것이다. DI는 확장을 위해 필요한 것이므로 항상 미래에 일어날 변화를 예상하고 고민해야 적합한 설계가 가능해진다. DI란 결국 미래를 프로그래밍하는 것이다.
DI와 인터페이스 프로그래밍
DI를 적용할때는 가능한 한 인터페이스를 사용해야 한다. 물론 인터페이스를 사용하지 않고도 DI는 가능하다. 의존 오브젝트가 생성자나 수정자 등을 통해 주입만 가능하면 되기 때문에 의존 오브젝트의 클래스 타입을 클라이언트가 직접 사용해도 문제는 발생하지 않는다. 하지만 DI를 DI답게 만들려면 두 개의 오브젝트가 인터페이스를 통해 느슨하게 연결돼야 한다.
인터페이스를 사용하는 이유
- 다형성을 얻기 위해서 - 하나의 인터페이스를 통해 여러 개의 구현을 바꿔가면서 사용할 수 있게 하는것이 DI가 추구하는 첫 번째 목적이다. 물론 지금까지 여러 가지 DI 적용 예를 살펴봤듯이 의존 오브젝트가 가진 핵심 로직을 바꿔서 적용하는 것 외에도 프록시, 데코레이터, 어댑터, 테스트 대역 등의 다양한 목적을 위해 인터페이스를 통한 다형성이 활용된다. 하지만 단지 DI의 목적이 다형성을 편리하게 적용하는 것 때문만이라면 제약이 많고 불편한 점이 있다고 해도 클래스를 사용할 수도 있다. 상속이 불가능한 final클래스만 아니라면 상속을 통해서도 여러 가지 방식으로 구현을 확장할 수 있기 때문이다. 템플릿 메소드 패턴이 대표적인 방법이다.
- 인터페이스 분리원칙을 통해 클라이언트와 의존 오브젝트 사이의 관계를 명확하게 해줄 수 있다 - A오브젝트가 B오브젝트를 사용한다고 했을 때 A를 사용하니까 클라이언트, B는 사용하는 의존관계에 있으니 의존 오브젝트라 부른다면 A와 B가 인터페이스로 연결되어 있다는 의미는 다르게 해석하면 A가 B를 바라볼 때 해당 인터페이스라는 창을 통해서만 본다는 뜻이다. 만약 B1이라는 인터페이스를 B가 구현하고 있고, A는 B1인터페이스를 통해서만 B를 사용한다면, 그래서 DI를 받을때도 B1인터페이스를 통해 받는다면 A에게 B는 B1이라는 관심사를 구현한 임의의 오브젝트에 불과하다. 그래서 같은 B1이라는 인터페이스를 구현했다면 B가 아니라 C, D클래스로 만들어진 오브젝트라도 A에게 DI가 가능해진다. 그런데 B오브젝트는 B1이 아니라 B2라는 다른 인터페이스를 구현하고 있을 수도 있다. 자바의 클래스는 하나 이상의 인터페이스를 구현할 수 있다. B는 왜 또 B2라는 다른 인터페이스를 구현하고 있을까? 그 이유는 B2라는 인터페이스가 그려주는 창으로 B를 바라보는 다른 종류의 클라이언트가 존재하기 때문이다. 즉 인터페이스는 하나의 오브젝트가 여러 개를 구현할 수 있으므로, 하나의 오브젝트를 바라보는 창이 여러 가지일 수 있다는 것이다. 각기 다른 관심과 목적을 가지고 어떤 오브젝트에 의존하고 있을 수 있다는 의미다. 굳이 B2라는 인터페이스에 정의된 내용에는 아무런 관심이 없는 A오브젝트가 B2인터페이스의 메소드까지 모두 노출되어 있는 B라는 클래스에 직접 의존할 이유가 없다. 게다가 B2인터페이스의 메소드에 변화가 발생하면 그에는 관심도 없는 A오브젝트의 코드에 영향을 줄 수도 있다. 인터페이스를 이렇게 클라이언트의 종류에 따라 적절히 분리해서 오브젝트가 구현하게 하면 매우 유용하다. 오브젝트가 그 자체로 충분히 응집도가 높은 작은 단위로 설계됐더라도, 목적과 관심이 각기 다른 클라이언트가 있다면 인터페이스를 통해 이를 적절히 분리해줄 필요가 있고, 이를 객체지향 설계원칙에서는 인터페이스 분리원칙이라 부른다. 인터페이스를 사용하지 않고 직접 참조하는 방식으로 DI를 했다면, 인터페이스 분리 원칙과 같은 클라이언트에 특화된 의존관계를 만들어낼 방법 자체가 없는것이다.
public class A {
B1 b1;
public void setB1(B1 b1) {
this.b1 = b1;
}
public String printName() {
String name = b1.print();
return "my name is " + name;
}
}
public class B implements B1, B2 {
private String name;
private int num;
public void setNum(int num) {
this.num = num;
}
public void setName(String name) {
this.name = name;
}
@Override
public String print() {
return name;
}
@Override
public int num() {
return num;
}
}
public interface B1 {
public String print();
}
public interface B2 {
public int num();
}
<bean id="a" class="springbook.learningtest.polymorphism.A">
<property name="b1" ref="b1"/>
</bean>
<bean id="b1" class="springbook.learningtest.polymorphism.B">
<property name="name" value="B" />
<property name="num" value="1" />
</bean>
2번 처럼 B가 B1과 B2를 상속해도, 분리원칙을 통해 클라이언트와 의존 오브젝트 사이의 의존관계를 명확히 할 수 있다.
다형성은 물론이고 클라이언트별 다중 인터페이스 구현과 같은 유연하고 확장성 높은 설계가 가능함에도 인터페이스를 피할 이유가 없다! DI는 특별한 이유가 없는 한 항상 인터페이스를 사용한다고 기억해두자. 굳이 인터페이스를 써야 하냐고 주장하는 사람들을은 단지 인터페이스가 추가하기 귀찮아서 게으름을 부리고자 인터페이스를 생략한다면 이후의 개발, 디버깅, 테스트, 기능의 추가, 변화 등에서 적지 않은 부담을 안게 될 것이다.
2. 인터페이스 상속
하나의 오브젝트가 구현하는 인터페이스를 여러 개 만들어서 구분하는 이유중의 하나는 오브젝트의 기능이 발전하는 과정에서 다른 종류의 클라이언트가 등장하기 때문이다. 때로는 인터페이스를 여러 개 만드는 대신 기존 인터페이스를 상속을 통해 확장하는 방법도 사용된다. 인터페이스 분리원칙이 주는 장점은 모든 클라이언트가 자신의 관점에 따른 접근 방식을 불필요한 간섭 없이 유지할 수 있다는 점이다. 그래서 기존 클라이언트에 영향을 주지않은 채로 오브젝트의 기능을 확장하거나 수정할 수 있다. 기존 클라이언트는 자신이 사용하던 인터페이스를 통해 동일한 방식으로 접근할 수만 있다면 오브젝트의 변경에 영향받지 않는다. 오브젝트가 완전히 새로운 인터페이스를 추가로 구현하는 경우뿐 아니라 기존 인터페이스를 상속해서 기능을 확장하는 경우에도 마찬가지다.
'Spring > 개념' 카테고리의 다른 글
[토비의 스프링1] 7.1 SQL과 DAO의 분리 (2) | 2022.06.26 |
---|---|
[토비의 스프링1] 7 스프링 핵심 기술의 응용 (0) | 2022.06.26 |
Maven scope (0) | 2022.06.20 |
[토비의 스프링] AOP 용어 (0) | 2022.06.02 |
[토비의 스프링1] 서비스 추상화 (0) | 2022.05.12 |