이펙티브 자바 18

참치돌고래·2021년 11월 9일
0

이펙티브 자바

목록 보기
16/21

상속보다는 컴포지션을 사용하라.

메서드 호출과 달리 상속은 캡슐화를 깨뜨린다.

잘못된 상속의 예

public class InstrumentHashSet<E> extends HashSet<E>{

	private int addCount = 0;
    
    public InstrumentHashSet(){
    }
    
    public InstrumentHashSet(int initCap, float loadFactor){
    	super(initCap, loadFactor);
    }
    
    @Override
    public boolean add(E e){
    	addCount++;
        return super.add(e);
    }
    
    @Overrid
    public boolean addAll(Collection<? extends E> c){
    	addCount += c.size();
        return super.addAll(c);
    }
    
    public int getAddCount(){
    	return addCount;
    }
}
public SampleRunner{
	public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.addAll(List.of("tick", "pung", "ticktick"));
        System.out.println(s.getAddCount());
    }
}

본래 의도했던 결과값은 3이지만, 상위 클래스에서도 중복으로 계산되어서 6으로 출력이 나온다.

컴포지션

기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하자.

  • 전달: 새 클래스의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출 해 그 결과를 반환하는 방식

  • 전달 메서드 : 전달하는 새 클래스의 메서드

이 전달 메서드들은 기존 클래스의 내부 구현 방식의 영향에서 벗어나며, 새로운 메서드가 추가되더라도 전혀 영향을 받지 않는다.

예제코드

class InstrumentedSet<E> extends ForwardingSet<E>{
    private int addCount = 0;

    public InstrumentedSet(Set<E> s){
        super(s);
    }

}
public class ForwardingSet<E> implements Set<E> {

    private final Set<E> s;
    public ForwardingSet(Set<E> s){
        this.s = s;
    }

    public void clear(){
        s.clear();
    }

    public boolean contains(Object o){
        return s.contains(o);
    }

    public int size(){
        return s.size();
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    public Iterator<E> iterator(){
        return s.iterator();
    }

    public boolean add(E e){
        return s.add(e);
    }

    public boolean remove(Object o){
        return s.remove(o);
    }

    public boolean containsAll(Collection<?> c){
        return s.containsAll(c);
    }

    public boolean addAll(Collection<? extends  E> c){
        return s.addAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    public boolean removeAll(Collection<?> c){
        return s.removeAll(c);
    }

    public Object[] toArray(){
        return s.toArray();
    }

    public <T> T[] toArray(T[] a){
        return s.toArray(a);
    }

    @Override
    public String toString() {
        return super.toString();
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public Spliterator<E> spliterator() {
        return Set.super.spliterator();
    }
}

InstrumentedSet은 Set 인터페이스를 활용해 설계되어 Set의 기능에 새로운 계측 기능을 덧씌워 새로운 Set으로 만들어진 클래스이다.

다른 Set인스턴스를 감싸고 있다는 뜻에서 InstrumentedSet은 래퍼 클래스, 데코레이터 패턴이라고 한다.

컴포지션 대신 상속 사용 시 고려할 점

  • 확장하려는 클래스의 API에 아무런 결함이 없는가?

  • 이 결함의 클래스의 API에 전파돼도 괜찮은가?

profile
안녕하세요

0개의 댓글