[Java] 의존성_우아한테크세미나-190620-우아한객체지향 (1)
19 Feb 2022의존성_우아한테크세미나-190620-우아한객체지향 (1)
의존성
- A가 B를 의존할 경우 B가 변경 될 경우 A도 변경 될 수 있다.
클래스 의존성의 종류
- 연관관계(Association)
A라는 클래스에 B라는 객체를 잠조하고있다.
class A {
private B b;
}
- 의존관계(Dependency)
A라는 클래스에 B 객체의 파라미터, 리턴타입 또는 인스턴스를 생성한다.
class A {
private B method (B b) {
return new B();
};
}
- 상속관계(Inheritance)
B의 객체가 변경 될 경우 A도 같이 변경이 된다.
class A extends B{
}
- 실체화관계(Realization)
B객체의 구현체를 A객체에서 구현한다.
class A implements B {
}
*상속관계와 실체화관계의 차이는 상속관계는 부모 객체가 변경될시 자식 객체가 같이 변경되지만 실체화 관계는 부모 객체의 코드가 변경 될 경우 자식 객체가 변경된다.
패키지 의존성
패키지에 포함된 클래스 사이의 의존성 간단하게 설명하자면 클래스 내에 타 패키지를 import한 경우라고 볼 수 있다.
패키지 의존성 규칙
- 양방향 의존성을 피하라
- 다중성이 적은쪽으로 설계
- 의존성이 필요없으면 제거
- 패키지 사이의 의존성 사이클을 제거 해야한다.
Example
ex) 관계에는 방향성이 필요하다.
관계의 방향 = 협력의 방향 = 의존성의 방향
관계의 종류 결정하기
- 연관관계 : 협력을 위해 필요한 영구적인 탐색구조 (객체참조)
- 의존관계 : 협력을 위해 일시적으로 필요한 의존성 (파라미터, 리턴타입, 자연변수)
연관관계 = 탐색가능성(navigability)
- Order에서 OrderLineItem으로 탐색 가능
- 즉, Order가 뭔지 알면 Order를 통해 원하는 OrderLineItem을 찾을 수 있다.
- 두 객체 사이에 협력이 필요하고 두 객체의 관계가 영구적이라면 연관관계를 이용해 탐색 경로 구현
class Order {
private List<OrderLineItem> orderLineItem;
public void place(){
validate();
ordered();
}
private void validate(){
...
// 연관관계를 이용해서 협력
for(OrderLineItem orderLineItem : orderLineItem) {
orderLineItem.validate();
}
}
}
해당 예제 구현하기
- Shop & OrderLineItem의 연관관계를 연결한다.
@Entity
@Table(naem="ORDERS")
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ORDER_ID")
private Long id;
// Shop(상점정보)의 객체 참조
@ManyToOne
@JoinColumn(name="SHOP_ID")
private Shop shop;
// OrderLineItem(주문정보)의 객체 참조
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name="ORDER_ID")
private List<OrderLineItem> orderLineItems = new ArrayList<>();
...
public void place() {
validate();
ordered();
}
private void validate() {
}
private void ordered() {
}
}
- 가게가 영업중인지 확인 & 주문금액이 최소주문금액이상인지
- (Order -> Shop)
- isOpen();
- isValidAmount();
Order
public class Order {
public void place() {
validate();
ordered();
}
private void validate() {
if (orderLineItem.isEmpty()) {
throw new IllegalStateException("주문 항목이 비어 있습니다.");
}
if (!shop.isOpen()) {
throw new IllegalStateException("가게가 영업중이 아닙니다.");
}
if (!shop.isValidOrderAmount(calculateTotalPrice())) {
throw new IllegalStateException(String.format("최소 주문 금액 %s 이상을 주문해주세요.", shop.getMinOrderAmount()));
}
for (OrderLineItem orderLineItem : orderLineItems) {
orderLineItem.validate();
}
}
private void ordered() {
this.orderStatus = OrderStatus.ORDERED;
}
}
Shop
public class Shop {
public boolean isOpen(){
return this.open;
}
public boolean isValidOrderAmount(Money amount) {
return amount.isGreaterThanOrEqual(minOrderAmount);
}
}
- 주문중에 서버의 주문정보가 바뀌었을 수 있으니 재검증
- (Order -> OrderLineItem)
- validate();
- (OrderLineItem -> Menu)
- validateOrder(name, orderLineItem);
OrderLineItem
public class OrderLineItem {
public void validate() {
menu.validateOrder(name, this.orderOptionGroups);
}
}
- 메뉴의 이름과 주문항목의 이름 비교
- (Menu -> OptionGroup Specification)
- isSatisfiedBy(oog);
- (OptionGroup Specification -> Order OptionGrop)
- getOption();
Menu
public class Menu {
public void validateOrder(String menuName, List<OptionGroup> optionGroups) {
if (!this.name.equals(menuName)) {
throw new IllegalArgumentException("기본 상품이 변경됐습니다.");
}
if (!isSatisfiedBy(optionGroups)) {
throw new IllegalArgumentException("메뉴가 변경됐습니다.");
}
}
private boolean isSatisfiedBy(List<OptionGroup> cartOptionGroups) {
return cartOptionGroups.stream().anyMatch(this::isSatisfiedBy);
}
private boolean isSatisfiedBy(OptionGroup group) {
return optionGroupSpecs.stream().anyMatch(spec -> spec.isSatisfiedBy(group));
}
}
- 옵션그룹의 이름과 주문옵션그룹의 이름 비교
- (OptionGroup Specification -> Option Specification)
- isSatisfiedBy(oOption);
OptionGroupSpecification
public class OptionGroupSpecification {
public boolean isSatisfiedBy(OptionGroup optionGroup) {
return !isSatisfied(optionGroup.getName(), satisfied(optionGroup.getOptions()));
}
private boolean isSatisfied(String groupName, List<Option> satisfied) {
if (!name.equals(groupName) ||
satisfied.isEmpty() ||
(exclusive && satisfied.size() > 1)) {
return false;
}
return true;
}
private List<Option> satisfied(List<Option> options) {
return optionSpecs
.stream()
.flatMap(spec -> options.stream().filter(spec::isSatisfiedBy))
.collect(toList());
}
}
- 옵션의 이름과 주문옵션의 이름 비교 & 옵션의 가격과 주문옵션의 가격 비교
- (OptionGroup Specification -> Option Specification)
- getName();
- getPrice();
OptionSpecification
public class OptionSpecification {
public boolean isSatisfiedBy(Option option) {
return Objects.equals(name, option.getName()) &&
Objects.equals(price, option.getPrice());
}
}
레이어 아키텍처 (Layered Architecture)
위의 해당 예제는 레이어 아키텍처 중 Domain의 과정에 들어가는 과정이다. 영역의 관계를 정의하는 단계라고 보면 된다.
아래의 소스와 같이 Domain과정에서 벗어난 request를 받거나 DB에 접근하는 로직의 구현은 Service 레이어에서 진행한다.