# 8. 강한 결합 - 복잡하게 얽혀서 풀 수 없는 구조

### 내용

***

* 결합도?
  * 클래스 사이의 의존도를 나타내는 지표
* 강한 결합
* 느슨한 결합
  * 느슨한 결합을 가지고 있으면, 코드 변경이 쉬워진다.
* 책무
  * 책임과 의무
  * 의무를 다 해야 하는 책임

#### 결합도와 책무

* 소프트웨어의 책임
  * 자신의 관심사와 관련해서, 정상적으로 동작하도록 제어하는 것
  * 단일 책임 원칙
    * 클래스가 담당하는 책임은 하나로 제한해야 한다는 설계원칙
* DRY 원칙
  * Don't Repeat Yourself
  * 모든 지식은 시스템 내에서 단 한번만, 애매하지 않고, 권위 있게 표현되어야한다.
    * 지식 -> 비즈니스 지식
    * e.g. '할인', '관심 상품', '크리스마스 캠페인', '공격력', '매직포인트'
  * 일반 할인과 여름 할인은 서로 다른 개념
    * 같은 로직, 비슷한 로직이라도 개념 (비즈니스 로직) 이 다르면 중복을 허용해야 한다.

#### 다양한 강한 결함 사례와 대처 방법

* 상속 가능한 사용하지 말자
  * 왜?
    * 서브 클래스는 슈퍼 클래스의 구조를 하나하나 신경 써야함.
    * 슈퍼 클래스의 변화를 놓치는 순간, 버그 생성
* 상속보다는 Composition을 이용하자
* public 이유없이 사용하지 말자
  * public -> 모든 클래스에서 접근 가능
  * protected -> 같은 클래스, 서브 클래스에서만
  * 없음 -> package private
  * private -> 같은 클래스에서만
    * 기본적으로 package private으로 만들자
    * 외부 공개가 필요하면 public으로 만들자
* private 메서드가 너무 많다는 건 책임이 많다는 것
* 응집도가 높다는 것은 같은 개념 (비즈니스 로직) 이 모여있는 것을 의미.
  * 다른 개념의 변수들이 모여있으면 강한 결합 (지양하는 바)
* 트랜잭션 스크립트 패턴
  * 데이터를 보유하고 있는 클래스 (데이터 클래스) 와 데이터를 처리하는 클래스가 나누어 구현하고 있을 때.
* 트랜잭션 스크립트 패턴 예시

```java
public class OrderService {
    public void processOrder(int orderId, int productId, int quantity, String paymentMethod) {
        // 주문 정보 조회
        Order order = orderRepository.getOrder(orderId);
        
        // 상품 정보 조회
        Product product = productRepository.getProduct(productId);
        
        // 재고 확인
        if (product.getStock() < quantity) {
            throw new InsufficientStockException("재고가 부족합니다");
        }
        
        // 결제 처리
        double totalAmount = product.getPrice() * quantity;
        boolean paymentResult = paymentGateway.processPayment(paymentMethod, totalAmount);
        
        if (!paymentResult) {
            throw new PaymentFailedException("결제에 실패했습니다");
        }
        
        // 재고 감소
        product.setStock(product.getStock() - quantity);
        productRepository.updateProduct(product);
        
        // 주문 상태 업데이트
        order.setStatus("PAID");
        order.setTotalAmount(totalAmount);
        orderRepository.updateOrder(order);
    }
}
```

* 모든 비즈니스 로직이 단일 메서드 내에 절차적으로 작성
* Order와 Product는 단순한 데이터 컨테이너로, 실제 동작은 포함하지 않음
* 모든 작업이 서비스 계층에서 조율됨.
* 도메인 모델 패턴 예시

```java
// 도메인 모델 - Order 클래스
public class Order {
    private int id;
    private List<OrderItem> items;
    private String status;
    private double totalAmount;
    
    public void addItem(Product product, int quantity) {
        OrderItem item = new OrderItem(product, quantity);
        items.add(item);
        recalculateTotal();
    }
    
    public void checkout() {
        validateOrder();
        reserveInventory();
        status = "PENDING_PAYMENT";
    }
    
    public void pay(PaymentMethod paymentMethod) {
        if (status != "PENDING_PAYMENT") {
            throw new IllegalStateException("주문이 결제 대기 상태가 아닙니다");
        }
        
        paymentMethod.process(totalAmount);
        status = "PAID";
    }
    
    private void validateOrder() {
        for (OrderItem item : items) {
            item.validate();
        }
    }
    
    private void reserveInventory() {
        for (OrderItem item : items) {
            item.reserveInventory();
        }
    }
    
    private void recalculateTotal() {
        totalAmount = items.stream()
            .mapToDouble(OrderItem::getSubtotal)
            .sum();
    }
}

// 도메인 모델 - OrderItem 클래스
public class OrderItem {
    private Product product;
    private int quantity;
    
    public OrderItem(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
    }
    
    public double getSubtotal() {
        return product.getPrice() * quantity;
    }
    
    public void validate() {
        if (product.getStock() < quantity) {
            throw new InsufficientStockException("재고가 부족합니다: " + product.getName());
        }
    }
    
    public void reserveInventory() {
        product.decreaseStock(quantity);
    }
}

// 애플리케이션 서비스
public class OrderService {
    public void processOrder(int orderId, int productId, int quantity, PaymentMethod paymentMethod) {
        Order order = orderRepository.getOrder(orderId);
        Product product = productRepository.getProduct(productId);
        
        order.addItem(product, quantity);
        order.checkout();
        order.pay(paymentMethod);
        
        orderRepository.saveOrder(order);
    }
}
```

* 비즈니스 로직이 도메인 객체(Order, OrderItem 등)에 분산되어 있습니다.
* 각 객체는 자신의 상태와 행동을 책임집니다.
* 서비스 계층은 도메인 객체들을 조율하는 역할만 합니다.
* 비즈니스 규칙과 로직이 도메인 객체에 캡슐화되어 있습니다.
* 갓 클래스
  * 하나의 클래스 내부에 수천, 수만줄의 로직을 담고 있음..

### 정리

***

* 단일 책임 원칙
  * 클래스가 각자 적절한 책임을 가지도록 해야함
  * 그 책임이 가능한 하나로.
* DRY
  * 비즈니스 개념이 다르면, 코드 중복을 허용해야한다.
* 느슨한 결합, 높은 응집도.
  * 동일한 개념끼리만 응집도를 가지게 하고,
  * 다른 개념이면 클래스를 나눠 관리하여 느슨한 결합을 유지하도록 한다.

### 참고

***

* <https://product.kyobobook.co.kr/detail/S000202521361>
