Post

오브젝트 책 리뷰 - (2) 응집도, 결합도, 캡슐화

서론

역할, 책임, 협력에 이어서 객체지향 설계가 잘 되었는지를 검증해볼 수 있는 기준이 되는 응집도, 결합도, 캡슐화에 대해서 이번 글에서는 살펴본다.

응집도, 결합도, 캡슐화란 무엇인가?

  • 응집도 : 하나의 기능에 대해 변경이 일어날 때 그 객체의 모든 부분이 유기적으로 다 같이 바뀐다면 응집도가 높은 것이고, 하나의 기능에 대해 변경이 일어날때, 그 객체의 일부분만 변경된다면 응집도가 낮은 것임
    • 하나의 변경에 대해 하나의 모듈만 변경된다면 응집도가 높지만 다수의 모듈이 함께 변경돼야 한다면 응집도가 낮은 것임
    • 개발을 하다보면 요구사항 변경이 생기고 특정 요구사항 변경에 대해 내가 어느 부분을 얼만큼 수정하느냐를 기준으로 응집도를 판단할 수 있음
  • 캡슐화 : 상태와 행동을 하나의 객체 안에 모아서, 객체의 내부 구현을 외부로부터 감추는 것. 진정한 캡슐화는 변경될 수 있는 어떠한 것이라도 감추는 것을 의미함. 내부 구현의 변경으로 외부가 영향을 받는다면 캡슐화를 위반한 것임. 설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화 해야 함
    • 상대적으로 변경 가능성이 높은 구현을 내부로 감추고 상대적으로 변경 가능성이 낮은 안전한 인터페이스 만을 외부로 노출함으로써, 변경의 영향을 통제할 수 있음
    • 객체는 다른 객체의 상세한 내부 구현에 직접 접근할 수 없기 때문에 오직 메시지 전송(=인터페이스 오퍼레이션)을 통해서만 자신의 요청을 전달할 수 있음 → 캡슐화
    • Screening이 Movie에게 calculateMovieFee 메시지를 전송하는 이유는, 요금을 계산하는 데 필요한 기본 요금과 할인 정책을 가장 잘 알고 있는 객체가 Movie이기 때문임(책임 할당 방법 중 INFORMATION EXPERT 패턴 ) → 캡슐화 (외부에서는 메시지 호출만 하고 상세한 내용은 객체 내부에서 처리)
    • 상태와 행동을 객체 라는 하나의 단위로 묶는 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위해서임
  • 결합도 : 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도

캡슐화 준수 시 → 모듈 안의 응집도 상승(상태와 행동이 하나의 객체 안에 모이기에), 모듈 사이의 결합도는 하락(인터페이스로만 의존하기 때문에)

캡슐화 위반 시 → 모듈 안의 응집도 하락(내부의 정보가 바깥으로 새어 나가기 때문에, 변경이 필요할 시 내부와 외부를 다 바꿔주어야 하게 됨), 모듈 사이 결합도 상승( 마찬가지로 내부 구현이 바깥으로 흘러 나오기 때문에 내부 구현이 바뀌는 상황에 대해 외부 클라이언트도 바뀌어야 하므로 결합도가 상승하는 것 )

의존성(결합도) 관리하기

  • 결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정됨
    • MoviePercentDiscountPolicy 에 의존한다고 가정해보면 Movie는 협력할 객체가 비율 할인 정책에 따라 할인 요금을 계산할 것이라는 사실을 알고 있는 것
    • 반면 DiscountPolicy 인터페이스에 의존하는 경우에는 구체적인 계산 방법은 알 필요가 없이, 할인 요금을 계산한다는 사실만 알고 있음
  • 결합도를 낮추려면 추상화에 의존하면 됨. 인터페이스 > 추상 클래스 > 구체 클래스, 순서대로 결합도가 낮음
  • 추상화에 의존하게 되면 구체적인 컨텍스트에 매이지 않기에 컨텍스트 확장이 가능함(=개방폐쇄원칙. 변경에는 닫혀있고 확장에는 열려있다). 예를 들어 MovieDiscountPolicy라는 인터페이스에 의존하게 되면 비율할인정책, 금액할인정책, 중복할인정책, 할인혜택제공x 의 모든 경우에 대해 동일한 의존성으로 구현이 가능함

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
      public class Movie {
      	private DiscountPolicy discountPolicy;
        
      	public Movie(String title, Duration runningTime, Money fee,
      								DiscountPolicy discountPolicy) {
      			...
      			this.discountPolicy = discountPolicy;
      	}
      }
        
      Movie avatar = new Movie(
      	"아바타", Duration.ofMinutes(120), Money.wons(10000),
      	new NoneDiscountPolicy());
        
      Movie avatar = new Movie(
      	"아바타", Duration.ofMinutes(120), Money.wons(10000),
      	new OverlappedDiscountPolicy(
      		new AmountDiscountPolicy(...),
      		new PercentDiscountPolicy(...)));
    

