Home 우아한 객체지향
Post
Cancel

우아한 객체지향

모든 내용은 다음 링크를 출처로 두고 있다.

우아한 객체지향 - 조영호


설계의 핵심은 의존성이다.

의존성을 어떻게 잡냐에 따라 설계의 모양이 바뀐다.

역할/책임을 다루는 이야기는 하지만, 역할이나 책임이 필요하게된 핵심은 의존성을 어떻게 관리하느냐이다.

이 세미나의 내용을 통해 의존성을 어떻게 관리할 것인지 알아가자!


의존성

설계를 어떻게 할 것인가? 에 대한 답은 코드를 어떻게 배치할 것인가? 와 같은 질문이다.

어떤 클래스에 어떤 코드를 넣을 것이고, 어떤 패키지에 어떤 코드를 넣을 것이고 등…

그러면 어디다가 어떤 코드를 어떻게 넣어야 할까?

이는 변경에 초점을 맞추는게 좋다.

같이 변경되는 코드를 같이 배치해야하고 (높은 응집도)

같이 변경되지 않는 코드는 따로 배치해야한다.

의존성의 개념

image1.png

위 다이어그램은 A라는 객체가 B에 의존하고 있다는 것이다.

즉, B가 변한다면 A는 B에 의존적이기 때문에 A도 변하게 된다는 것이다.

정리하자면 의존성이란, 변경(클래스 이름, 메서드 이름, 구현 등)이 일어나면 B가 바뀌면 A도 바뀔 수 있다는 것을 알려주는 가능성이다.

그러므로 의존성은 변경과 관련된 것이고

B가 바뀌면 A도 바뀔 수 있다는 것을 알리는 것이기 때문에 B가 변경되었다고해서 무조건 A가 변경되지 않을 수도 있다.

클래스 간 의존성 종류

image2.png

연관관계

1
2
3
class A {
    private B b;
}

A라는 클래스의 B의 참조를 직접 가지고 있는 것을 연관관계라고 한다.

연관관계는 A에서 B로 영구적으로 갈 수 있는 관계이다.

의존관계

1
2
3
4
5
6
class A {

    public B method(B b) {
        return new B();
    }
}

코드로 이야기하자면

  • 파라미터에 해당 타입이 나온다.

  • 리턴 타입에 해당 타입이 나온다.

  • 메서드 내부에서 해당 타입의 인스턴스를 생성한다.

라는 상황이라면 의존관계라고 볼 수 있다.

의존관계는 A에서 B로 일시적으로 갈 수 있는 관계이다.

상속 관계

1
2
class A extends B {
}

실체화 관계

1
2
class A implements B {
}

상속 관계와 실체화 관계의 차이점은, 상속 관계는 구현이 바뀌면 그 영향이 퍼져나가지만 실체화 관계는 인터페이스의 메서드 시그니처가 바뀌었을 때만 영향을 받는다는 것이다.

패키지 간 의존성

패키지 B의 클래스가 변경된다면 패키지 A의 클래스도 변경될 가능성이 있는 것이 패키지 간 의존성이다.

image3.png

또한 간단하게 패키지 간 의존성을 확인하는 방법은

어떤 클래스에 다른 패키지 이름이 등장하는 것이 패키지 간 의존성이 있는 것이다.

좋은 의존성 관리를 위한 규칙

규칙 1. 양방향 의존성을 피하라

img_1.png

양방향 의존성 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A {
    private B b;

    public void setA(B b) {
        this.b = b;
        this.b.setA(this);
    }
}

class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }
}

단방향 의존성 예시

1
2
3
4
5
6
7
8
9
10
class A {
    private B b;

    public void setA(B b) {
        this.b = b;
    }
}

class B {
}

왜 그럴까? 왜 양방향 의존성을 피해야할까?

의존성의 정의를 다시 생각해보면, 양방향 의존성은

  • A가 바뀔 때 B가 바뀔 가능성이 있게 된다.

  • 또한 B가 바뀔 때 A가 바뀔 가능성이 있게 된다.

이 말인 즉슨, A와 B가 같은 클래스로 묶여있어야 정상적인 것을 분리해 놓은 것이다.

그리고 A와 B의 관계를 항상 동기화 시켜줘야하는 문제도 발생한다. 이로인해 성능이슈와 더불어 동기화적인 문제가 쉽게 발생한다.

규칙 2. 다중성이 적은 방향을 선택하라

img_1.png

다중성이 많은 방향 예시

1
2
3
4
5
6
class A {
    private List<B> bs;
}

class B {
}

다중성이 적은 방향 예시

1
2
3
4
5
6
class A {
}

class B {
    private A a;
}

왜 다중성이 적은 방향을 선택해야할까?

컬렉션처럼 다중성을 표현할 수 있는 타입으로 관리를 하게 되면 성능이슈가 발생할 뿐만 아니라

객체들간의 관계를 유지하기 위한 코드가 늘어나게 된다.

규칙 3. 의존성이 없다면 제거하자

덧붙일 내용이 없다. 말 그대로 굳이 의존성이 필요하지 않으면 제거하면 된다.

규칙 4. 패키지 간 의존성 사이클을 제거하자

