3
\$\begingroup\$

I'm going through the Head First Design Patterns book and I want to check whether I'm understanding some aspects of the first chapter. Does the code below program correctly to interfaces, encapsulate changeable behavior, and employ composition in a reasonable manner?

// trying to enforce some design pattern habits

// create a duck, that prints a line to the page as text
class DuckAbilities {
    constructor() {
        this.flying = new FlyBehavior()
    }
    addToPage() {
        this.statement = document.createElement("p")
        this.statement.innerHTML=this.flying.fly()
        document.body.append(this.statement)
    }
}
//interface for behaviors
class FlyBehavior {
    fly() {
        null
    }
 }

class DoesFly{
    fly() {
        return "I'm flying"
    }
}
class DoesNotFly {
    fly() {
        return "not flying"
    }
}

class Mallard {
    constructor() {
        this.abilities = new DuckAbilities()
        this.abilities.flying = new DoesFly()
    }

}

class Rubber{
    constructor() {
        this.abilities = new DuckAbilities()
        this.abilities.flying = new DoesNotFly()
    }
}


window.onload = ()=> {
    let mallard = new Mallard()
    mallard.abilities.addToPage()
    let rubber = new Rubber()
    rubber.abilities.addToPage()
}

\$\endgroup\$
4
  • 2
    \$\begingroup\$ As Javascript, no, this is not "spiritually" nor technically an Interface implementation. Due to JS's nature proper interface-y code will not look like the examples in Head First. Google for JS specific examples - which will be different depending on the version of JS you use. \$\endgroup\$
    – radarbob
    Feb 9 '20 at 4:11
  • \$\begingroup\$ And searching StackOverflow can be very helpful as well. \$\endgroup\$
    – radarbob
    Feb 9 '20 at 4:25
  • 1
    \$\begingroup\$ Javascript syntax does not map well to the classic OOP pattern class diagrams. I'd stick with the Head First book and forget JS for its sake - I like the Head First series a lot & have several of their titles, including Design Patterns. \$\endgroup\$
    – radarbob
    Feb 9 '20 at 4:34
  • \$\begingroup\$ Thank you for the additional insight! I'll try to implement code from this book in a language that has interfaces, and I think it will confuse me less because it will be of obvious use. \$\endgroup\$
    – Li Brary
    Feb 10 '20 at 5:09
3
\$\begingroup\$

There are some issues with your class, but I don't think that they are so specific to implementing a strategy pattern.

addToPage() {
    this.statement = document.createElement("p")
    this.statement.innerHTML=this.flying.fly()
    document.body.append(this.statement)
}

This assumes there is a global document variable, and that statement is part of the abilities object. If such a method is present, I would expect document to be a parameter of the method and statement to be a local variable. But even that doesn't seem right because you would be mixing representation (a paragraph "p") and a data class.

//interface for behaviors
class FlyBehavior {
    fly() {
        null
    }
 }

Now that doesn't seem right. If you have JS ducktyping then you don't need this class. Furthermore, specifying null is just asking for null pointer exceptions at a later stage.

this.abilities = new DuckAbilities()

OK, so now we have DuckAbilities object but without a valid state, just null, which we then adjust in the next call. There seem to be two ways of resolving this issue:

  1. having the fying behavior as parameter to the DuckAbilities constructor;
  2. removing the DuckAbilities altogether and just assigning the various flybehaviors to a field.

So when we're using classes anyway, lets implement it using those.

I've created an "abstract" class Duck because we require inheritance there. I don't like to create an interface for the strategies because JavaScripts duck-typing should be sufficient.

'use strict'

class Duck { // this is the context
    constructor(flyAbility) {
        // ES2015 only, avoid instantiation of Duck directly
        // if (new.target === Abstract) {
        //  throw new TypeError("Cannot construct Abstract instances directly");
        // }

        this.flyAbility = flyAbility;
    }

    // this is the operation, returning the flyBehavior as a string
    showFlyBehavior() {
        return "This duck " + this.flyAbility.flyBehavior();
    }
}

// Strategy interface is missing due to JS ducktyping

// Strategy #1 
class DoesFly {
    // with algorithm #1
    flyBehavior() {
        return "flies";
    }
}

// Strategy #2
class DoesNotFly {
    // with algorithm #2
    flyBehavior() {
        return "doesn't fly";
    }
}

// Context #1
class Mallard extends Duck {
    constructor() {
        super(new DoesFly());
    }
}

// Context #2
class Rubber extends Duck {
    constructor() {
        super(new DoesNotFly());
    }
}

let duck = new Mallard();
console.log(duck.showFlyBehavior());

duck = new Rubber();
console.log(duck.showFlyBehavior());

Sorry about using NodeJS, but in principle only console.log is NodeJS specific ... I hope.

\$\endgroup\$
4
  • \$\begingroup\$ Direct answer, I'd still use a different language at least to study design patterns. \$\endgroup\$ Feb 13 '20 at 22:32
  • \$\begingroup\$ In Python FlyBehavior would be an ABC, which is known simply as abstract classes elsewhere. If JavaScript supported this; do you think it would be a good use case here? \$\endgroup\$
    – Peilonrayz
    Feb 13 '20 at 22:40
  • \$\begingroup\$ No, not really, because FlyBehavior doesn't have any generic behavior. Python is as much ducktyped as JavaScript, so it is just not necessary. However, with Python you could at least make Duck an abstract base class (ABC) to avoid instantiation. \$\endgroup\$ Feb 13 '20 at 22:43
  • \$\begingroup\$ Ok, that makes a lot of sense to me. I would assume if this were TypeScript - since the duck typing kinda flys out the window, FlyBehaviour would become an interface. Thank you. :) \$\endgroup\$
    – Peilonrayz
    Feb 13 '20 at 22:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.