The Revealing Module is an anti-pattern
by I-Lin Kuo
who am i
Full stack developer Java/JavaScript
Columbia, MD
BLOG: ilinkuo.wordpress.com
@ilinkuo
https://slides.com/i-linkuo
A couple of warnings
- core JavaScript
- opinion-based
- lots of code
- fast pace
The problem with POJOs
History
- pre 2007: Plain old JavaScript object (POJO)
- _ means private
- 2007 Module Pattern
- 2007 Revealing Module Pattern
The Idea of the Module Pattern
Hide private members inside a closure
Original Module Pattern
- Hide private members inside closure
- Return an object literal
- Define public methods in the literal
- Reference through this
Revealing Module Pattern
- Hide private members inside closure
- Define all members in the closure
- Reveal public members in the literal
- Reference through closure
RMP Advantages
var RMPGreeter=(function(){
var _openDoor=function(){/*...*/};
var _name="Olaf";
var _sayHello=function(){
console.log("Hello, my name is " + _name);
};
var _greet=function(){
_sayHello();
_openDoor();
}
return {
//name: _name,
//sayHello: _sayHello,
greet: _greet
};
})()
- reveal/unreveal
- easy rename
- No this!
Lots of advantages for the coder
Revealing Module Pattern is bad for you.
You should find another pattern to love.
Stub Module Pattern
- Hide private members inside closure
- Create a stub return object
- Attach public members to stub
- Reference through stub
http://stackoverflow.com/questions/22906662/javascript-design-pattern-difference-between-module-pattern-and-revealing-modul/22918556#22918556
Tomato, Tamato?
Is it just a difference in coding style?
Or is there a real difference?
POOR OBJECT BEHAVIOR
trumps
EASE OF CODING
WWPD?
The public part of a module pattern implementation should behave like a POJO
Addy's warning
http://addyosmani.com/resources/essentialjsdesignpatterns/book/
Most people don't pay attention to it
RMP returns "Hello, my name is Olaf" in both cases
Why does RMP fail?
This is what the warning means
RMP
Stub
Original
Wat kind of OOP is this!?
WWPD?
POJO picks up the changes
original and stub MP pick up changes
RMP doesn't
Java, Ruby, Python, ... all pick up changes
stub | orig | RMP | |
simple override | pass | pass | FAIL |
The Revealing Module Pattern breaks simple extensibility
(Sometimes it works like you expect it to,
sometimes it doesn't)
Simple Overrides
The Revealing Module Pattern works when:
- your public methods don't call each other, or
- you don't override called public methods
The speaker used to be/still is a
java programmer
Prototypal Inheritance
Once again, RMP returns "Hello, my name is Olaf"
two fails
stub | orig | RMP | |
prototype override | FAIL | pass | FAIL |
Why does stub fail?
Results so far ...
stub | orig | RMP | |
simple override | pass | pass | FAIL |
proto override | FAIL | pass | FAIL |
The Revealing and Stub Module Patterns break prototype extensibility
Prototype Extension
Revealing Module can be used for prototype when:
- public prototype methods don't call each other, or
- you don't override called public methods on the prototype
Love is irrational
Advanced Overriding
- mixins:
- $.extend()
- aspects:
- cujojs/meld
- function wrappers
- _.once
- _.debounce
- _.throttle
- ...
Mixins
Stub | Orig | RMP | |
mixin | pass | pass | FAIL |
Aspects
Stub | Orig | RMP | |
mixin | pass | pass | FAIL |
aspect | pass | pass | FAIL |
signature
stub | orig | RMP | |
simple override | pass | pass | FAIL |
proto override | FAIL | pass | FAIL |
adv override | pass | pass | FAIL |
I've covered the HOW RMP is bad.
Now the WHY?
Real beauty is on the inside
(of the function body)
RMP w/ this | |
simple override | pass |
prototype | pass |
adv override | pass |
RMP w/ stub | |
simple override | pass |
prototype | FAIL |
adv override | pass |
stub w/ this | |
simple override | pass |
prototype | pass |
adv override | pass |
Skipped
Original Module Pattern w/ stub
Original Module Pattern using closure
How you reference public members
is the only thing that matters
Everything else is superficial.
WWPD?
POJO | stub | orig | RMP | |
simple override | pass | pass | pass | FAIL |
proto override | pass | FAIL | pass | FAIL |
adv override | pass | pass | pass | FAIL |
The behavior of original module pattern resembles the POJO.
The original module pattern uses
this internally, just like POJO
What would a POJO do?
Which pattern should you date?
The original Module Pattern has warts, too.
Examples are Too SimpLE
... and teaching by example isn't always enough
private1 -> private2 | private2() |
public -> private | private() |
public1 -> public2 | this.public2() |
private - > public | ?? |
What do you do in the original module pattern when a private function wants to call a public function?
Take sayHello() and make it private
Easy for RMP
OK for stub
But ...
naive original | |
simple override | ERROR! |
prototype | ERROR! |
adv override | ERROR! |
The private function has no this
How can it get name?
private -> public
is really
public1 -> private -> public2
public1 can pass
this
to private
Ugly Solution
Please Use .call or .apply
The Missing Advice
When invoking a private function
which uses a public member
.call(this) or .apply(this)
The complete Referencing Guide
private1 - > private2 | private2() |
public -> private | private() |
public1 -> public2 | this.public2() |
public1 -> private ... - > public2 |
private.call(this) or private.apply(this), this.public2() |
Or ...
You could ALWAYS use .call(this) or .apply(this)
Revealing Module Pattern doesn't use this
vs.
You should always be passing this context
Functional/Prototype programming
- Revealing Module
- Don't use this, use the closure
- Stub Module
- Don't use this, use the return object
- Original Module
- Use this
- Original Module + missing advice
- Use this and call(this) and apply(this), avoid unnecessary closure
I lied
This talk isn't about the Revealing Module Pattern
This talk is about
function references
Generalize to ALL object creation
- constructor functions
- methods that return an object
If you want your return object to be extensible, use
this or .call or .apply
Refactor into PRototype
A constructor POJO can be refactored into a prototype
Refactor into Parent Closure
A regular function return object can be refactored into a parent closure
But when you Start hiding ...
You pay a price for secrecy
Hide with var self = this
hide sayHello in the closure
SELF = STUB!!!
Using var self = this
has the same pitfalls as the stub module pattern
stub | orig | RMP | |
simple override | pass | pass | FAIL |
proto override | FAIL | pass | FAIL |
adv override | pass | pass | FAIL |
... and it doesn't refactor into prototype
Functions which use the closure can't get pulled into the prototype
... But reference correctly ...
... allows refactoring into prototype.
In practice, you can't always avoid using the closure.
Exercise for Reader
Work out the consequences of different hiding implementations with SnowmanFactory
(I'll fill this in with updated slides ~ 2weeks)
Summary
- If you're going to hide things in the closure, do it correctly!
- Use this & call(this) & apply(this) as much as you can
- Use the closure only if you can't use this
- Object literal with _ convention keeps things simple
Contact info
ilinkuo.wordpress.com
Twitter: @ilinkuo
Feedback, please
http://speakerrate.com/talks/39201-the-revealing-module-is-an-antipattern
CREDITS
Olaf picture from Disney
The Revealing Module is an anti-pattern
By I-Lin Kuo
The Revealing Module is an anti-pattern
- 2,935