Start learning free

LESSON 187 OF 344 · OBJECT-ORIENTED PROGRAMMING

OOP Capstone in JavaScript: An Animal Class Hierarchy

Lesson Read the full explanation

TL ; DR:

Put it all together: an abstract parent class, three concrete subclasses, and a polymorphic loop that dispatches by runtime type. This is the shape of nearly every real OOP design.


class Animal {
    constructor(name) { this.name = name; }
    sound() { throw new Error("Not implemented"); }
    speak() {                  // shared by all
        console.log(this.name + ": " + this.sound());
    }
}
// Dog, Cat, Bird each override sound
// let zoo = [new Dog("Rex"), ...];
// for (let a of zoo) a.speak();


Full lesson:

This problem stitches together everything you've learned in this chapter:

  1. Inheritance — three concrete classes share a common parent.
  2. Constructors — each subclass forwards a name to the parent.
  3. Abstract methods — the parent declares sound() with no body; each child must provide one.
  4. Shared behavior — the parent has a concrete speak() that calls sound(); every subclass inherits it for free.
  5. Polymorphism — a collection typed as Animal holds a Dog, a Cat, and a Bird; speak() dispatches to the right sound() at runtime.

The shape of this code is the shape of nearly every real OOP design you'll see — a small contract at the top (the abstract parent), concrete variants below, and the rest of the program written against the abstract type. New variants get added without touching the consumer code.


Your task

The abstract Animal class is already defined, with a concrete speak() that calls the abstract sound(). Add three subclasses — Dog (returns "Woof"), Cat (returns "Meow"), and Bird (returns "Tweet"). Then in MainFunction / main, build a polymorphic collection holding one of each (Rex, Whiskers, Tweety in that order) and loop through it calling speak().

Expected output:

Rex: Woof
Whiskers: Meow
Tweety: Tweet
STEP 1 OF 2
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING

    Oop Capstone in JavaScript, explained

    What this capstone brings together

    The program in this lesson uses every idea from the object-oriented chapter in one small design:

    1. Inheritance. Dog, Cat and Bird all extend one parent, Animal.
    2. Constructors. Animal stores the name, and the subclasses reuse that constructor.
    3. An abstract method. Animal declares sound() but gives it no real behaviour. Each child must supply its own.
    4. Shared behaviour. Animal has a finished speak() that calls sound(), so every subclass gets it for free.
    5. Polymorphism. One array holds a dog, a cat and a bird, and one loop treats them all the same way while each prints its own sound.

    The shape matters more than the animals: a small contract at the top, concrete variants below, and the rest of the program written against the parent type. A new variant can be added without changing the code that uses the animals.

    The starting point

    The lesson gives you the parent class:

    class Animal {
        constructor(name) { this.name = name; }
        sound() { throw new Error("Not implemented"); }
        speak() {
            console.log(this.name + ": " + this.sound());
        }
    }
    

    JavaScript has no abstract keyword, so sound() is an abstract method by convention: its body throws. A subclass that forgets to override it fails with Not implemented the first time speak() is called on it. speak() is the concrete, shared method. It builds a line from this.name and whatever this.sound() returns.

    The task asks for three subclasses and a loop. The output must be, in this order:

    Rex: Woof
    Whiskers: Meow
    Tweety: Tweet
    

    Step by step

    Step 1: three subclasses

    Write class Dog extends Animal, and the same for Cat and Bird. Each class needs one method, sound(), which returns its string: "Woof", "Meow" and "Tweet". It returns the text rather than printing it, because the inherited speak() concatenates the result into a longer line and does the printing itself. A sound() that logged would print the bare sound and then speak() would build "Rex: undefined".

    The subclasses define no constructor. When a class has none, JavaScript supplies one that passes all its arguments to the parent constructor, so new Dog("Rex") runs Animal's constructor and sets this.name. You would write an explicit constructor, calling super(name) first, only if a subclass needed extra fields of its own.

    Step 2: the polymorphic collection and loop

    Build an array named animals holding new Dog("Rex"), new Cat("Whiskers") and new Bird("Tweety"), in that order, because the output follows the array's order. Then loop with for (let a of animals) and call a.speak().

    The loop never asks what kind of animal it has. When it calls a.speak(), JavaScript finds speak on Animal. Inside it, this is the actual object, and this.sound() is looked up starting from that object's own class. So the dog's turn reaches Dog's sound(), the cat's reaches Cat's, and so on. That lookup at run time is what polymorphism means here.

    The solution in JavaScript

    class Animal {
        constructor(name) { this.name = name; }
        sound() { throw new Error("Not implemented"); }
        speak() {
            console.log(this.name + ": " + this.sound());
        }
    }
    
    class Dog extends Animal {
        sound() { return "Woof"; }
    }
    
    class Cat extends Animal {
        sound() { return "Meow"; }
    }
    
    class Bird extends Animal {
        sound() { return "Tweet"; }
    }
    
    let animals = [new Dog("Rex"), new Cat("Whiskers"), new Bird("Tweety")];
    for (let a of animals) {
        a.speak();
    }
    

    Running it prints the three expected lines. A few JavaScript details are worth noting:

    • for...of versus for...in. for (let a of animals) gives you the elements. for (let a in animals) would give the index keys as strings, "0", "1", "2", and a.speak() would fail on a string.
    • Method lookup goes up the prototype chain. A Dog object has no speak of its own. The call finds it on Animal.prototype, which is where extends connects Dog.prototype to.
    • The overridden method wins. sound exists on both Animal and Dog, and the lookup stops at the first one it finds, which is Dog's.
    • Classes are not hoisted like functions. Each class must appear before the line that creates its instances. The array is built after the three classes for that reason.

    Extending the design

    To add a cow, write a fourth class with one method and put an instance in the array. The loop and Animal stay as they are:

    class Cow extends Animal {
        sound() { return "Moo"; }
    }
    animals.push(new Cow("Bessie"));
    

    The next pass over animals ends with Bessie: Moo. That is the benefit of writing the consumer against the parent type.

    Common mistakes

    • Printing instead of returning in sound(). The result is a line with undefined in it, since a function without a return gives back undefined.
    • Wrong order in the array. The lines come out in array order, so Rex, Whiskers, Tweety must stay in that order.
    • Forgetting extends. Without it, Dog doesn't inherit speak or the constructor, and new Dog("Rex").speak() fails with speak is not a function.
    • Misspelling an override. A method named sounds() or Sound() overrides nothing. The inherited stub runs and throws Not implemented.
    • Calling new Animal("x").speak(). Animal is meant only as a parent. Creating one directly works, but speak() then throws, because no sound() has been provided.
    • Calling super.sound() in a child. That reaches the stub and throws. Return a string instead.

    Key takeaways

    • Put what all variants share in the parent, and let the children supply only what differs.
    • A method that throws is JavaScript's convention for an abstract method.
    • Code written against the parent type works for every subclass, including ones added later.
    • With polymorphism, the method that runs is chosen by the object's actual class when the call happens.
    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free