결합도란 모듈(클래스) 사이의 의존도를 나타내는 지표다.
클래스의 책무를 제대로 생각하지 않으면 결합도가 높아지기 쉽다.
이는 디버깅과 번경을 어렵게 한다.
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;
}
}
어느날 일반 할인 가격을 3000 -> 4000원으로 변경했다.
DiscountManager#getDiscountPrice 메소드를 수정해야한다.
자연스럽게 해당 메소드를 사용하는 SummerDiscountManager#add 메소드도 영향을 받게 된다. (버그)
어떤 클래스는 작업이 집중되어 있고, 어떤 클래스는 특별히 하는 일이 없다.
이런 설계를 책무를 고려하지 않은 설계라 할 수 있다.
소프트웨어의 책임 : 자신의 관심사와 관련하여 정상적으로 동작하도록 제어하는 것.
단일 책임 원칙 : 클래스가 담당하는 책임은 하나로 제한해야 한다라는 설계 원칙
DiscountManager#getDiscountPrice 메소드
DiscountManager 클래스와 Product 클래스
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;
}
}
이처럼 관심사에 따라 분리해서 독립된 구조를 느슨한 결합
이라고 한다
RegularDiscountedPrice와 SummerDiscountedPrice의 로직은 DISCOUNT_AMOUNT만 제외하면 차이가 없다.
이를 보고 "중복 코드"라 생각할 수 있지만, 추후에 SummerDiscountedPrice의 할인 가격 계산 사양이 변경된다면 두 로직이 달라질 것이다.
책무를 생각하지 않고 로직의 중복을 제거하면 안된다.
그렇게 되면 하나로 모인 로직이 여러 책무를 담당하게 된다.
DRY(Don't Repeat Yourself) 원칙
모든 지식은 시스템 내에서 단 한 번만, 애매하지 않고, 권위 있게 표현되어야 한다.
반복을 피하라는 원칙을 코드의 중복을 절대 허용하지 말라로 받아들이면 안된다.
여기서 말하는 "지식"은 소프트웨어가 대상으로 하는 "비지니스 지식"이기도 하다.
같거나 비슷한 로직이라도 "개념"이 다르면 중복을 허용해야 한다.
개념적으로 다른 것 까지도 무리하게 중복을 제거하면 "강한 결합 상태"가 되어 단일 책임 원칙이 깨진다.