객체의 내부상태는 외부에서 접근하지 못하도록 감추고 외부에 공개하는 public interface를 통해 내부 상태에 접근할 수 있도록 허용함
객체가 다른 객체와 상호작용할 수 있는 유일한 방법 - 메시지 전송
다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신
메시지를 받은 객체는 자신이 알아서 메시지를 처리하며, 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 함
메시지와 메서드를 구별하는 것도 중요함 -> 다형성의 개념 시작
template method 패턴
부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 패턴
할인 정책: 금액 할인 정책(Amount), 비율 할인 정책(Percent)
public abstract class DiscountPolicy {
private List<DiscountCondition> conditions = new ArrayList<>();
public DiscountPolicy(DiscountCondition ... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for(DiscountCondition each : conditions) {
if (each.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
overriding, overloading
오버라이딩은 부모 클래스에 정의된 같은 이름, 파라미터 목록을 가진 메서드를 자식 클래스에서 재정의
오버로딩은 메서드의 이름만 같고 제공되는 파라미터의 목록이 다름
어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 갖거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이에 의존성이 존재한다고 말한다.
Movie 클래스는 DiscountPolicy 추상 클래스와 연결되어 있음
Movie avatar = new Movie("아바타", Duration.ofMinutes(120),
Money.wons(10000), new AmountDiscountPolicy(Money.wons(800), ...));
영화 요금 계산에 금액 할인 정책을 적용하고 싶다면 Movie 인스턴스를 생성할 때 인자로 AmountDiscountPolicy의 인스턴스를 전달하기만 하면 됨
-> 실행시에 Movie 인스턴스는 AmountDiscountPolicy 클래스의 인스턴스에 의존하게 됨
비율 할인 정책을 적용하고 싶다면 PercentDiscountPolicy의 인스턴스 전달
Movie avatar = new Movie("아바타", Duration.ofMinutes(120),
Money.wons(10000), new PercentDiscountPolicy(0.1, ...));
여기서 말하고 싶은건, 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다는 것
즉, 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있음
- 유연하고 쉽게 재사용할 수 있고 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다름!
가정: 클래스를 하나 추가하고 싶은데, 이미 존재하는 클래스와 매우 유사할 경우
상속을 이용하면 클래스 사이에 관계를 설정하는 것만으로 기존 클래스가 갖고있는 모든 속성과 행동을 새로운 클래스에 포함시킬 수 있음
이처럼 부모 클래스와 다른 부분만 추가해 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍(programming by difference)라고 함
상속이 가치가 있는 이유: 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문
자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있어서 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주 가능
public class Movie {
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
}
Movie가 DiscountPolicy에 정의된 calculateDiscountAmount 메시지를 전송하고 있음
자식 클래스는 상속을 통해 부모 클래스의 인터페이스를 물려받으므로 부모 클래스 대신 사용 가능
컴파일러는 코드 상에서 부모 클래스가 나오는 모든 장소에서 자식 클래스를 사용하는 것을 허용
- Upcasting: 자식클래스가 부모클래스를 대신하는 것