Package A -> Package B -> Package C -> Package A

위와 같은 의존성 사이클은 제거해야한다.

이러한 사이클이 발생하는 것 자체가 하나의 패키지에 있어도 될 것들을 모아놓은 좋지 않은 설계를 해버린 것이다.


의존성 - 예제

예제 도메인 컨셉 - 가게, 메뉴

img.png

img.png

  • 메뉴를 판매하는 가게가 존재한다.
    • 가게는 가게이름을 가진다.
    • 가게는 영업여부를 가진다.
    • 가게는 최소주문금액을 가진다.
    • 가게는 수수료율을 가진다.
    • 가게는 수수료누적액을 가진다.
    • 하나의 가게는 메뉴를 여러 개 가질 수 있다.
  • 메뉴가 존재한다.
    • 메뉴는 이름을 가진다.
    • 메뉴는 설명을 가진다.
    • 하나의 메뉴는 옵션그룹을 여러 개 가질 수 있다.
  • 옵션 그룹이 존재한다.
    • 옵션 그룹은 이름을 가진다.
    • 옵션 그룹은 배타 선택여부를 가진다.
    • 옵션 그룹은 기본 옵션여부를 가진다.
    • 하나의 옵션 그룹은 옵션을 여러 개 가질 수 있다.
  • 옵션이 존재한다.
    • 옵션은 이름을 가진다.
    • 옵션은 가격을 가진다.

예제 도메인 컨셉 - 주문

img.png

img.png

  • 주문이 존재한다.
    • 주문은 주문 시간을 가진다.
    • 주문은 주문 상태를 가진다.
    • 하나의 주문은 주문항목을 여러 개 가질 수 있다.
  • 주문 항목이 존재한다.
    • 주문 항목은 이름을 가진다.
    • 주문 항목은 갯수를 가진다.
    • 하나의 주문 항목은 주문 옵션 그룹을 여러 개 가질 수 있다.
  • 주문 옵션 그룹이 존재한다.
    • 주문 옵션그룹은 이름을 가진다.
    • 하나의 주문 옵션 그룹은 주문 옵션을 여러 개 가질 수 있다.
  • 주문 옵션이 존재한다.
    • 주문 옵션은 이름을 가진다.
    • 주문 옵션은 가격을 가진다.

예제 도메인 다이어그램 - 메뉴, 주문

img.png

플로우에 따라 위 설계의 문제점 찾기

플로우

  • 메뉴 선택

  • 장바구니 담기

  • 주문하기


메뉴 선택 다이어그램

메뉴 선택

장바구니 다이어그램

장바구니

메뉴 선택을 진행한 후 장바구니에 메뉴를 담으면, 실제 DB에 담기는 것이 아닌 휴대폰 Local 저장소에 담기게 된다.

그런데 이 때, 장바구니에 담긴 상태에서 가게의 메뉴명과 가격이 바뀐다면 어떻게 될까?

메뉴명 변경 발생 시

장바구니에 담겨있는 메뉴 정보와 실제 DB에 담겨있는 메뉴 정보가 싱크가 안맞게 된다.

따라서, 실제로 주문이 전송되는 메뉴 정보의 데이터와, 업주가 등록한 메뉴 정보와 검증을 해야한다.

검증 요소

검증을 해야할 요소는 아래와 같다.

검증 요소

검증을 위한 협력 설계

img.png

검증을 위한 협력 설계 - 클래스 다이어그램

img.png

객체지향에서는 객체간의 의존성/관계의 방향성이 필요하다.

방향성을 정하기 위해서는 협력의 방향을 알아야하고, 협력의 방향이란 의존성/관계의 방향을 의미한다.

그리고 관계 방향을 설정한 후 관계의 종류를 설정해야한다.

연관관계, 의존관계 조금 더 알아보기

  • 연관관계는 협력을 위해 필요한 영구적인 탐색 구조이다.

연관관계는 탐색 가능성을 의미하며 아래 그림과 같은 흐름을 이야기한다.

img.png

내가 지금 Order를 알면, OrderLineItem을 알 수 있는 것을 의미한다.

이 관계에서 객체간의 협력이 필요하고, 객체 간의 관계가 영구적이라면 연관관계를 이용하여 탐색할 수 있다.

img.png

이 탐색에 있어서는 그 객체간을 이어줄 통로역할을 하는 메세지가 있는 것이다.

일반적인 연관관계 표현 및 협력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Order {
    // 연관관계 표현
    private List<OrderLineItem> orderLineItems;

    public void place() {
        validate();
        ordered();
    }

    private void validate() {
        for (OrderLineItem orderLineItem : orderLineItems) {
            // 연관관계 간 협력
            orderLineItem.validate();
        }
    }
}
  • 의존관계는 협력을 위해 필요한 일시적인 요소 (파라미터, 리턴타입, 지역변수)

img.png

객체지향 설계를 처음부터 완벽하게 할 수는 없다.

그러므로 코드를 작성한 후 의존성 다이어그램을 그려보는 것으로 설계를 검토하는 것이 설계 개선에 도움이 된다.

This post is licensed under CC BY 4.0 by the author.