높은 응집도와 낮은 결합도 (예시 1)

영화 예매 도메인을 구성하는 타입들의 구조영화 예매 도메인을 구성하는 타입들의 구조

  • 설계는 트레이드오프 활동이라는 것을 기억하라. 동일한 기능을 구현할 수 있는 무수히 많은 설계가 존재한다. 따라서 실제로 설계를 진행하다 보면 몇 가지 설계 중 한가지를 선택해야 하는 경우가 빈번하게 발생한다.
  • 예로, 위의 영화 예매 시스템에서는 할인 요금 계산하기 위해 MovieDiscountCondition에 할인 여부를 판단하라는 메시지를 전송함
    • 이 설계의 대안으로 Screening이 직접 DiscountCondition에 할인 여부 판단하라고 메시지 보내고, Movie에게 가격 계산하라는 식으로 메시지 보낸다면?

    대안 설계 커뮤니케이션 다이어그램대안 설계 커뮤니케이션 다이어그램

    • 기능은 동일하지만, 도메인 개념을 참고해보면 MovieDiscountCondition의 리스트를 갖고 있기에 Movie가 할인 여부 판단하라는 메시지를 보내는 편이 결합도를 낮추게 된다.(Screening이 굳이 DiscountCondition을 몰라도 되니) ⇒ LOW COUPLING
    • 또한, Screening의 가장 중요한 책임은 예매를 생성하는 것인데, 만약 ScreeningDiscountCondition과 협력해야 한다면 Screening은 영화 요금 계산과 관련된 책임 일부를 떠안아야 할 것임. 이 경우 ScreeningDiscountCondition이 할인 여부를 판단할 수 있고 Movie가 이 할인 여부를 필요로 한다는 사실 역시 알고 있어야 한다. ⇒ 예매 요금 계산 방식이 변경될 경우 Screening도 함께 변경 → 응집도가 낮아짐( HIGH COHESION X)
    • Movie 의 주된 책임이 영화 요금을 계산하는 것이기에 DiscountCondition과 협력하는 것이 응집도에 아무런 해도 끼치지 않는다.

데이터 중심 설계의 문제점 => 캡슐화가 힘듦

  • 데이터 중심 설계가 변경에 취약한 이유
    • 데이터 중심 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다 ( 데이터는 구현의 일부!!!)
    • 데이터 중심 설계에서 객체는 그저 단순한 데이터의 집합체임. 따라서 객체의 행동보다는 상태에 초점을 맞추게 됨
      • 데이터를 먼저 결정하고 데이터를 처리하는 데 필요한 오퍼레이션을 나중에 결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 되어 캡슐화가 실패하게 됨
    • 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정함
      • 데이터 중심설계에서 초점은 객체의 외부가 아니라 내부로 향함. 실행 문맥에 대한 깊이 있는 고민 없이 객체가 관리할 데이터의 세부 정보를 먼저 결정함. 객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워맞출 수 밖에 없음

자바의 JPA 사용 시 이슈

  • JPA 를 사용하게 되면 Entity 객체를 여기저기서 많이 사용하게 되고 이 클래스를 기반으로 설계가 진행되게 된다. 따라서 데이터 중심 설계가 되고 변경에 매우 취약한 시스템이 된다.
    • eg. 예를 들어 Entity 에 필드 하나 추가한다고 하자(db에 컬럼 추가). 그러면 해당 Entity를 이용한 테스트, 그 위의 리포지토리, 서비스, 컨트롤러 레이어까지 싹 다 변경해주어야 함. 결합도, 캡슐화, 응집도 다 떨어짐 ⇒ 도메인 계층과 영속성 계층 각각에서 엔티티를 만들고 각각의 계층이 데이터 주고 받을 때 두 엔티티를 서로 변환해야 함
    • 즉, 서비스 레이어에서 JPA에 의존성을 갖지 않도록 중간에 인터페이스를 두고 인터페이스로만 영속성 레이어를 호출하고 영속성 레이어는 그 인터페이스를 구현하는 식으로 코딩하기
  • 육각형 아키텍처 도입이 필요할까.. (다음 책으로는 만들면서 배우는 클린 아키텍처 책을 읽어봐야지..)
This post is licensed under CC BY 4.0 by the author.