본문 바로가기
Study/오브젝트

[오브젝트] 11장. 합성과 유연한 설계

by Nahwasa 2023. 1. 4.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 오브젝트(조용호 저) 책 입니다.

     


     

    10장과 연관된 내용으로, 10장 부터 봐야 이해 가능합니다.

     

    CHAPTER 11. 합성과 유연한 설계

    ⚈ 상속(Inheritance)과 합성(Composition)

    • 객체지향 프로그래밍에서 가자 널리 사용되는 코드 재사용 기법이다.
    • 상속 : 부모 클래스와 자식 클래스를 연결해서 부모 클래스의 코드를 재사용. 부모 클래스와 자식 클래스 사이의 의존성은 컴파일타임에 해결된다. =is-a 관계
    • 합성 : 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용. 두 객체 사이의 의존성은 런타임에 해결된다. =has-a 관계
    • 부모 클래스의 내부 구현에 대해 상세히 알아야 하므로 부모와 자식 클래스간의 결합도가 높아진다. 상속은 코드를 재사용할 수 있는 쉽고 간단한 방법일지는 몰라도 우아한 방법이라고 할 수는 없다.
    • 상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만 합성은 포함되는 객체의 퍼블릭 인터페이스를 재사용한다.
    • 합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다. 변경의 영향을 최소화 할 수 있고, 실행 시점에 동적으로 변경도 할 수 있다.

     


    01 상속을 합성으로 변경하기

    ⚈ 합성을 사용하면 10장에서 나온 상속이 초래하는 세 가지 문제점을 해결할 수 있다.

    • 불필요한 인터페이스 상속 문제
    • 메서드 오버라이딩의 오작용 문제
    • 부모 클래스와 자식 클래스의 동시 수정 문제

     

    ⚈ 상속을 합성으로 바꾸는 방법 : 자식 클래스에 선언된 상속 관계를 제거하고 부모 클래스의 인스턴스를 자식 클래스의 인스턴스 변수로 선언

     

    ⚈ 불필요한 인터페이스 상속 문제 해결 - Stack이 Vector를 상속해서 발생했던 문제

    • 이제 Stack의 퍼블릭 인터페이스에는 불필요한 Vector의 오퍼레이션들이 포함되지 않게 된다.
    public class Stack<E> {
        private Vector<E> elements = new Vector<>();
    
        public E push(E item) {
            elements.addElement(item);
            return item;
        }
    
        public E pop() {
            if (elements.isEmpty()) {
                throw new EmptyStackException();
            }
            return elements.remove(elements.size() - 1);
        }
    }

     

    메서드 오버라이딩의 오작용 문제 해결 - InstrumentedHashSet 문제

    • 이제 super를 사용해 호출한 addAll에서 InstrumentedHashSet의 add가 호출되어 addCount가 중복으로 세어지던 문제가 없어진다.
    public class InstrumentedHashSet<E> implements Set<E> {
        private int addCount = 0;
        private Set<E> set;
    
        public InstrumentedHashSet(Set<E> set) {
            this.set = set;
        }
    
        @Override
        public boolean add(E e) {
            addCount++;
            return set.add(e);
        }
    
        @Override
        public boolean addAll(Collection<? extends E> c) {
            addCount += c.size();
            return set.addAll(c);
        }
    
        public int getAddCount() {
            return addCount;
        }
    }

     

    • 다만 이 경우엔 Set의 인터페이스도 모두 제공해줘야 한다. 이 경우엔 아래와 같이 하면 된다. 그럼 구현 결합도는 제거하면서도 퍼블릭 인터페이스는 그대로 유지할 수 있다.
    public class InstrumentedHashSet<E> implements Set<E> {
        private int addCount = 0;
        private Set<E> set;
    
        public InstrumentedHashSet(Set<E> set) {
            this.set = set;
        }
    
        @Override
        public boolean add(E e) {
            addCount++;
            return set.add(e);
        }
    
        @Override
        public boolean addAll(Collection<? extends E> c) {
            addCount += c.size();
            return set.addAll(c);
        }
    
        public int getAddCount() {
            return addCount;
        }
    
        @Override public boolean remove(Object o) {
            return set.remove(o);
        }
    
        @Override public void clear() {
            set.clear();
        }
    
        @Override public boolean equals(Object o) {
            return set.equals(o);
        }
    
        @Override public int hashCode() {
            return set.hashCode();
        }
    
        @Override public Spliterator<E> spliterator() {
            return set.spliterator();
        }
    
        @Override public int size() {
            return set.size();
        }
    
        @Override public boolean isEmpty() {
            return set.isEmpty();
        }
    
        @Override public boolean contains(Object o) {
            return set.contains(o);
        }
    
        @Override public Iterator<E> iterator() {
            return set.iterator();
        }
    
        @Override public Object[] toArray() {
            return set.toArray();
        }
    
        @Override public <T> T[] toArray(T[] a) {
            return set.toArray(a);
        }
    
        @Override public boolean containsAll(Collection<?> c) {
            return set.containsAll(c);
        }
    
        @Override public boolean retainAll(Collection<?> c) {
            return set.retainAll(c);
        }
    
        @Override public boolean removeAll(Collection<?> c) {
            return set.removeAll(c);
        }
    }

     

    • 이처럼 동일한 메서드 호출을 그대로 전달하는걸 포워딩(forwarding)이라 부르고, 동일한 메서드를 호출하기 위해 추가된 메서드를 포워딩 메서드(forwarding method)라고 부른다.

     

    부모 클래스와 자식 클래스의 동시 수정 문제 - PersonalPlaylist 문제

    • 이 경우엔 합성으로 변경하더라도 Playlist와 PersonalPlaylist를 함께 수정해야 하는 문제가 해결되지는 않는다.
    public class Playlist {
        private List<Song> tracks = new ArrayList<>();
        private Map<String, String> singers = new HashMap<>();
    
        public void append(Song song) {
            tracks.add(song);
            singers.put(song.getSinger(), song.getTitle());
        }
    
        public List<Song> getTracks() {
            return tracks;
        }
    
        public Map<String, String> getSingers() {
            return singers;
        }
    }
    
    ----
    
    public class PersonalPlaylist {
        private Playlist playlist = new Playlist();
    
        public void append(Song song) {
            playlist.append(song);
        }
    
        public void remove(Song song) {
            playlist.getTracks().remove(song);
            playlist.getSingers().remove(song.getSinger());
        }
    }

     

    • 문제가 해결되진 않았지만 여전히 상속보다는 합성을 사용하는 게 더 좋은데, 향후에 Playlist의 내부 구현을 변경하더라도 파급효과를 최대한 PersonalPlaylist 내부로 캡슐화할 수 있기 때문이다.

     

    대부분의 경우 구현에 대한 결합보다는 인터페이스에 대한 결합이 더 좋다.

     

    몽키 패치(Monkey Patch) : 현재 실행 중인 환경에만 영향을 미치도록 지역적으로 코드를 수정하거나 확장하는 것

     


    02 상속으로 인한 조합의 폭발적인 증가

    ⚈ ☆ '01' 에서 10장에서 살펴봤던 상속으로 코드 재사용 할 때의 문제점을 해결했다. 그리고 '02'에서 상속으로 인한 문제점을 하나 더 보여주고, '03'에서 합성으로 이를 해결하고 있다. 그러니 '02'는 다시 상속의 문제점을 보여주기 위한 예시이다.

     

    상속으로 인해 결합도가 높아지면 코드를 수정하는 데 필요한 작업의 양이 과도하게 늘어나는 경향이 있다. 가장 일반적인 상황은 작은 기능들을 조합해서 더 큰 기능을 수행하는 객체를 만들어야 하는 경우다. 일반적으로 다음과 같은 두 가지 문제점이 발생한다.

    • 하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 한다.
    • 단일 상속만 지원하는 언어에서는 상속으로 인해 오히려 중복 코드의 양이 늘어날 수 있다.

     

    기본 정책과 부가 정책 조합하기

    • 10장에서 봤던 핸드폰 과금 시스템(Phone 클래스)에 새로운 요구사항을 추가해보자.
    • 기존엔 일반 요금제와 심야 할인 요금제가 있었다.
    • 새로운 요구사항은 이 두 요금제에 부가 정책을 추가하는 것이다. 이후로는 핸드폰 요금제가 '기본 정책'과 '부가 정책'을 조합해서 구성된다고 가정한다.

     

    부가 정책 특성

    • 세금 정책과 기본 요금 할인 정책이 존재한다.
    • 기본 정책의 계산 결과에 적용된다. 기본 정책의 계산이 끝난 결과에 세금을 부과한다.
    • 선택적으로 적용할 수 있다. 기본 정책의 계산 결과에 세금 정책을 적용할 수도 있고 적용하지 않을 수도 있다.
    • 조합 가능하다. 부가 정책 각각을 단일로 적용하는 것도 가능하고, 함께 적용하는 것도 가능하다.
    • 부가 정책은 임의의 순서로 적용 가능하다. 부가 정책을 적용하는 순서는 임의로 조정 가능하다.

     

    기본 정책과 부가 정책의 조합 가능한 수가 매우 많다. 따라서 설계는 다양한 조합을 수용할 수 있도록 유연해야 한다.

     

    10장에서 보았던 상속을 통해 구현된 기본 정책 코드는 아래와 같다.

    public abstract class Phone {
        private List<Call> calls = new ArrayList<>();
    
        public Money calculateFee() {
            Money result = Money.ZERO;
    
            for(Call call : calls) {
                result = result.plus(calculateCallFee(call));
            }
    
            return result;
        }
    
        abstract protected Money calculateCallFee(Call call);
    }
    
    ----
    
    public class RegularPhone extends Phone {
        private Money amount;
        private Duration seconds;
    
        public RegularPhone(Money amount, Duration seconds) {
            this.amount = amount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
        }
    }
    
    ----
    
    public class NightlyDiscountPhone extends Phone {
        private static final int LATE_NIGHT_HOUR = 22;
    
        private Money nightlyAmount;
        private Money regularAmount;
        private Duration seconds;
    
        public NightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds) {
            this.nightlyAmount = nightlyAmount;
            this.regularAmount = regularAmount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            if (call.getFrom().getHour() >= LATE_NIGHT_HOUR) {
                return nightlyAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            } else {
                return regularAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            }
        }
    }

     

    기본 정책에 부가 정책 중 하나인 세금 정책 조합하기

    • 가장 간단한 방법은 RegularPhone 클래스를 상속받은 TaxableRegularPhone 클래스를 추가하는 것이다.
    public class TaxableRegularPhone extends RegularPhone {
        private double taxRate;
    
        public TaxableRegularPhone(Money amount, Duration seconds,
                                   double taxRate) {
            super(amount, seconds);
            this.taxRate = taxRate;
        }
    
        @Override
        public Money calculateFee() {
            Money fee = super.calculateFee();
            return fee.plus(fee.times(taxRate));
        }
    }

     

    위처럼 짜게될 경우, 10장에서 나온 내용처럼 부모 클래스의 메서드를 재사용하기 위해 super 호출을 사용하면 원하는 결과를 쉽게 얻을 수 있지만 자식 클래스와 부모 클래스 사이의 결합도가 높아진다.

    • 따라서 역시 10장에서 나온 것 처럼 추상메서드에 의존하도록 변경하자. afterCalculated가 추가되었고, 전체적으로 모든 클래스가 변경되었다.
    public abstract class Phone {
        private List<Call> calls = new ArrayList<>();
    
        public Money calculateFee() {
            Money result = Money.ZERO;
    
            for(Call call : calls) {
                result = result.plus(calculateCallFee(call));
            }
    
            return afterCalculated(result);
        }
    
        protected abstract Money calculateCallFee(Call call);
        protected abstract Money afterCalculated(Money fee);
    }
    
    ----
    
    public class RegularPhone extends Phone {
        private Money amount;
        private Duration seconds;
    
        public RegularPhone(Money amount, Duration seconds) {
            this.amount = amount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee;
        }
    }
    
    ----
    
    public class NightlyDiscountPhone extends Phone {
        private static final int LATE_NIGHT_HOUR = 22;
    
        private Money nightlyAmount;
        private Money regularAmount;
        private Duration seconds;
    
        public NightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds) {
            this.nightlyAmount = nightlyAmount;
            this.regularAmount = regularAmount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            if (call.getFrom().getHour() >= LATE_NIGHT_HOUR) {
                return nightlyAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            } else {
                return regularAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            }
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee;
        }
    }

     

    이 때, 부모 클래스에 추상 메서드를 추가하면 모든 자식 클래스들이 추상 메서드를 오버라이딩해야 하는 문제가 발생함을 알 수 있다.

    • 또한 모든 추상메서드의 구현이 동일하다. 이걸 해결하려면 Phone에서 afterCalculated 메서드의 기본 구현을 함께 제공해야 한다. (추상 메서드와 동일하게 자식 클래스에서 오버라이딩할 의도로 메서드를 추가했지만 편의를 위해 기본 구현을 제공하는 메서드를 '훅 메서드(hook method)' 라고 부른다.)
    public abstract class Phone {
        private List<Call> calls = new ArrayList<>();
    
        public Money calculateFee() {
            Money result = Money.ZERO;
    
            for(Call call : calls) {
                result = result.plus(calculateCallFee(call));
            }
    
            return afterCalculated(result);
        }
    
        protected Money afterCalculated(Money fee) {
            return fee;
        }
    
        protected abstract Money calculateCallFee(Call call);
    }
    
    ----
    
    public class RegularPhone extends Phone {
        private Money amount;
        private Duration seconds;
    
        public RegularPhone(Money amount, Duration seconds) {
            this.amount = amount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
        }
    }
    
    ----
    
    public class NightlyDiscountPhone extends Phone {
        private static final int LATE_NIGHT_HOUR = 22;
    
        private Money nightlyAmount;
        private Money regularAmount;
        private Duration seconds;
    
        public NightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds) {
            this.nightlyAmount = nightlyAmount;
            this.regularAmount = regularAmount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            if (call.getFrom().getHour() >= LATE_NIGHT_HOUR) {
                return nightlyAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            } else {
                return regularAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            }
        }
    }
    
    ----
    
    public class TaxableRegularPhone extends RegularPhone {
        private double taxRate;
    
        public TaxableRegularPhone(Money amount, Duration seconds, double taxRate) {
            super(amount, seconds);
            this.taxRate = taxRate;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.plus(fee.times(taxRate));
        }
    }

     

    이제 심야 할인 요금제인 NightlyDiscountPhone에도 세금을 부과할 수 있도록 추가하자.

    public class TaxableNightlyDiscountPhone extends NightlyDiscountPhone {
        private double taxRate;
    
        public TaxableNightlyDiscountPhone(Money nightlyAmount, Money regularAmount, Duration seconds, double taxRate) {
            super(nightlyAmount, regularAmount, seconds);
            this.taxRate = taxRate;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.plus(fee.times(taxRate));
        }
    }
    • 이렇게되면 TaxableNightlyDiscountPhone과 TaxableRegularPhone 사이에 코드를 중복했다는 문제가 발생한다.
    • 자바를 비롯한 대부분의 객체지향 언어는 단일 상속만 지원하기 때문에 상속으로 인해 발생하는 중복 코드 문제를 해결하기가 쉽지 않다.

     

    이제 기본 정책에 기본 요금 할인 정책을 조합한 코드를 추가하자.

    public class RateDiscountableRegularPhone extends RegularPhone {
        private Money discountAmount;
    
        public RateDiscountableRegularPhone(Money amount, Duration seconds, Money discountAmount) {
            super(amount, seconds);
            this.discountAmount = discountAmount;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.minus(discountAmount);
        }
    }
    
    ----
    
    public class RateDiscountableNightlyDiscountPhone extends NightlyDiscountPhone {
        private Money discountAmount;
    
        public RateDiscountableNightlyDiscountPhone(Money nightlyAmount,
                                                    Money regularAmount, Duration seconds, Money discountAmount) {
            super(nightlyAmount, regularAmount, seconds);
            this.discountAmount = discountAmount;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.minus(discountAmount);
        }
    }

    • 어떤 클래스를 선택해느냐에 따라 적용하는 요금제의 조합이 결정된다.
    • 이번에도 역시 중복 코드가 추가되었다.

     

    중복의 덫

    • 상속을 이용한 해결 방법은 모든 가능한 조합별로 자식 클래스를 하나씩 추가하는 것이다.
    • 두 가지의 부가 정책을 혼합한 정책과, 새로운 부가 정책을 추가하게 되면 아래와 같이 될 것이다.

    • 새로운 부가 정책을 추가하기 위해 5개의 새로운 클래스가 추가되었고, 두 부가 정책이 혼합된 정책과 새로운 부가 정책 모두 중복 코드가 추가되었다.

     

    이처럼 상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우를 가리켜 클래스 폭발(class explosion) 문제 또는 조합의 폭발(combinational explosion) 문제라고 부른다.

    • 자식 클래스가 부모 클래스의 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제이다.
    • 컴파일타임에 결정된 자식 클래스와 부모 클래스 사이의 관계는 변경될 수 없기 때문에 자식 클래스와 부모 클래스의 다양한 조합이 필요한 상황에서 유일한 해결 방법은 조합의 수만큼 새로운 클래스를 추가하는 것뿐이다.

     

     또한 세금 정책을 변경해야 한다면 관련 코드가 여러 클래스 안에 중복돼 있기 때문에 세금 정책과 관련된 모든 클래스를 찾아 동일한 방식으로 수정해야 할 것이다. 하나라도 누락한다면 세금이 부과되지 않는 버그가 발생할 것이다. 

     

    최선의 방법은 상속을 포기하는 것이다.

     


    03 합성 관계로 변경하기

    ⚈ 합성은 컴파일타임 관계를 런타임 관계로 변경함으로써 '02'의 문제를 해결한다. 합성을 사용하면 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 런타임에 객체의 관계를 변경할 수 있다.

     

    합성을 사용하면 구현 시점에 정책들의 관계를 고정시킬 필요가 없으며 실행 시점에 정책들의 관계를 유연하게 변경할 수 있게 된다.

     

    코드가 유연해지면 복잡성은 올라간다. 대부분의 경우에는 단순한 설계가 정답이지만 변경에 따르는 고통이 복잡성으로 인한 혼란을 넘어서고 있다면 유연성의 손을 들어주는 것이 현명한 판단일 확률이 높다.

    • 변경하기 편리한 설계를 만들기 위해 복잡성을 더하고 나면 원래의 설계보다 단순해지는 경우도 종종 볼 수 있다. -> 이하 코드들에서 볼 수 있음

     

    기본 정책 합성하기

    public interface RatePolicy {
        Money calculateFee(Phone phone);
    }
    
    ----
    
    public abstract class BasicRatePolicy implements RatePolicy {
        @Override
        public Money calculateFee(Phone phone) {
            Money result = Money.ZERO;
    
            for(Call call : phone.getCalls()) {
                result.plus(calculateCallFee(call));
            }
    
            return result;
        }
    
        protected abstract Money calculateCallFee(Call call);
    }
    
    ----
    
    public class RegularPolicy extends BasicRatePolicy {
        private Money amount;
        private Duration seconds;
    
        public RegularPolicy(Money amount, Duration seconds) {
            this.amount = amount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
        }
    }
    
    ----
    
    public class NightlyDiscountPolicy extends BasicRatePolicy {
        private static final int LATE_NIGHT_HOUR = 22;
    
        private Money nightlyAmount;
        private Money regularAmount;
        private Duration seconds;
    
        public NightlyDiscountPolicy(Money nightlyAmount, Money regularAmount, Duration seconds) {
            this.nightlyAmount = nightlyAmount;
            this.regularAmount = regularAmount;
            this.seconds = seconds;
        }
    
        @Override
        protected Money calculateCallFee(Call call) {
            if (call.getFrom().getHour() >= LATE_NIGHT_HOUR) {
                return nightlyAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
            }
    
            return regularAmount.times(call.getDuration().getSeconds() / seconds.getSeconds());
        }
    }
    
    ----
    
    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);
        }
    }

     

    부가 정책 적용하기

    public abstract class AdditionalRatePolicy implements RatePolicy {
        private RatePolicy next;
    
        public AdditionalRatePolicy(RatePolicy next) {
            this.next = next;
        }
    
        @Override
        public Money calculateFee(Phone phone) {
            Money fee = next.calculateFee(phone);
            return afterCalculated(fee) ;
        }
    
        abstract protected Money afterCalculated(Money fee);
    }
    
    ----
    
    public class TaxablePolicy extends AdditionalRatePolicy {
        private double taxRatio;
    
        public TaxablePolicy(double taxRatio, RatePolicy next) {
            super(next);
            this.taxRatio = taxRatio;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.plus(fee.times(taxRatio));
        }
    }
    
    ----
    
    public class RateDiscountablePolicy extends AdditionalRatePolicy {
        private Money discountAmount;
    
        public RateDiscountablePolicy(Money discountAmount, RatePolicy next) {
            super(next);
            this.discountAmount = discountAmount;
        }
    
        @Override
        protected Money afterCalculated(Money fee) {
            return fee.minus(discountAmount);
        }
    }

     

    이제 새로운 정책 추가는 하나의 클래스 추가만으로 가능해진다.

     

    필요한 조합의 수만큼 매번 새로운 클래스를 추가해야 했던 '02'의 상속 방식과 비교해보자.

     

    더 중요한 것은 요구사항을 변경할 때 오직 하나의 클래스만 수정해도 된다는 것이다.

    • 변경 후의 설계는 단일 책임 원칙을 준수하고 있다.

     

    객체지향에서 코드 재사용하면서도 건전한 결합도를 유지할 수 있는 더 좋은 방법은 합성을 이용하는 것이다.

     

    상속을 사용해서는 안 되는 것인가?

    • 상속은 구현 상속과 인터페이스 상속의 두 가지로 나눠야 한다.
    • 이 글에서 살펴본 상속에 대한 모든 단점들은 구현 상속에 국한된다.

     


    04 믹스인

    믹스인(mixin) : 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법

    댓글