JavaScript design patterns - Prototype Pattern

in #code6 years ago (edited)

ThumbTemplate - Prototype Pattern.png

Prototype pattern - part of a series on JavaScript design patterns

In Object-Orented Programming the concept of inheritance is very important. We can define an object that is a template or class of an object, it defines an objects properties and methods. When we use that class or template to create a new object, the newly created object inherits those properties and methods.

So if I have an class or object template like this:

    var myObject = {
        this.someProperty;
        this.someMethod = function() {
            // ..some code...
        }
    }

We know that if we create an instance of that object that the new instance will contain its own someProperty property and its own someMethod method. Essentially, we are creating duplicates of the property and method every time we instantiate a new object.

We can't avoid this with the properties, when you think about it the whole reason we use objects is so that individual objects have their own properties with their own values. No point having two of the same objects with the exact same properties - they might aswell be the same object, right?

Method inheritance

Does the same argument for properties apply to methods? Let's take a look at a basic example:

    // Class or blueprint for a shape.
    var aShape = function(shape) {
        this.sides;
        
        // Decide how many sides the shape has and assign the value
        // to sides.
        if (shape == "triangle") this.sides = 3;
        if (shape == "square") this.sides = 4;
        if (shape == "pentagon") this.sides = 5;
        if (shape == "hexagon") this.sides = 6;
    }

    // Let's create 4 lovely shapes!
    var triangle = new aShape("triangle");
    var square = new aShape("square");
    var pentagon = new aShape("pentagon");
    var hexagon = new aShape("hexagon");

    // Let's have a look at each shape and see how many sides
    // they each have...
    console.log(`Triangle has ${triangle.sides} sides`);
    console.log(`Square has ${square.sides} sides`);
    console.log(`Pentagon has ${pentagon.sides} sides`);
    console.log(`Hexagon has ${hexagon.sides} sides`);

Very nice, we like shapes! Now, I don't know about you but I don't like the way we're calling the console.log for each shape like that, we could tidy this up using object methods. There are loads of ways we can add methods to our object, for example let's dynamically add a new method to the triangle shape, a new method that will tell us how many sides a triangle has...

    var aShape = function(shape) {
        this.sides;
        
        if (shape == "triangle") this.sides = 3;
        if (shape == "square") this.sides = 4;
        if (shape == "pentagon") this.sides = 5;
        if (shape == "hexagon") this.sides = 6;
    }

    var triangle = new aShape("triangle");
    var square = new aShape("square");
    var pentagon = new aShape("pentagon");
    var hexagon = new aShape("hexagon");

    // Add a showSides method to the triangle object.
    triangle.showSides = function() {
        console.log(`Triangle has ${this.sides} sides`)
    }

    triangle.showSides();   // This will work
    square.showSdes();      // This won't

This clearly illustrates an important point, it shows us that an object can deviate from the class or template that it derives from - it's scaleable. We can have two instances of the same object but each instance may have unique properties or methods. In this case, triangle has a showSides method but none of the other shape objects do.

Simple stuff, right?

So you've probably guessed by now that a better option would be to add the method as part of the class definition so that each object we create from the class will inherit that particular method...a-like a-so:

    var aShape = function(shape) {
        this.sides;
        this.shape = shape;
        
        if (shape == "triangle") this.sides = 3;
        if (shape == "square") this.sides = 4;
        if (shape == "pentagon") this.sides = 5;
        if (shape == "hexagon") this.sides = 6;
        
        this.showSides = function() {
            console.log(`${this.shape} has ${this.sides} sides`);
        }
    }

    var triangle = new aShape("triangle");
    var square = new aShape("square");
    var pentagon = new aShape("pentagon");
    var hexagon = new aShape("hexagon");

    triangle.showSides();
    square.showSides();
    pentagon.showSides();
    hexagon.showSides();

Yaaay! That works, and it's so much more elegant! Or ir it? Maybe it's wasteful, since each time we create a new object from this class we are essentially duplicating the showSides method. Now this is a very small method, only one line of code being executed so it's unlikely to be much of an issue.

Imagine we had a class with lots of methods, and they're all pretty big. We create a load of these objects and we're duplicating all of this code without even realising it! We have the exact same method being cloned and assigned to an object each time we create it.

Prototypal inheritance

This is where prototypal inheritance comes in. What if we could define a single showSides method that will be inherited, but there is only one instance of the method function that is shared among all of the objects? We can do this by making the function a prototype of the class...ch-ch-check it out:

    var aShape = function(shape) {
        this.sides;
        this.shape = shape;
        
        if (shape == "triangle") this.sides = 3;
        if (shape == "square") this.sides = 4;
        if (shape == "pentagon") this.sides = 5;
        if (shape == "hexagon") this.sides = 6;
    }

    // Add showSides as a prototype of the aShape object.
    aShape.prototype.showSides = function() {
        console.log(`${this.shape} has ${this.sides} sides`);
    }

    var triangle = new aShape("triangle");
    var square = new aShape("square");
    var pentagon = new aShape("pentagon");
    var hexagon = new aShape("hexagon");

    triangle.showSides();
    square.showSides();
    pentagon.showSides();
    hexagon.showSides();

How cool is that? So now all of our shape objects have access to the showSides method, the showSides method exists as a single, prototype method which is inherent to each object we create from the class. All objects will reference the exact same block of code rather than have its own internal copy of the method.

You may be thinking - yeah, big deal...more than one way to crack an egg! How enlightening! How entertaining! Etcetera.

Bigger picture

You're missing the bigger picture, though. For example, let's imagine that you wanted to implement a method that is inherent to every single array you create -- we can do this! Why do you thnk we have array methods at all? Take forEach as an example:

    var arr = [ "one", "two", "three" ];
    
    arr.forEach(element => {
        console.log(`element = ${element}`);
    });

We could create our own forEach method that is an Array prototype quite easily:

    Array.prototype.myForEach = function(callback) {
        // All we do is iterate through each element in the
        // array and pass it to the given callback function
        for (var i = 0; i < this.length; i++) {
            callback(this[i]);
        }
    };

    var arr = [ "one", "two", "three" ];
    
    // Our own version of forEach()!
    arr.myForEach(e => {
        console.log(`e = ${e}`);
    });

So you can see how this is useful because...well, because we use it all the time! Higher order functions like map() and reduce() are implemented in this way. String methods like .toString() are implemented in this way. Do you think every String object you create has its own internal copy of the .toString() method? Or that every array you create has it's own internal map(), filter(), reduce() etc?

In terms of practicality, prototyping is just as useful as classical inheritance in OOP, some may argue that it's more robust and flexible than classical inheritance...some might totally disagree, some people just don' liiiiike it! They don' liiiik yewr kiiiind roun' heyuh! They don't like change, or throwing away the methods of old and embracing the methods of new.

Well, I like it! I like how we can add custom methods for things like Array and String.

Prototying and inheritance in general are pretty big concepts in programming so there's so much more to it. As a pattern, just like inheritance in traditional OOP, prototyping is becoming extremely prevalent in the JavaScript world so get used to it!

Make use of it!

Just...just make sure you're having fun with it, m'kay?

Sort:  

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Congratulations @heathenjezzie! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.18
TRX 0.16
JST 0.030
BTC 68164.82
ETH 2641.32
USDT 1.00
SBD 2.70