본문 바로가기

Java/Java

(JAVA) 상속보다는 컴포지션을 사용하자

http://aeternum.egloos.com/3013830

 

단일 접근 원칙(Uniform Access Principle)을 통한 캡슐화 - (上)

속성과 메서드, 그리고 캡슐화은행 도메인에서 계좌(account)의 주된 용도는 고객의 잔액(balance)을 관리하는 것이다. 객체 지향 분석/설계의 핵심은 실세계의 개념과 유사한(그러나 완전히 동일하지는 않은) 추상 모델을 구축하는 것이므로 유비쿼터스 언어(UBIQUITOUS LANGUAGE)에 포함된 어휘인 account와 balance를 사용해서 도

aeternum.egloos.com

http://aeternum.egloos.com/3028192

 

단일 접근 원칙(Uniform Access Principle)을 통한 캡슐화-(下)[完]

단일 접근 원칙(Uniform Access Principle)은행 계좌 예제가 변경에 취약한 이유는 Account의 balance 속성을 외부에서 직접 변경할 수 있었기 때문이다. 따라서 balance와 관련된 설계 결정을 변경할 경우 public 속성에 의존하고 있는 많은 코드들이 연쇄적으로 영향을 받게 된다. 이를 방지하는 일반적인 방법은 public

aeternum.egloos.com

 

상속의 가장 큰 단점은 캡슐화를 깨뜨린다는 것이다.

부모 클래스가 어떻게 구현되느냐에 따라서 자식 클래스가 영향을 크게 받기 때문에 하위 클래스가 코드 한 줄 건드리지 않았을 언정 상위 클래스가 변경됨으로써 원하는 결과를 얻는데 실패할 수 있다.

 

릴리스마다 상위 클래스는 내부 구현이 달라질 수 있기 때문에 설계자는 상속을 충분히 고려한 설계를 해야 하고 이를 문서화해야 한다.


HashSet을 상속하는 클래스를 구현해보자 - add 할 때마다 count 증가

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

    private int addCount = 0;


    public InstrumentedHashSet() {

    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }

}

 

전혀 문제가 없어 보이는 코드지만 실제로 호출할 때 문제가 발생한다.

class Main {
    public static void main(String[] args) {
        InstrumentedHashSet instance = new InstrumentedHashSet();
        instance.addAll(List.of("choi","jun","woo"));

        System.out.println(instance.getAddCount());


    }
}

 

실행결과

 

3개의 String 값을 addAll 하였지만 결과는 3이 아닌 6이다.

 

 

왜일까?

allAll을 재정의 하였기 때문에 하위 클래스에서 3번 등록하고 상위 클래스에도 3번 등록하기 때문이다. (반복되는 작업)


해결 방법  : Wrapper Class

wrapper 클래스란?
특정 인스턴스를 감싸고 있는 클래스

 

public class InstrumentedHashSet2<E> implements Set<E> {

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


    public void clear()               { s.clear();            }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty()          { return s.isEmpty();   }
    public int size()                 { return s.size();      }
    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);      }
    public boolean removeAll(Collection<?> c)
    { return s.removeAll(c);   }
    public boolean retainAll(Collection<?> c)
    { return s.retainAll(c);   }
    public Object[] toArray()          { return s.toArray();  }
    public <T> T[] toArray(T[] a)      { return s.toArray(a); }
    @Override public boolean equals(Object o)
    { return s.equals(o);  }
    @Override public int hashCode()    { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }

}
public class InstrumentedHashSet3<E> extends InstrumentedHashSet2<E>{
    private int addCount = 0;

    public InstrumentedHashSet3(Set set) {
        super(set);
    }
    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();

        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }


}

위의 코드는 HashSet을 직접적으로 상속받지 않고 HashSet이 구현하는 Set 인터페이스를 재정의한 Wrapper 클래스를 만든다.

그리고 해당 Wrapper 클래스를 상속받는다.

public InstrumentedHashSet3(Set set) {
        super(set);
}

또한 인스턴스 생성 시 파라미터로 Set 타입을 받아서 부모의 인스턴스를 생성한다.

재정의는 자식 클래스에서 하지 않으며 모든 재정의는 부모 클래스 (Wrapper 클래스)에서 한다. 자식은 부모 클래스에서 만든 것을 가져다 쓸 뿐이다.


상속을 굳이 사용하고자 한다면 두 가지를 되새겨 보자

1. is a 관계인가?

2. 확장하는 클래스의 API에는 결함이 정말 없는가?


References

이펙티브 자바 Effective Java 3/E
국내도서
저자 : 조슈아 블로크(Joshua Bloch) / 이복연(개앞맵시)역
출판 : 인사이트 2018.11.01
상세보기

code Link

https://github.com/mike6321/PURE_JAVA/tree/master/EffectiveStudy

 

mike6321/PURE_JAVA

Contribute to mike6321/PURE_JAVA development by creating an account on GitHub.

github.com