객체지항의 본질
어떤 Class가 필요한 지, 어떤 Method가 필요한지(X)
어떤 객체가 어떤 상태와 행동을 가지는지 설정 → 더 단순하고 깔끔한 설계
접근 제어의 필요성
- Why? 외부 간섭 최소화 → 객체에게 자율성 부여
- 외부에서는 객체에게 간섭, 직접 개입 x 객체에게 요청, 객체가 결정해야함
- 인터페이스와 구현의 분리 (핵심 원칙)
구현 은닉
- 역할: 클래스 작성자, 클라이언트 프로그래머
- 내부 외부를 명확하게 경계지음 → 내부 구현 은닉, 의도치 않은 부분 접근 방지
- 프로그래머 입장에선 알아야할 부분 감소, 작성자 입장에선 구현 변경 폭 상승
Money class (custom)
- 내부적으로 하나의 인스턴스 변수(BigDecimal)만 포함
- Long 타입으로 처리하지 않은 이유? → 개념을 명시적으로 표현하는것이 더 큰 장점을 가져옴
- 전체적인 설계의 명확성, 유연성 상승
template method 패턴
부모 클래스에서 기본적인 흐름 구현 → 자식에게 필요처리 위임
코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다
- DiscountPolicy interface(코드)를 구현한 AmountDiscountPolicy(실행 시점) 객체
트레이드-오프
- 설계 유연 → 코드 이해, 디버깅 어려워짐
- 유연성 억제 → 코드 이해도, 디버깅 쉬움// 재사용성, 확장성 하락
- 무조건 유연한 설계, 무조건 읽기 쉬운코드 둘다 정답이 아님 → 상황에 따라 적절한 설계가 필요함(개인 의견)
다형성, 지연 바인딩(lazy binding)
- 컴파일 시점의 의존성과 실행 시점의 의존성 분리
추상화
- 요구사항의 정책을 높은 수준에서 서술 가능
- 설계가 더 유연해짐
- 기존 구조 수정하지 않고 새로운 기능 쉽게 추가, 확장
- 영화 예매 요금은 최대 하나의 ‘할인 정책’과 다수의 ‘할인 조건’을 이용해 계산할 수 있다” 로 추상화 가능
- → 금액, 비율 할인정책과 기간, 순서 조건을 포괄 할 수 있음 (예시)
유연한 설계
할인 정책이 없는 경우에 상위 흐름(Movie)에서 처리할 것인가? 조건문 사용?
→ 좋지 않은 선택임, 일관성을 유지할 방법을 찾아야 함
→ 할인 금액이 0임을 결정하는 책임을 DiscountPolicy에 위치시킴 (일관성 유지)
⚡추상클래스와 인터페이스의 Trade-Off
- DiscountPolicy의 calcualteDiscountAmount 메서드에서는 NoneDiscountPolicy의 getDiscountAmount()에서 어떤 값을 반환하든 0을 반환하게 되어있음
public Money calculateDiscountAmout(Screening screening){
for(DiscountCondition each : conditions){
if (each.isSatisfiedBy(screening){
return getDiscountAmount(screening);
}
}
// NonDiscountPolicy는 discount condition이 없기 때문에 무조건 0(Money.ZERO)을 반환함
return Money.ZERO;
}
- NoneDiscountPolicy는 DiscountPolicy와 개념적으로 결합하게됨
→ 인터페이스 분리 , Overriding으로 처리 설계
trade off
- 구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다는 뜻
- 위의 개선이 이상적이지만 현실적으로는 과할 수 도 있음(이전 설계의 None~ 클래스 역시 할인 금액이 0원이라는 사실을 효과적으로 전달하기 때문)
- 작성하는 모든 코드에 합당한 이유가 있어야함
- 사소한 결정도 트레이드 오프를 통해 얻어진 결론과 그렇지 않은 결론 사이의 차이는 큼
코드 재사용: 상속 vs 합성
상속
- 캡슐화를 위반함 : 자식은 부모클래스의 내부구조를 잘 알아야 함
- 캡슐화의 약화는 부모-자식 간 강 결합을 만들고 유연성을 떨어뜨림
- 부모 클래스 변경 → 자식클래스의 변경을 만들 확률 상승
- 설계가 유연하지 않음: 컴파일 시점에 부모-자식 관계가 결정되어버림
- Movie의 discount을 실햄시점에서 변경한다?
- 복사해서 넣는 방법 밖에없음 (percent → amount)
- 반면에 합성은?
public class Movie{ private DiscountPolicy discountPolicy; //해당 함수를 호출하면 변경이 가능하다!! public void changeDiscountPolicy(DiscountPolicy discountPolicy){ this.dicountPolicy = discountPolicy; } }
- Movie의 discount을 실햄시점에서 변경한다?
합성
- 인터페이스를 통해 약하게 결합된다.
- Movie가 DiscountPolicy의 내부 구현을 알지 못하고 외부 인터페이스를 통한 메서드 제공만 알게된다
- 상속의 두 가지 문제점읅 모두 해결한다
- 인터페이스에 정의된 메세지를 통해서만 재사용 → 구현을 효과적으로 캡슐화
그렇다고 해서 상속을 사용하지 말라는 것은 X
상속 + 합성을 사용할 것
- 코드 재사용은 합성
- 다형성을 위한 인터페이스 재사용 → 상속
ps. 객체지향 설계에 관해 중요한 것이 무엇인지 알게 된 것 같다
객체에게 적절한 역활, 책임을 부여하고 객체간 상호작용을 하여 협력하게 하도록 설계해야한다
이 챕터에선 예시를 통해 캡슐화, 추상화를 통한 유연한 설계의 중요성을 강조했고 잘 이해되었다
트레이드-오프에 관해서도 깊게 생각해본 계기가 되었다