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()
}
1 Answer 1
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:
- having the fying behavior as parameter to the
DuckAbilities
constructor; - 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.
-
\$\begingroup\$ Direct answer, I'd still use a different language at least to study design patterns. \$\endgroup\$Maarten Bodewes– Maarten Bodewes2020年02月13日 22:32:11 +00:00Commented Feb 13, 2020 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\$2020年02月13日 22:40:48 +00:00Commented Feb 13, 2020 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 makeDuck
an abstract base class (ABC) to avoid instantiation. \$\endgroup\$Maarten Bodewes– Maarten Bodewes2020年02月13日 22:43:51 +00:00Commented Feb 13, 2020 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\$2020年02月13日 22:51:29 +00:00Commented Feb 13, 2020 at 22:51
Interface
implementation. Due to JS's nature proper interface-y code will not look like the examples inHead First.
Google for JS specific examples - which will be different depending on the version of JS you use. \$\endgroup\$Head First
book and forget JS for its sake - I like theHead First
series a lot & have several of their titles, includingDesign Patterns
. \$\endgroup\$