들어가기
계층형 아키텍처에대해서 검색을 해보던 중에 이동욱 개발자님의 포스팅을 보게 됐다.
계층형 아키텍처
학교 다닐때 잠깐 JAVA 관련 수업을 들은적이 있다. 그때 수업 내용은 넷빈즈(Netbeans) IDE를 통해 JAVA로 윈도우 애플리케이션을 만드는 것이였다. 간단한 시간표 관리 프로그램을 만드는 과제는 얼
jojoldu.tistory.com
전체 글을 절반 쯤 읽던 중 Rich Domain Model에대한 이야기가 나왔고, 사실 처음 들어봤다...
그래서 글을 읽다 말고 1시간 40분 짜리 동영상을 시청하게됐다.
왜 이제서야 봤는지 후회가 될 정도였고 이 내용을 잘 정리해두려한다.
eternity-oop - Overview
eternity-oop has 4 repositories available. Follow their code on GitHub.
github.com
[수정본] 우아한 객체지향
[수정본] 우아한 객체지향 - Download as a PDF or view online for free
www.slideshare.net
책임, 협력 그리고 의존성
조영호님의 객체지향의 사실과 오해 에서도 숱하게 등장하던 단어들이다.
의존성은 객체들이 서로 책임을 나눠서 가지고 메세지로 협력하는 그 과정에서 생겨난다.
이번 세미나는 이 의존성을 어떻게 관리할 것이냐 가 핵심 주제다.
의존성의 종류
첫 번째 : 연관관계 [Association]
public class A{
private B b;
}
A라는 객체가 B라는 객체를 참조하는 형태로 연관관계를 맺는다.
뒤에서도 나오겠지만 가장 강력한 형태의 의존성을 갖게된다.
두 번째 : 의존관계 [Dependency]
public class A{
public B doSomething(B b){
return new B();
}
}
특정 객체 타입을 반환하거나 파라미터로 받거나 생성하는 경우 의존성을 갖게된다.
세 번째 : 상속관계 [extends]
너무 당연해 따로 코드를 적진 않겠다.
네 번째 : 실체화 관계 [implements]
마찬가지로 코드를 적지 않겠다.
다섯 번째 : 패키지 의존성
객체 간의 의존성말고도 패키지 간의 의존성도 고려해야한다.
의존성을 도대체 어떻게 해야할까?
해당 세미나가 아니더라도 의존성이라는 단어는 개발자라면 무조건 마주칠 수 밖에 없는 단어다.
이 의존성을 도대체 왜 관리해야 하는 걸까?
보통 의존성이 높다 라는 말은 결합도가 높다는 말과도 같다. 높은 결합도는 소프트웨어를 더욱 복잡하게 만들고 의존성의 방향을 제대로 관리하지 않는다면 유지보수는 매우매우 힘들어진다.
결합도는 하나의 변경이 미치는 영향을 파악하기도 어렵고 변경 그 자체도 어렵게한다. 그래서 우리는 의존성을 관리해야 한다.
즉, 의존성을 관리한다는 것은 변경에 대비하는 것과도 같다.
의존성의 방향을 결정해야한다.
결론 부터 말하자면 의존성은 한 방향으로 흘러야한다.
방향을 단방향으로 관리하지 않으면 변경을 통제할 수가 없다.
양방향 의존성은 애초에 두 객체가 하나여야 했음을 의미한다. (패키지 또한 마찬가지)
이제부터 조영호님의 예제 코드와 자료를 바탕으로 더 세밀하게 들여다 보자.
주문 Validation
여기서부터 소개하는 내용들은 복잡하기 때문에 영상을 보지 않으면 이해하기가 어렵다.
영상의 첫 번째 문제점이다.
실선은 객체참조로 연관관계 의존성을 나타내고 점선은 의존관계 의존성을 나타낸다.
order와 shop은 위에서 언급한 양방향 의존관계가 존재한다.
order 패키지의 변경이 발생하면 shop에 변경이 전파되고 다시 order로 전파되기 쉬운 구조다. 즉 변경을 대비하기 어려운 구조로 이루어져 있다.
어떻게 이 구조를 개선할 수 있을까?
중간 객체(추상화, DIP)를 이용해 의존성을 단방향으로
OptionGroup과 Option이라는 중간객체를 도입해 의존성 사이클을 끊어낼 수 있다.
조금은 다른 이야기지만 이 해결방법을 듣던중 다소 신선한 이야기를 듣게됐다.
보통 추상화라 함은 추상클래스나 인터페이스를 통해 설계될거라 생각하고 그렇게 배웠다.
하지만 일반 객체도 추상화 역할을 할 수 있다.
추상이라는 것은 잘 변하지 않다는 것이고 어색하지만 일반 객체도 잘 변하지 않는다면 추상화의 역할을 해낼 수 있다.
핵심은 잘 변하지 않아야 한다는 것이다.
SOLID 중에 D(DIP)는 구체 클래스에 의존하지 않고 추상 클래스에 의존해야한다는 원칙인데, 추상 클래스 자체가 변한다면 추상 클래스에 기대고 있던 모든 객체들이 영향 받는다.
그렇기때문에 추상은 잘 변하지 않도록 설계해야하고 또 잘 변하지 않는다면 추상화로서 이용될 수 있다.
기존에 OptionGroupSpecification -> OrderOptionGroup으로 의존하고 있었다.
여기에 OptionGroup을 중간에 두고 의존관계를 역전시켰다.
먼저 아래는 OrderOptionGroup이다.
@Entity
@Table(name="ORDER_OPTION_GROUPS")
@Getter
public class OrderOptionGroup {
...
//생략
...
public OptionGroup convertToOptionGroup() {
return new OptionGroup(name, orderOptions.stream().map(OrderOption::convertToOption).collect(toList()));
}
}
@Entity
@Table(name="OPTION_GROUP_SPECS")
@Getter
public class OptionGroupSpecification {
//생략
public boolean isSatisfiedBy(OptionGroup optionGroup) {
return !isSatisfied(optionGroup.getName(), satisfied(optionGroup.getOptions()));
}
}
@Data
public class OptionGroup {
private String name;
private List<Option> options;
@Builder
public OptionGroup(String name, List<Option> options) {
this.name = name;
this.options = options;
}
}
이제 위에서 사이클이 생겼던 두 객체는 직접적으로 의존하지 않는다.
더 중요한 것은 OptionGroup에 의존하게되면서 의존방향 자체가 단방향으로 흐르게됐다.
'Java' 카테고리의 다른 글
테스트 코드 그리고 리팩토링 (0) | 2024.04.19 |
---|---|
Java Integer Caching (0) | 2024.04.13 |
[WAS를 만들어보자 (3)] HttpMessageBody 추출하기 (1) | 2024.03.23 |
[WAS를 만들어보자 (2)] HTTP 메세지 출력하기 (0) | 2024.03.23 |
[WAS를 만들어보자 (1)] 자바로 TCP/IP 통신하기 (0) | 2024.03.23 |