본문 바로가기
카테고리 없음

[F-Lab 모각코 챌린지 12일차] TIL : 패키지 by 레이어, 도메인

by 구튼탁 2023. 6. 14.
728x90

레이어별 패키지


레이어드 아키텍처는 레이어별로 관련 역할을 하는 클래스들을 묶는다.

각 계층의 패키지에 서로 밀접한 관련이 없는 클래스들을 묶기 때문에 패키지 내 응집력이 낮다. 

 

  • UI
    • OrderController.java
    • CartController.java
  • Businesslogic
    • OrderService.java
    • CartService.java
  • Repository
    • OrderRepository.java
    • CartRepository.java
  • Model
    • Order.java
    • Cart.java


이 구조를 살펴보면 패키지 간의 결합은 높지만 패키지 내 응집력은 낮다.

리포지토리 클래스는 서비스 클래스에서, 서비스 클래스는 컨트롤러 클래스에서 사용되기 때문에

패키지 간에 높은 결합이 발생하고 있다.

 

  • 응집력

패키지 내 클래스들이 서로 연관되어 있는 정도를 말한다. 클래스 간의 관계가 높을수록 패키지의 독립성이 보장된다.

응집력이 낮으면 독립성이 떨어질 뿐만 아니라 재사용성과 이해도도 떨어진다.

 

  • 결합

패키지 간, 클래스 간 상호 의존성 정도를 나타낸다. 결합도가 낮아야 유지보수에 용이하다.

결합도가 낮다는 것은 A 와 B 가 의존 관계지만 A 가 변경되어도 해서 B 에 미치는 영향이 낮은 정도를 말한다.

레이어별 패키지의 단점은 바로 낮은 응집력과 높은 결합이다. 

 

Repository 패키지에 어플리케이션의 모든 Repository 클래스가 속했다 가정해보자. 

이 레포지토리 클래스들은 서로 연관이 없다.  model, controller, service 패키지도 마찬가지로 가장해보자.

Order 와 관련된 코드를 찾으려면 모든 패키지를 다 뒤져야한다.  패키지의 응집력이 낮다. 

 

그리고 레이어별 패키지의 단점은 Order 와 관련된 기능을 수행하기 위해

모든 패키지가 의존하고 있다. 패키지 간 결합도가 높다. 

 

 

 

도메인별 패키지

 

도메인별 패키지는 레이어별 패키지의 단점을 커버한다. 도메인 패키지에 해당 도메인에 밀접하게 관련된 클래스들을 묶었다.

왼쪽의 레이어별 패키지의 패키지간 결합이 오른쪽의 도메인별 패키지에서는 보이지 않는다.

패키지의 독립성이 보장되고 있다. 또한 패키지에 밀접하게 관련된 클래스들만 모여 응집도가 높아졌다. 

 


멘토링중 패키지를 도메인, 레이어로 묶는 기준이 무엇인지 질문을 받아서 학습하며 헷갈리는건

패키지 구조 = 어플리케이션 아키텍처 인건가 ? 싶었다. 그다음 어려웠던건 기준에 대한 정의 ? 질문 의도 ?

가 헷갈린다.  어떤 내용을 학습하도 어떻게 정리해서 어떻게 전달해야할까 많은 고민이 든다. 

 

 

 

 

 

https://www.slipp.net/questions/36

 

패키지 구조는 어떻게 가져가는 것이 좋을까?

패키지 구조와 관련해 끊이지 않는 논쟁 거리가 있는데 오늘은 그 이야기를 함 해보자. 이 또한 상황에 따라 다르다고 할 수도 있겠지만 나름의 이유는 있어야 된다고 생각한다. layered architecture

www.slipp.net

 

 


상속보단 컴포지션을 사용해라

 

Intro

Item18 에서는 상속의 취약점을 설명하고 컴포지션을 통해 이 취약점을 방지할 수 있는 방법을 제시하고 있다. 상속과 합성에 대해 간략하게 정리해보고 책 내용을 살펴보겠다

상속 (Inheritance)

상속이란 subclass가 superclass의 속성과 메서드를 이어받는 것을 뜻한다. superclass에는 추상화된 속성과 메서드를 구현하고 subclass 에는 그 클래스에 구체적인 속성과 메서드를 구현한다.
subclass 는 추상화된 속성과 메서드는 superclass 의 것을 재사용하면 된다.
그렇기에 책 도입부에서는 “상속은 코드를 재사용하는 강력한 수단”이라고 말하고 있다.
상속의 superclass와 subclass 의 관계를 “is-a” 관계라 한다.

