ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 상속보다는 컴포지션을 사용하라 > 이펙티브 자바 아이템18
    BackEnd/자바 2021. 4. 26. 17:13

    HashSet 에 안에 있는 요소를 count 하는 기능을 추가한 InstrumentedHashSet 이라는 클래스를 만들고자 할 때, 

    HashSet 을 상속해서 만들면 어떤 결과를 가져올까 ?

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
    
        private transient HashMap<E,Object> map;
    
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
       
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<>(initialCapacity, loadFactor);
        }
        
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    
    public abstract class AbstractCollection<E> implements Collection<E> {
    	
        
        public boolean addAll(Collection<? extends E> c) {
            boolean modified = false;
            for (E e : c)
                if (add(e))  //add() 가 자식 클래스에서 override 되어있지 않으면 exception
                    modified = true;
            return modified;
        }
    }
    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 addAll(Collection<? extends E> c) {
            addCount+=c.size();
            return super.addAll(c);
        }
     
        @Override
        public boolean add(E e) {
            addCount++;
            return super.add(e);
        }
        
        public int getAddCount() {
            return addCount;
        }
    }
    public class SetTest {
        public static void main(String[] args) {
            Set<String> s = new InstrumentedHashSet<>();
            s.addAll(Arrays.asList("dd","aa","gg")); //6 
            System.out.println(((InstrumentedHashSet<String>) s).getAddCount());
            
            s.add("s"); //7
            System.out.println(((InstrumentedHashSet<String>) s).getAddCount());
        }
    }
    

     

    HashSet 의 addAll() 은 AbstractCollection 이 정의한 addAll() 을 호출하고,

    AbstractCollection의 allAll() 은 내부의 add를 호출한다.

    그러면 addAll 을 하면 add까지 호출해 addCount 가 실제 요소의 2배만큼 counting 되어 

    결과가 3개가 아니라 6개가 된다.

    그렇다고 add 에서 카운트를 늘려주는 것을 재정의 하지 않으면 add 할 때 당연히 addCount 가 업데이트 되지 않는다.

     

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

    기존 클래스가 구성요소로 쓰인다는 뜻에서 이런 설계를 컴포지션(구성)이라 한다.

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

    public class ForwardingSet<E> implements Set<E>{
    
        private final Set<E> s;
        public ForwardingSet(Set<E> s) {
            this.s=s;
        }
    
        @Override
        public boolean add(E e) {
            return s.add(e);
        }
        
    
        @Override
        public boolean addAll(Collection<? extends E> c) {
            return s.addAll(c);
        }
    	....
    }    

    다른 Set인터페이스를 감싸고 있다는 뜻에서 IstrumentedHashSet 같은 클래스를 wrapper 클래스라 하며,

    다른 Set에 계측기능을 덧씌운다는 뜻에서 데코레이터 패턴이라고 한다.

    컴포지션과 조합은 넓은 의미로 위임이라고 한다. (엄밀히 따지면 래퍼 객체가 내부 객체에 사신의 참조를 넘길 때만 위임에 해당한다.)

    public class InstrumentedHashSet<E> extends ForwardingSet<E> {
        private int addCount=0;
        
        public InstrumentedHashSet(Set<E> s) {
            super(s);
        }
        /*
         * public InstrumentedHashSet(int initCap,float loadFactor) {
         * super(initCap,loadFactor); }
         */
        @Override
        public boolean addAll(Collection<? extends E> c) {
            addCount+=c.size();
            return super.addAll(c);
        }
     
        @Override
        public boolean add(E e) {
            addCount++;
            return super.add(e);
        }
        public int getAddCount() {
            return addCount;
        }
    }
    

    addAll 은 Set 의 abstract 메서드 addAll 을 찾아가고 
    이는 다시 InstrumentedHashSet 에 정의된 addAll 을 호출한다.

    Set 에 있는 메서드 중 InstrumentedHashSet 없지만 HashSet 이 재정의한 메서드 사용가능하다.

    public class SetTest {
        public static void main(String[] args) {
            Set<String> s = new InstrumentedHashSet<>(new HashSet<>(5));
            s.addAll(Arrays.asList("dd","aa","gg"));
            System.out.println(((InstrumentedHashSet<String>) s).getAddCount());
            
            s.add("s");
            System.out.println(((InstrumentedHashSet<String>) s).getAddCount());
        }
    }
    

     

     

    반응형

    'BackEnd > 자바' 카테고리의 다른 글

    GCGC  (0) 2021.05.25
    tdd  (0) 2021.05.11
    Enum  (0) 2021.01.31
    예외처리  (0) 2021.01.16
    인터페이스  (0) 2021.01.09
Designed by Tistory.