오브젝트 10장 상속과 코드 재사용
객체지향에서는 상속 외에도 코드를 효과적으로 재사용할 수 있는 방법이 한 가지 더 있다. 새로운 클래스의 인스턴스 안에 기존 클래스의 인스턴스를 포함시키는 방법으로 흔히 합성이라고 부른다.
- 308p
중복 코드의 변경 !
중복 코드는 항상 함께 수정돼야 하기 때문에 수정할 때 하나라도 빠트린다면 버그로 이어질 것이다. phone은 수정했지만 NightlyDiscountPhone은 수정하지 않은 채 코드가 배포됐다고 생각해보라.
- 315p
상속을 이용해서 중복 코드 제거하기
public class NightlyDiscountPhone extends Phone{
private static final int LATE_NIGHT_HOUR = 22;
private Money nightlyAmount;
public NightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds) {
super(regularAmount, seconds);
this.nightlyAmount = nightlyAmount;
}
@Override
public Money calculateFee() {
// 부모 클래스의 calculateFee 호출
Money result = super.calculateFee();
Money nightlyFee = Money.ZERO;
for (Call cll : getCalls()){
if (call.getFrom().get.......
- 320p
상속을 위한 경고
자식 클래스의 메서드 안에서 super 참조를 이용해 부모 클래스의 메서드를 직접 호출할 경우 두 클래스는 강하게 결합된다. super 호출을 제거할 수 있는 방법을 찾아 결합도를 제거할
- 322p
InstrumentedHashSet<String> languages = new InstrumentedHashSet<>();
languages.addAll(Arrays.asList("Java", "Ruby" "Scala"));
대부분의 사람들은 위 코드를 실행한 후에 addCount의 값이 3이 될 거라고 예상할 것이다. 하지만 실제로 실행한 후의 addCount의 값은 6이다. 그 이유는 부모 클래스인 hashSet의 addAll 메서드 안에서 add 메서드를 호출하기 때문이다.
- 327p
상속은 코드 재사용을 위해 캡슐화를 희생한다. 완벽한 캡슐화를 원한다면 코드 재사용을 포기하거나 상속 이외의 다른 방법을 사용해야 한다.
- 329p
가장 일반적인 방법은 자식 클래스가 부모 클래스의 구현이 아닌 추상화에 의존하도록 만드는 것이다.
- 322p
오브젝트 11장 합성과 유연한 설계
상속에서 부모 클래스와 자식 클래스 사이의 의존성은 컴파일 타임에 해결되지만 합성에서 두 객체 사이의 의존성은 런타임에 해결된다. 상속 관계는 is-a 관계라고 부르고 합성 관계는 has-a 관계라고 부른다. 상속과 합성은 코드 재사용이라는 동일한 목적을 가진다는 점을 제외하면 구현 방법부터 변경을 다루는 방식에 이르기까지 모든 면에서 도드라진 차이를 보인다.
- 346p
합성은 구현에 의존하지 않는다는 점에서 상속과 다르다. 합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다. 따라서 합성을 이용하면 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화할 수 있기 때문에 변경에 더 안정적인 코드를 얻을 수 있게 된다.
- 346p
상속 관계는 클래스 사이의 정적인 관계인 데 비해 합성 관계는 객체 사이의 동적인 관계다. 이 차이점은 생각보다 중요한데, 코드 작성 시점에 결정한 상속 관계는 변경이 불가능하지만 합성 관계는 실행 시점에 동적으로 변경할 수 있기 때문이다. 따라서 상속 대신 합성을 이용하면 변경하기 쉽고 유연한 설계를 얻을 수 있다.
- 347p
합성 관계로 변경하기
package ch11;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Phone {
private RatePolicy ratePolicy;
private List<Call> calls =new ArrayList<>();
public phone(RatePolicy ratePolicy){
this.ratePolicy = ratePolicy;
}
public List<Call> getCalls () {
return Collections.unmodifiableList(calls);
}
public Money calculateFee(){
return ratePolicy.calculateFee(this);
}
}
Phone 내부에 RatePolicy에 대한 참조자가 포함돼 있다는 것에 주목하라. 이것이 바로 합성이다. phone이 다양한 요금 정책과 협력할 수 있어야 하므로 요금 정책의 타입이 RatePolicy라는 인터페이스로 정의돼 있다는 것에도 주목하라. Phone은 이 컴파일타임 의존성을 구체적인 런타임 의존성으로 대체하기 위해 생성자를 통해 RatePolicy의 인스턴스에 대한 의존성을 주입받는다. phone의 경우처럼 다양한 종류의 객체와 협력하기 위해 합성 관계를 사용하는 경우에는 합성하는 객체의 타입을 인터페이스나 추상 클래스로 선언하고 의존성 주입을 사용해 런타임에 필요한 객체를 설정할 수 있도록 구현하는 것이 일반적이다.
- 372p
일반 요금제
Phone phone = new Phone(new RegularPolicy(Money.wons(10), Duration.ofSeconds(10)));
심야 할인 요금제
Phone phone = new Phone (new nightlyDiscountPolicy(Money.wons(5), Money.wons(10), Duration.ofSeconds(10)));
'Book' 카테고리의 다른 글
[독서 기록] 오브젝트 14장 일관성 있는 협력 (0) | 2023.01.18 |
---|---|
[독서 기록] 오브젝트 12장 다형성 (0) | 2023.01.17 |
[독서 기록] 오브젝트 8장 의존성 관리하기 9장 유연한 설계 (0) | 2023.01.17 |
[독서 기록] 오브젝트 6장 메시지와 인터페이스 (0) | 2023.01.17 |
[독서 기록] 오브젝트 5장 책임 할당하기 (0) | 2023.01.15 |