1. 단일 책임 원칙
- '클래스가 담당하는 책임은 하나로 제한해야 한다'는 설계 원칙
[문제 코드]
DiscountManager 클래스가 Product클래스 대신 값을 확인하는 행위
-> DiscountManager는 다른 클래스에게 책임을 지우지 않고 무엇이든 대신 해주는, 즉 과보호하는 부모
class DiscountManager{
List<Product> discountProducts;
int totalPrice;
//상품 추가 메서드
boolean add(Product product, ProductDiscount productDiscount){
...
}
//할인 가격 확인 메서드
static int getDiscountPrice(int price){
...
}
}
[개선 코드]
1) 느슨한 결합 - class 분리
2) DRY 원칙(Don't Repeat Yourself)
- 각각의 개념 단위 내에서 반복을 하지 말라.
- 같은 로직, 비슷한 로직이라도 개념이 다르면 중복을 허용 (RegularDiscountedPrice와 SummerDiscountedPrice)
//정가 클래스
class RegularPrice{
private static final int MIN_AMOUNT = 0;
final int amount;
RegularPrice(final int amount){
// 유효성 검사와 관련된 책임을 모두 RegularPrice에서 짐.
// 다른 곳에 유효성 검사와 관련된 코드가 중복될 일이 없어짐.
if(amount < MIN_AMOUNT){
throw new IllegalArgumentException("가격은 0 이상이어야 합니다.");
}
this.amount = amount;
}
}
//일반 할인 클래스
class RegularDiscountedPrice{
private static final int MIN_AMOUNT = 0;
private static final int DISCOUNT_AMOUNT = 4000;
final int amount;
RegularDiscountedPrice(final RegularPrice price){
int discountedAmount = price.amount - DISCOUNT_AMOUNT;
if (discountedAmount < MIN_AMOUNT){
discountedAmount = MIN_AMOUNT;
}
amount = discountedAmount;
}
}
//여름 할인 클래스
class SummerDiscountedPrice{
private static final int MIN_AMOUNT = 0;
private static final int DISCOUNT_AMOUNT = 3000;
final int amount;
SummerDiscountedPrice(final RegularPrice price){
int discountedAmount = price.amount - DISCOUNT_AMOUNT;
if (discountedAmount < MIN_AMOUNT){
discountedAmount = MIN_AMOUNT;
}
amount = discountedAmount;
}
}
2. 상속으로 인한 강한 결합
[문제 코드]
슈퍼 클래스의 구조 변경시, 슈퍼 클래스의 구조를 서브 클래스는 하나하나 신경 써야 한다.
class FighterPhysicalAttack extends PhysicalAttack{
@Override
int singleAttackDamage(){
...
}
@Override
int doubleAttackDamage(){
...
}
}
[개선 코드]
상속보다 컴포지션 구조를 활용
컴포지션(Composition)이란?
컴포지션(Composition)은 객체 지향 프로그래밍에서 한 객체가 다른 객체를 포함하거나 조합하여 더 복잡한 기능을 만드는 설계 방법이다. 즉, "무엇을 포함하는가"에 중점을 두고, 객체 간의 관계를 형성하는 방식이다.
강한 결합을 피하기 위해 상속 대신 컴포지션을 사용하는 이유는, 상속이 클래스 간의 강한 의존성을 만들어 변경이 어려워질 수 있기 때문이다. 반면, 컴포지션은 객체가 다른 객체를 포함하도록 하여 더 유연한 설계를 가능하게 한다.
class FighterPhysicalAttack extends PhysicalAttack{
private final PhysicalAttack physicalAttack;
...
int singleAttackDamage(){
...
}
int doubleAttackDamage(){
...
}
}
3. 상속을 사용하는 나쁜 일반화
1) 하나의 로직으로 봐야 하는 흐름이 두 클래스에 분산되어 있는 설계는 좋은 설계라고 말할 수 없다.
2) 슈퍼 클래스에서 if문을 사용하여 조건 분기를 태울 때 (조건 분기를 늘릴 경우)
- 비즈니스 로직이 분산되기 때문에 좋지 않다.
4. 클래스, 메서드 간의 관계를 파악할 때 영향 스케치를 활용
- 의존 관계 그림을 영향 스케치라고 함
- 소스 코드를 자동으로 분석해서 영향 스케치를 그려주는 도구 활용 : Jig(https://github.com/dddjava/jig)
5. 특별한 이유 없이 public 사용하지 않기
1) public과 private 같은 접근 수식자를 붙이면, 클래스와 메서드의 가시성을 제어할 수 있다.
2) 이유 없이 public으로 만들면, 관계를 맺지 않았으면 하는 클래스끼리도 결합되어, 영향 범위가 확대됩니다. 결과적으로 유지 보수가 어려운 강한 결합 구조가 되고 맙니다. (같은 패키지에서 사용하고 싶으면 package private 사용)
3) 외부에 정말로 공개하고 싶은 클래스만 한정해서 public을 사용
6. 높은 응집도를 오해해서 생기는 강한 결합
응집도가 높다는 개념을 염두에 두고, 관련이 깊다고 생각되는 로직을 한곳에 모으려고 했지만, 결과적으로 강한 결합 구조를 만드는 상황
-> 각각의 개념을 분리해야, 느슨한 결합 구조로 만들 수 있다. (결합이 느슨하고 응집도가 높은 설계)
7. 거대 데이터 클래스
public class Order{
public int orderId; // 주문 ID
public int customerId; //주문자 ID
public int List<Product> products; // 주문 내역
public ZonedDateTime orderTime; //주문 일자
public OrderState orderState; // 주문 상태
public int reservationId; // 예약 ID
public ZonedDateTime reservationDateTime; //예약 일자
public String deliveryDestination; //발송지
}
1) 거대 데이터 클래스는 수많은 인스턴스 변수를 갖는다.
2) 설계를 고려하지 않고 구현하다 보면, 수많은 데이터가 모일 수 있다. 또한, 데이터가 많아지다 보면 클래스를 '데이터를 편리하게 운반하는 역할'로 인식하고 데이터를 계속 추가하기 쉽다. 이렇게 되면 데이터 클래스가 점점 더 거대해진다.
3) 거대 데이터 클래스는 특정 유스케이스와 관련없는 데이터도 포함하고 있어, 수많은 유스케이스에서 사용된다.
-> 유스케이스의 설계에 맞춰서 데이터 클래스를 분리해 사용하자.
'일상 > 레벨업 독서' 카테고리의 다른 글
[내 코드가 그렇게 이상한가요?] 10장 이름 설계:구조를 파악할 수 있는 이름 (3) | 2024.11.14 |
---|---|
[내 코드가 그렇게 이상한가요?] 9장 설계의 건전성는 여러 악마 (2) | 2024.10.28 |
[내 코드가 그렇게 이상한가요?] 7장-컬렉션:중첩을 제거하는 구조화 테크닉 (0) | 2024.08.28 |
[내 코드가 그렇게 이상한가요?] 6장-조건 분기 : 미궁처럼 복잡한 분기 처리를 무너뜨리는 방법 (0) | 2024.07.16 |
[내 코드가 그렇게 이상한가요?] 5장 응집도:흩어져 있는 것들 (0) | 2024.07.06 |