Hoinzey

Javascript. Kotlin. Android. Java

Composition > Inheritance (HashSet example)

It is much easier to use inheritance incorrectly than to use it correctly. Don't get me wrong, you can have classes that inherit, work fine and are some of your favorite classes, I get it. But you should only inherit from classes which have been explicitly designed to be inherited from


I'm going to go through a quick example using a HashSet - Imagine for whatever reason you want to count how many times you add something to this hashset. You sit at your desk and thing "bazinga!" and come up with a piece of code like this:

CountedHashSet
main
                            
    public class CountedHashSet<E> extends HashSet<E> {

        private int addCount = 0;

        public CountedHashSet() {}

        public CountedHashSet(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;
        }
    }
                        
                            
    CountedHashSet chocolateBars = new CountedHashSet<>();
    chocolateBars.addAll(List.of("Cadburys", "Hersheys", "Lindt"));

    System.out.println(chocolateBars.getAddCount());
    // 6
                        

You would expect this to print out 3, but it prints out 6. This is because the addAll() call is implemented on top of its add() method. So we count the size of the given list, then end up counting again each time it is actually added. We could simply remove the count from addAll(), or override it and loop through the list given and call the add() method but the very fact we are jumping through so many hoops is itself a sign of danger.


If we make those changes our class will be fragile and if a new version of Java comes out that alters how its add() or addAll() methods behave it could break our class even though our code was not altered at all.


So instead of extending an existing class we are going to create a new class with a private field that references an instance of the existing class. Designing a class like this is called composition.


ComposedCountedSet
ForwardingSet
                            
    public class ComposedCountedSet<E> extends ForwardingSet<E>{

        private int addCount = 0;
    
        public ComposedCountedSet(Set<E> 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;
        }
    }
                        
                            
    public class ForwardingSet<E> implements Set<E> {

        private final Set<E> set; // <-- Composition
    
        public ForwardingSet(Set<E> set) {
            this.set = set;
        }
    
        @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 add(E e) { return set.add(e); }
    
        @Override
        public boolean remove(Object o) { return set.remove(o); }
    
        @Override
        public boolean containsAll(Collection<?> c) { return set.containsAll(c); }
    
        @Override
        public boolean addAll(Collection<? extends E> c) { return set.addAll(c); }
    
        @Override
        public boolean retainAll(Collection<?> c) { return set.retainAll(c); }
    
        @Override
        public boolean removeAll(Collection<?> c) { return set.removeAll(c); }
    
        @Override
        public void clear() { set.clear(); }
    }
                        

Now this could have been achieved in a single class, but this design is powerful as it allows us to use any instance of type Set to work with whereas inheritance would require a concrete class and a separate constructor for each type.

Resources

This post was written after reading Item 18 in Effective Java, third edition by Joshua Bloch. I would highly recommend this book from my reading so far and to clarify - I am in no way associated with it.