(여기서 말하는 상속은 클래스가 다른 클래스를 확장하는 구현 상속을 뜻한다.)

컴포지션 (Composition)

컴포지션은 whole(전체)와 part(부분)을 포함하는 합성 관계를 나타낸다. 이런 관계를 “has-a” 관계라고 한다.
whole 인스턴스가 part 인스턴스의 수명 전체에 책임을 진다. 다시 말해 part 인스턴스는 whole 인스턴스 없이는 생존할 수 없다.

아래 코드로 보면 Car(전체) , Engine(부분) 클래스가 컴포지션 관계로 구성되어있다. Engine 클래스는 Car 클래스 내부에서 생성되고 Car 인스턴스가 파괴되면 Engine 인스턴스 역시 파괴된다.


final class Car {

  private final Engine engine;

  Car(EngineSpecs specs) {
    engine = new Engine(specs);
  }

  void move() {
    engine.work();
  }
}

상속의 취약점

상속은 캡슐화를 깨뜨린다.

위의 상속 설명에서 subclass 는 superclass의 코드를 재사용 한다고 했다. 이는 상위클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상을 유발할 수 있다.

HashSet을 상속한 InstrumentedHashSet 에서 상위클래스의 addAll 메서드를 재사용하고 있다.
기대한 동작은 “Sanp”, “Crackle”, “Pop” 세 원소를 추가한 뒤 원소의 개수를 리턴하는 getAddCount() 값은 3이어야 한다.
그러나 결과는 6이다.

HashSet 클래스의 addAll 메서드 내부에서 add 메서드를 호출하고 있다.
상속 관계에서는 하위 클래스에서 상위 클래스의 메서드를 재정의 했을 경우 하위 클래스의 재정의한 메서드가 실행된다. 그래서 자식 클래스 InstrumentedHashSet 의 add 메서드가 호출되었다.

이처럼 상위클래스 내부 구현을 확인할 수 밖에 없으므로 캡슐화도 깨졌고
상위 클래스의 내부 구현이 바뀔때마다 자식 클래스에서 영향을 받을 수 있다.

컴포지션을 통한 해결

기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하여, 이 인스턴스에 새로운 클래스가 받은 요청을 위임한다.

위에서 설명한 컴포지션 관계에 대입하면

  • 새로운 클래스 = InstrumentedSet<E> → whole(전체)
  • private 필드로 기존 클래스의 인스턴스 참조 = s → part (부분)

ForwardingSet 클래스의 메서드들은 기존 클래스(Set) 의 메서드에 대응하는 메서드를 호출해 그 결과를 반환한다. 이 방식을 전달(forwarding) 이라고 하며 ForwardingSet 클래스의 메서드들은 전달 메서드(forwarding method) 라 부른다.

ForwardingSet 클래스는 Set 인터페이스를 구현하고 InstrumentedSet 생성자에서 Set 의 인스턴스를 인수로 받음으로써 유연하게 변경되었다.

Set<Instant> times = new InstrumentedSet<>(new TreeSet<>(element));
Set<E> s = new InstrumentedSet<>(new HashSet(INIT_CAPACITY));

아까처럼 상속 구조에선 addAll 메서드에서 내부에서 재정의한 add 메서드가 호출되었으나 지금 구조에선 Set 인터페이스 구현체의 add 메서드가 호출된다.

public class InstrumentedHashSet<E> extends HashSet<E> {
  // 생략
}

public class InstrumentedTreeSet<E> extends TreeSet<E> {
  // 생략
}

public class InstrumentedSortedSet<E> extends SortedSet<E> {
  // 생략
}

그리고 상속 방식은 구체 클래스 각각을 따로 확장해야 하는 반면 컴포지션 방식은 한 번만 구현해두면 어떠한 Set 구현체라도 계측할 수 있다.

InstrumentedSet 같은 클래스를 래퍼 클래스라 하며, 데코레이터 패턴이라고 한다.

‘래퍼’는 패턴의 주요 아이디어를 명확하게 표현하는 데코레이터 패턴의 별명입니다. 래퍼는 일부 대상 객체와 연결할 수 있는 객체입니다. 래퍼에는 대상 객체와 같은 메서드들의 집합이 포함되어 있으며, 래퍼는 자신이 받는 모든 요청을 대상 객체에 위임합니다.

정리

상속은 반드시 하위 클래스가 상위 클래스의 진짜 하위 타입인 상황에서만 쓰라고 한다. 클래스 A 를 상속하는 클래스 B를 작성하려 한다면 “B가 정말 A인가?”라고 자문해보고 “그렇다"고 확신할 때만 상속을 사용하라고 한다.

 

 

728x90

댓글