Why Extends is Evil

import java.util.ArrayList;
public class  SimpleSentence
{
    private ArrayList<Character> letters = new ArrayList<Character>();

    public void add(char symbol)
    {
        letters.add(symbol);
    }
    public void addWord(char[] word)
    {
        for (int i=0; i < word.length; i++)
        {
            add(word[i]);
        }
    }
    public void remove()
    {
        letters.remove(0);
    }
        
    public String say()
    {
        String result = "";
        for (Character ch: letters)
        {
            result += ch;
        }
        return result;
    } 
}

Say we need a new kind of sentence that has only alphabetic characters,
an AlphaSentence.  It has many of the same characteristics, so let's extend
Simple Sentence and override the add() method.

/**
 * AlphaSentence is a SimpleSentence but comprised of alphabetic characters.
 * It also has a size.
 */
public class AlphaSentence extends SimpleSentence
{

    private int size = 0;
   
    public void add(char symbol)
    {
        if (Character.isLetter(symbol)) // allow only Alphabetic characters
        {
            size++;
            super.add(symbol);
        }
    }

    public int getSize()
    {
        return size;
    }

}

This will compile and run, and the first two test cases pass.  When addWord() is called on AlphaSentence, Java invokes the parent's addWord() method, which then calls add().  Happily, Java is smart enough to know that the call to add() in SimpleSentence should really be to add() in AlphaSentence.

    public void testOne()
    {
        AlphaSentence alphaSen1 = new AlphaSentence();
        alphaSen1.add('D');
        alphaSen1.add('O');
        alphaSen1.add('3');
        alphaSen1.add('G');
        assertEquals(3, alphaSen1.getSize());
    }
    public void testWord()
    {
        char[] dog = {'D','O','3','G'};
        AlphaSentence alphaSen1 = new AlphaSentence();
        alphaSen1.addWord(dog);
        assertEquals(3, alphaSen1.getSize());
    }


Unfortunately testRemove() fails.  Can you tell why?
    public void testRemove()
    {
        char[] dog = {'D','O','G'};
        AlphaSentence alphaSen1 = new AlphaSentence();
        alphaSen1.addWord(dog);
        assertEquals(3, alphaSen1.getSize());
        alphaSen1.remove();
        assertEquals("OG",alphaSen1.say());
        assertEquals(2, alphaSen1.getSize());
    }
???







The solution is that we have to override remove() also. 
    public void remove()
    {
        size--;
        super.remove();
    }
So inheritance bought us a little savings because we don't have to rewrite addWord().
But we have to override remove() and any other methods that manipulate size.
So implementation-inheritance isn't always all it's cracked up to be.

However, a deeper problem still exists.  Let's say that we discover a better implementation of SimpleSentence.  

/* The improved implementation uses StringBuffer instead of ArrayList */
    private StringBuffer letters = new StringBuffer();
    public void add(char symbol) { letters.append(symbol); }
    public void addWord(char[] word) { letters.append(word); }
    public void remove() {letters.deleteCharAt(0); }
    public String say() { return letters.toString(); }   


Now AlphaSentence still compiles and runs, but testWord() fails.
Can you explain why?

This is a big problem.  Changing the implementation of a parent class
might break its children.  So we have to retest all the children, and possibly
change their implementations.  This is another aspect of the Fragile-Base Class problem.
And that's why Extends is Evil.

When would this happen? A common situation is when a developer writes a class, and another developer on the same team writes a subclass, trying to simplify their job by relying on a parent class implementation (visible in the team repository). Then later a third developer changes the parent class implementation, unaware that a child class is reusing that implementation.

A more flexible and robust design approach is to use interface-inheritance and composition.
Can you use this approach to fix this design?
???







public interface Sayable
{
    public void add(char symbol);
    public void addWord(char[] word);
    public void remove();
    public String say();
}

import java.util.ArrayList;
public class  SimpleSentence implements Sayable
{

    private ArrayList<Character> letters = new ArrayList<Character>();

/* The remainder of the class is the same as the original */

}


/**
 * AlphaSentence is a sentence of alphabetic characters.
 */
public final class AlphaSentence implements Sayable
{

    private int size = 0;
    private SimpleSentence words = new SimpleSentence();
   
    public void add(char symbol)
    {
        if (Character.isLetter(symbol)) // allow only Alphabetic characters
        {
            size++;
            words.add(symbol);
        }
    }

    public void addWord(char[] word)
    {
        for (int i=0; i < word.length; i++)
        {
           add(word[i]);
        }
    }
   
    public void remove()
    {
        size--;
        words.remove();
    }
   
    public int getSize()
    {
        return size;
    }
    public String say()
    {
        return words.say();
    }
}

Now if the implementation of SimpleSentence changes, it has no effect on its clients.