강한 결합 : 복잡하게 얽혀서 풀 수 없는 구조

hyyyynjn·2024년 4월 22일
0

강한 결합 : 복잡하게 얽혀서 풀 수 없는 구조

결합도란 모듈(클래스) 사이의 의존도를 나타내는 지표다.

  • 강한 결합 : 다른 클래스에 많이 의존하고 있는 구조
  • 느슨한 결합 : 결합도가 낮은 구조

8.1 결합도와 책무

클래스의 책무를 제대로 생각하지 않으면 결합도가 높아지기 쉽다.
이는 디버깅과 번경을 어렵게 한다.

책무를 생각하지 않은 구조

class DiscountManager {
  List<Product> discountProducts;
  int totalPrice;

  boolean add(Product product, ProductDiscount productDiscount) {
    // 유효성 검사 로직들
    int discountPrice = getDiscountPrice(product.price);

    // 조건 로직들
    if (조건1) {
      discountProducts.add(product);
      return true;
    }
    return false;
  }

  static int getDiscountPrice(int price) {
    int discountPrice = price - 3000;
    if (discountPrice < 0) {
      discountPrice = 0;
    }
    return discountPrice;
  }
}

위와 같은 온라인 쇼핑몰 할인 서비스 DiscountManager가 존재한다.
이후 여름 할인 사양이 추가되어 SummerDiscountManager 클래스를 구현했다.

class SummerDiscountManager {
  DiscountManager discountManager;

  boolean add(Product product) {
    // 유효성 로직
    
    int temp = discountManager.getDiscountPrice(product.price);

    if (조건1) {
      discountManager.discountProducts.add(product);
      return true;
    }
    return false;
  }
}

8.1.1 다양한 버그

어느날 일반 할인 가격을 3000 -> 4000원으로 변경했다.
DiscountManager#getDiscountPrice 메소드를 수정해야한다.
자연스럽게 해당 메소드를 사용하는 SummerDiscountManager#add 메소드도 영향을 받게 된다. (버그)

8.1.2 로직의 위치에 일관성이 없음

  1. DiscountManager#add 메소드에서 너무 많은 일을 하고 있다
    • 상품 정보 확인
    • 할인 가격 계산
    • 할인 적용
  2. Product에서 이뤄져야 할 유효섬 검사가 DiscountManager, SummerDiscountManager에 구현되어 있다
  3. 여름 할인 가격 계산을 위해 SummerDiscountManager가 DiscountManager의 로직을 활용하고 있다.

어떤 클래스는 작업이 집중되어 있고, 어떤 클래스는 특별히 하는 일이 없다.

이런 설계를 책무를 고려하지 않은 설계라 할 수 있다.

8.1.3 단일 책임 원칙

소프트웨어의 책임 : 자신의 관심사와 관련하여 정상적으로 동작하도록 제어하는 것.
단일 책임 원칙 : 클래스가 담당하는 책임은 하나로 제한해야 한다라는 설계 원칙

8.1.4 단일 책임 원칙 위반으로 발생하는 문제점

DiscountManager#getDiscountPrice 메소드

  • 할인되는 가격이 같다라는 이유로 SummerDiscountManager에서도 사용된다. -> 둘 다 책임지는 것은 단일 책임 원칙에 위반된다

DiscountManager 클래스와 Product 클래스

  • DiscountManager가 Product 대신 값을 확인하고 있다 (유효성 검사) -> Product 클래스에 책임을 지우지 않고 과보호 해주고 있다 -> Product 클래스의 성장을 막고 있다

8.1.5 책임이 하나가 되게 클래스 설계하기

RegularPrice : 상품 가격
RegularDiscountedPrice : 일반 할인 가격
SummerDiscountedPrice : 여름 할인 가격

class RegularPrice {
  final int amount;

  RegularPrice(int amount) {
    validateAmount(amount);
    this.amount = amount;
  }
}

class RegularDiscountedPrice {
  private static final int DISCOUNT_AMOUNT = 4000;
  final int amount;

  RegularDiscountedPrice(RegularPrice price) {
    int discountedAmount = price.amount - DISCOUNT_AMOUNT;
    validateDiscountedAmount(discountedAmount);
    this.amount = discountedAmount;
  }
}

class SummerDiscountedPrice {
  private static final int DISCOUNT_AMOUNT = 3000;
  final int amount;

  SummerDiscountedPrice(RegularPrice price) {
    int discountedAmount = price.amount - DISCOUNT_AMOUNT;
    validateDiscountedAmount(discountedAmount);
    this.amount = discountedAmount;
  }
}

이처럼 관심사에 따라 분리해서 독립된 구조를 느슨한 결합이라고 한다

8.1.6 DRY 원칙의 잘못된 적용

RegularDiscountedPrice와 SummerDiscountedPrice의 로직은 DISCOUNT_AMOUNT만 제외하면 차이가 없다.
이를 보고 "중복 코드"라 생각할 수 있지만, 추후에 SummerDiscountedPrice의 할인 가격 계산 사양이 변경된다면 두 로직이 달라질 것이다.

책무를 생각하지 않고 로직의 중복을 제거하면 안된다.
그렇게 되면 하나로 모인 로직이 여러 책무를 담당하게 된다.

DRY(Don't Repeat Yourself) 원칙

모든 지식은 시스템 내에서 단 한 번만, 애매하지 않고, 권위 있게 표현되어야 한다.

반복을 피하라는 원칙을 코드의 중복을 절대 허용하지 말라로 받아들이면 안된다.
여기서 말하는 "지식"은 소프트웨어가 대상으로 하는 "비지니스 지식"이기도 하다.

같거나 비슷한 로직이라도 "개념"이 다르면 중복을 허용해야 한다.
개념적으로 다른 것 까지도 무리하게 중복을 제거하면 "강한 결합 상태"가 되어 단일 책임 원칙이 깨진다.

0개의 댓글