결국엔 프로그래밍
[OBJECT] 오브젝트 Chapter 2 - 객체지향 프로그래밍 (part2) 본문
상속과 인터페이스
일반적인 상속의 목적은 메서드나 인스턴스 변수를 재사용하는 것으로 생각되지만,
부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문에 상속은 가치 있다.
결과적으로, 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에
외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주할 수 있다.
따라서, 부모 클래스가 나오는 모든 자리에서 자식 클래스가 부모 클래스를 대신해 사용될 수 있다. (업 캐스팅)

다형성
동일한 메시지를 전송하더라도 어떤 메서드가 실행될 것인지는 객체의 클래스가 무엇이냐에 따라 달라지는 특성
상속 → 동일한 인터페이스 사용 → 하나의 계층 → 객체들이 서로 같은 메시지들을 이해 → 서로 다른 메서드를 실행 가능 → 다형성
다형성의 구현 방법은 실행될 메서드를 실행 시점에 바인딩한다. (지연 바인딩 or 동적 바인딩) ↔ 컴파일 시점에 실행될 함수나 프로시저를 결정(초기 바인딩 or 정적 바인딩)
→ 객체지향은 지연 바인딩을 사용하기 때문에 하나의 메시지에 대해 서로 다른 메서드와 연결할 수 있다.
상속 (구현 상속, 인터페이스 상속)
- 구현 상속 : 코드를 재사용하기 위한 목적
- 인터페이스 상속: 부모, 자식 클래스가 인터페이스를 공유하기 위해 상속
→ 인터페이스 재사용이 아닌, 구현을 재사용할 목적으로 상속을 사용하면 변경에 취약한 코드를 만들 가능성이 높다.
인터페이스와 다형성
구현을 공유할 필요가 없고 단순히 인터페이스만 공유하고 싶을 때 인터페이스를 사용한다.
자바의 인터페이스는 구현 말고 다형적 협력에만 참여하는 클래스들이 공유 가능한 외부 인터페이스를 정의한 것이다.

02장 04절까지
추상화와 유연성
추상화의 힘
추상화를 사용하면, 추상화만 떼어 놓고 봤을 때 요구사항의 정책을 높은 수준에서 서술할 수 있다.
다시 말해, 상위 정책을 쉽고 간단하게 표현할 수 있다.
추상화를 이용해 상위 정책을 기술한다는 것은 기본적인 애플리케이션의 협력 흐름을 기술한다는 것을 의미.
자식 클래스들은 추상화를 이용해서 정의한 상위의 협력 흐름을 그대로 따른다.
추상화는 '디자인 패턴'과 '프레임워크'에서 모두 추상화를 이용해 상위 정책을 정의하는 객체지향 메커니즘을 활용하기 때문에 중요하다.
유연한 설계
또한 추상화를 사용하면 설계가 좀 더 유연해진다.
다시 말해, 기존 구조를 수정하지 않고 새로운 기능을 쉽게 추가하고 확장할 수 있다.
예를 들어 '스타워즈'라는 영화가 할인이 없어 기본 금액 그대로 사용한다.
public class Movie{
public Money calculateMovieFee(Screening screening){
if(discountPolicy == null){
return fee;
}
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
}
위의 문제점은 할인 정책이 없는 경우를 예외 케이스로 여기기 때문에 지금까지 할인 정책의 일관성 있던 협력에서 벗어나게 된다는 점이다. 이 경우 할인 금액이 0이라는 사실을 결정하는 책임이 기존과 달리 DiscountPolicy가 아닌 Movie에게 있기 때문이다.
책임의 위치 결정을 위해 조건문을 사용하는 것은 협력 측면에서 좋지 않은 방법이다. 항상 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택해야 한다.
public class NoneDiscountPolicy extends DiscountPolicy{
// 0원 이라는 할인 요금을 계산
@Override
protected Money getDiscountAmount(Screening screening){
return Money.ZERO;
}
}
위와 같이, 같은 계층에 새로운 클래스를 만들어 0원의 할인 요금을 계산하는 책임을 한 계층에 유지시키면 된다.
결론적으로 DiscountPolicy를 수정하지 않고도 새로운 클래스를 만듬으로 인해 애플리케이션의 기능을 확장했다.
추상화는 특정한 상황에 결합되는 것을 방지하기 때문에 어떤 클래스와도 협력을 가능하게 한다.
따라서, 유연성이 필요한 곳에 추상화를 잘 사용하라.
추상 클래스와 인터페이스 Trade Off
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;
}
protected abstract Money getDiscountAmount(Screening screening);
}
그러나 DiscountPolicy는 할인 조건이 없는 경우 getDiscountAmount() 메서드를 호출하지 않는다. 그렇기 때문에 NoneDiscountPolicy의 getDiscountAmount()가 어떤 값을 반환하던지 상관이 없다. 이는 부모 클래스인 DiscountPolicy와 NoneDiscountPolicy를 개념적으로 결합시킨다.
이러한 문제를 해결하기 위해 DiscountPolicy를 인터페이스로 변경한다.
기존의 DiscountPolicy는 DefaultDiscountPolicy로 변경하고, NoneDiscountPolicy가 DiscountPolicy 인터페이스를 구현하도록 변경하면 개념적 결합을 방지할 수 있다.

이상적으로는 이러한 변경이 좋으나 현실적으로는 좋지 않을 수도 있다.
구현과 관련된 모든 것들이 트레이드 오프의 대상이 될 수 있다.
코드 재사용 , 상속, 합성
합성: 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법
기존의 Movie가 DiscountPolicy를 재사용하는 방법이 바로 합성이다.
합성이 상속보다 좋은 이유
상속은 1) 캡슐화를 위반하고 2) 설계를 유현하지 못하게 만든다.

1) 상속을 위해 부모 클래스의 내부 구조를 잘 알아야한다. 결과적으로 부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화된다. 그렇게 되면 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률을 높이게 되므로 변경이 어려워진다.
2) 상속은 부모 클래스와 자식 클래스의 관계를 컴파일 시점에 설정하기 때문에, 실행 시점에 객체의 종류를 변경하는 것이 어렵다.
합성은 인터페이스에 정의된 메세지를 통해서만 코드를 재사용한다.
그 결과 1) 구현을 효과적으로 캡슐화 할 수 있다. 2) 인존하는 인스턴스를 교체하는 것이 쉬워 설계를 유연하게 한다.
따라서 '코드 재사용'을 위해서는 상속보다 합성을 사용하는 것이 더 좋은 방법이다.
하지만 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 함께 조합하여 사용한다.
02장 끝
'객체지향프로그래밍 > [책] Object 오브젝트' 카테고리의 다른 글
| [OBJECT] 오브젝트 Chapter 5 - 책임 할당하기 (0) | 2022.01.23 |
|---|---|
| [OBJECT] 오브젝트 Chapter 4 - 설계 품질과 트레이드오프 (0) | 2022.01.16 |
| [OBJECT] 오브젝트 Chapter 3 - 역할, 책임, 협력 (0) | 2022.01.09 |
| [OBJECT] 오브젝트 Chapter 2 - 객체지향 프로그래밍 (0) | 2022.01.03 |
| [OBJECT] 오브젝트 Chapter 1 - 객체, 설계 (0) | 2022.01.02 |