-
Notifications
You must be signed in to change notification settings - Fork 9
DesignPatterns
A design pattern is a proven solution to common software development problems
A language that team can use to discuss about design
Allow a behaviour to be selected at run time.
The different behaviours are the different strategies.
Useful when we have lot of behaviours slightly different and we don't know which one will be used.
Separate what changes of may change from what stays the same
Our
Inputobject is a wrapper ofHTMLInputElementthat handles different types pf validation.
class Input {
setElement(inputElement) {
this.element = inputElement
}
getValue() {}
setValue() {}
validate() {
var type = this.element.getAttribute('type')
if (type === 'phone') {
return /^[0-9]{11}$/g.test(this.getValue())
} else if (type === 'email') {
return this.getValue().indexOf('@') !== -1
}
}
/* ... */
}We know other types of input/validation could happen
We want to add input type number that is valid if value is a number
We have to change
validate(), which is a violation of the OCP
validate () {
var type = this.element.getAttribute('type')
if (type === 'phone') {
return (/^[0-9]{11}$/g).test(this.getValue());
} else if (type === 'email') {
return this.getValue().indexOf('@') !== -1
} else if (type === 'number') {
return typeof this.getValue() === 'number'
}
}We need to encapsulate the validation implementation
Validation implementation detail is encapsulated in different strategies
var emailValidator = {
validate(value) {
return value.indexOf('@') !== -1
}
}
var phoneValidator = {
validate(value) {
return /^[0-9]{11}$/g.test(value)
}
}Base class is created to hold the responsibility of abstract strategy implementation
class InputValidationStrategy {
selectValidator(validator) {
this._validator = validator
}
validate() {
this._validator.validate(this.getValue())
}
}Strategy is implemented.
class Input extends InputValidationStrategy {
setElement(DOMElement) {}
getValue() {}
setValue() {}
/* ... */
}
class InputEmail {
constructor() {
super()
this.selectValidator(emailValidator)
}
}
class InputPhone {
constructor() {
super()
this.selectValidator(phoneValidator)
}
}The main strategy does not know anything about concrete strategies.
Concrete strategies don't know anything about each other.
Code can be maintained with new strategies with less effort.
DuckLand shows a large variety of ducks species.
Ducks swim and make quack sounds.
For now, we have only two types of duck: mallard and redhead.
class AbstractDuck {
quack()
swim()
display() // Abstract method as all docks look different
}
class MallardDuck extends AbstractDuck {
display() // Looks like a mallard
}
class RedheadDuck extends AbstractDuck {
display() // Looks like a redhead
}Fly method is added
All ducks will inherit that behaviour
class AbstractDuck {
quack()
swim()
display() // Abstract method as all docks look different
fly()
}-
SteamDuckswims with steam jet and flies with steam jet -
PropellerDuckswims with propeller jet and flies with propeller -
HybridDuckswims with steam jet and flies with propeller
class SteamDuck extends AbstractDuck {
swim() // steam jet
fly () // steam jet
}
class PropellerDuck extends AbstractDuck {
swim () // propeller
fly () // propeller
}
class HybridDuck extends SteamDuck {
fly () // propeller
}Propeller fly behaviour is duplicated in
PropellerDuckandHybridDuck...
How do we do it without duplicating code, and preventing problem when new types of dick will be invented?
Inheritance is clearly not the response, as it does not allow our classes to change isolated behaviours, and lead to duplicated code...
New features of DuckLand make require a RobotMallardDuck that looks like a mallard but flies like a PropellerDuck ...
We must encapsulate behaviours that are different or may change
Let's implement abstract strategies in
AbstractDuck
class AbstractDuck {
setFlyBehavior(flyBehavior) { this.flyBehavior = flyBehavior }
setSwimBehavior(swimBehavior) { this.swimBehavior = swimBehavior }
fly() { this.flyBehavior.fly(); }
swim() { this.swimBehavior.swim(); }
quack()
display() // Abstract method as all docks look different
}Concrete strategies are implemented separately, in an encapsulate way...
class FlyBehavior {
fly() {
throw new Error('This method must be overwritten')
}
}
class FlyWithWings extends FlyBehavior {
fly() {
console.log('Flap!Flap!')
}
}
class FlyWithPropeller extends FlyBehavior {
fly() {
console.log('Fluflufluflu!')
}
}
class FlyWithSteamJet extends FlyBehavior {
fly() {
console.log('Psht!Psht!Psht!')
}
}Our ducks are now close for modification, open for extension
And our design is much more flexible
class SteamDuck extends AbstractDuck {
constructor() {
super()
this.setFlyBehavior(new FlyWithSteamJet())
this.setSwimBehavior(new SwimWithSteamJet())
}
}
class PropellerDuck extends AbstractDuck {
constructor() {
super()
this.setFlyBehavior(new FlyWithPropeller())
this.setSwimBehavior(new SwimWithPropeller())
}
}
class HybridDuck extends SteamDuck {
constructor() {
super()
this.setSwimBehavior(new SwimWithSteamJet())
this.setFlyBehavior(new SwimWithPropeller())
}
}- Open-Close (SOLID)
- Protected Variations (GRASP)
- Favor composition over inheritance
Modes of transportation to an airport is an example of a Strategy.
An person can use several modes of transportation: walk, car, bus, bycicle.
Any of these modes of transportation will get a person to an address, and they can be used interchangeably.
Convert transportation mode to a strategy so the bycicle strategy can be added without modifying Person.goToAddress() method.
class Person {
constructor(transportMode) {
switch (transportMode) {
case 'car':
this.transportStrategy = new PersonCarTransportation(this)
break
case 'publicTransports':
this.transportStrategy = new PersonPublicTransportation(this)
break
default:
this.transportStrategy = new PersonWalkTransportation(this)
}
}
goToAddress(address) {
this.transportStrategy.goToAddress(address)
}
}class Person {
constructor(transportMode) {
this.transportStrategy = transportMode
}
goToAddress(address) {
this.transportStrategy.goToAddress(address)
}
}
const destination = 'Plaça Catalunya, Barcelona'
new Person(new PersonCarTransportation()).goToAddress(destination)
new Person(new PersonPublicTransportation()).goToAddress(destination)
new Person(new PersonWalkTransportation()).goToAddress(destination)State is the brother of Strategy pattern
It responds to the same problem, but is used for multiple behaviours that depend on the state of the object, not the type.
Mario can be regular (small), tall and gets super powerfull when is takes a magical star.
Mario's behaviours change depending of his state...
const NORMAL = 'MARIO', TALL = 'TALL_MARIO', INVINCIBLE = 'SUPER_MARIO'
class SuperMario {
constructor () {
this.setState(NORMAL)
}
setState (state) {
this.state = state
}
collisionWith(object) {
if(object instanceOf Mushroom) {
this.collisionWithMushroom(object)
} else if (object instanceof MagicStar ) {
this.collisionWithStar(object)
} else if (object instanceof Enemy) {
this.collisionWithEnemy(object)
}
}
collisionWithStar (star) {
this.setState(INVINCIBLE)
}
collisionWithMushroom (mushroom) {
if(this.state === NORMAL) { this.setState(TALL) }
}
collisionWithEnemy(enemy) {
if(this.state === NORMAL) {
this.die()
} else if(this.state === TALL) {
this.setState(NORMAL)
} else if(this.state === INVINCIBLE) {
enemy.die()
}
}
}That's a lot of code, and a lot of "if" statements.
More over, some have high cohesion, like what happens to normal mario if he hits a mushroom or hits an enemy.
As Mario gets more states (raccoon, etc) and collisions to more types of objects, the code will grow and get converted into a nightmare.
We need to encapsulated behaviours that depend on Marios's state
State dependant behaviours are implemented separately
class MarioState {
constructor(mario) {
this.mario = mario
}
collisionWithStar()
collisionWithMushroom()
collisionWithEnemy()
}
class InvincibleMario extends MarioState {
collisionWithEnemy(enemy) { enemy.die() }
}
class NormalMario extends MarioState {
collisionWithEnemy(enemy) { this.mario.die() }
collisionWithMushroom(mushroom) { this.mario.setState(this.mario.getTallState()) }
}
class TallMario extends MarioState {
collisionWithEnemy(enemy) { this.mario.setState(this.mario.getNormalState()) }
}
SuperMarionow uses its states to behave...
class SuperMario {
constructor() {
this.setState(this.getNormalState())
}
setState(state) {
this.state = state
}
collisionWithStar(star) {
this.state.collisionWithStar(star)
}
collisionWithMushroom(mushroom) {
this.state.collisionWithMushroom(mushroom)
}
collisionWithEnemy(enemy) {
this.state.collisionWithEnemy(enemy)
}
getInvincibleState() {
return new InvincibleMario(this)
}
getNormalState() {
return new NormalMario(this)
}
getTallState() {
return new TallMario(this)
}
}- High Cohesion (GRASP)
- Favor composition over inheritance
Let's take back our transportation excercize.
Now transport mode depends on if our user has a car, a bike, etc.
- Apply State pattern to
Personclass.
class PersonState {
constructor(person) {
this.person = person
}
goToAddress(address) {
console.log(`Go by foot, managing sidewalks, stop lights, etc`)
}
}
class PersonWithCarState extends PersonState {
goToAddress(address) {
console.log(`Go by car, managing traffic, stop lights, etc`)
}
}
class PersonWithTransportCreditState extends PersonState {
goToAddress(address) {
this.person.publicTransportCredit--
console.log(`Go by public Transportation, going to bus stop, paying, etc`)
}
}It's a base pattern that favors composition over inheritance design.
Helpful to implement multiple interface inheritance.
In this case, interfaces are created on the fly.
const Flyable = Sup =>
class extends Sup {
fly() {
console.log('Flap, Flap!')
}
}
const Swimable = Sup =>
class extends Sup {
quack() {
console.log('Quack!')
}
}
class Duck extends Swimable(Flyable(null)) {
swim() {
console.log('Chop!')
}
}- Interface Segregation Principle (SOLID)
- Favor composition over inheritance
Communicate modules indirectly without coupling.
Instead of consuming other modules's method, we communicate through event channels.
Caution: easy to overuse and make relationship between modules confusing.
A global session object that publishes changes by events
import pubSub from './pubsub.js'
export class Session {
static SESSION_CHANGE = 'session:change'
static setUser(user) {
this.user = user
pubSub.publish(Session.SESSION_CHANGE, this.user)
}
static getUser() {
return this.user
}
}The header, which always displays currents user's name, listens to the event:
import pubSub from './pubsub.js'
import Session from './session.js'
class AppHeader {
constructor(domElement) {
this.title = domElement.getElementsByClassName('title')[0]
this._sessionHandler = pubSub.subscribe(
Session.SESSION_CHANGE,
this.setUser.bind(this)
)
}
setUser(user) {
this.title.innerHTML = 'User: ' + user.name
}
destroy() {
this._sessionHandler.dispose()
}
}Note: Always remember to unregister handlers when you don't need them anymore.
Event bus implmentation
// pubsub.js
var subscribers = {}
export default {
publish(topic, data) {
if (!subscribers[topic]) {
return
}
subscribers[topic].forEach(function(subscriber) {
subscriber(data)
})
},
subscribe(topic, callback) {
var index
if (!subscribers[topic]) {
subscribers[topic] = []
}
index = subscribers[topic].push(callback) - 1
return {
dispose: function() {
subscribers[topic].splice(index, 1)
}
}
}
}Communication takes place over a named channel.
Based on callbacks which are triggered when events occur.
Can pass data to callbacks.
Subscription can return a destroy method to unsubscribe events.
- Low coupling
- Indirection (GRASP)
Attaches additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending functionality.
Behaviors are extended in an isolated way, on runtime.
OCP tells us to open for extension. Decorator is a pattern to open for decoration.
CoffeeShop offers different types of coffee: espresso, decaf, dark roast, etc.
Each coffee has a different cost
class Beverage {
constructor () {
this.description = "Unknown Beverage";
}
getDescription() { return this.description }
cost() // abstract method
}
class Espresso extends Beverage {
constructor () { this.description = "Espresso Coffee"; }
cost() { return 1.99 }
}
class Decaf extends Beverage {
constructor () { this.description = "Decaf Coffee"; }
cost() { return 2.09 }
}
class DarkRoast extends Beverage {
constructor () { this.description = "DarkRoast Coffee"; }
cost() { return 2.39 }
}In addition to your coffee, you can also ask for several condiments: milk, soy, and mocha (chocolate)
A coffee can have several condiments, that have their own cost.
With Inheritance? It would be a nightmare of classes: DecafWithMilk, DecafWithMilkAndChocolate, DecafWithSoyAndChocolate, etc...
With Strategy? Strategy is useful for conditional behaviour. In this case, we are talking about nested behaviours.
Let's try the Decorator Pattern
class CondimentDecorator extends Beverage {
constructor (beverage) { this.beverage = beverage; }
getDescription() // abstract method
cost() // abstract method
}
class Milk extends CondimentDecorator {
getDescription() { return this.beverage.getDescription() + ", Milk" }
cost() { return 0.20 + this.beverage.cost() }
}
class Soy extends CondimentDecorator {
getDescription() { return this.beverage.getDescription() + ", Soy" }
cost() { return 0.24 + this.beverage.cost() }
}
class Mocha extends CondimentDecorator {
getDescription() { return this.beverage.getDescription() + ", Mocha" }
cost() { return 0.59 + this.beverage.cost() }
}Decorators have the same supertype as the objects they decorate.
You can use one or more decorators to wrap an object.
The decorator adds its own behaviour either before and/or after delegating to the object it decorates to do the rest of the job.
import Espresso from './espresso'
import Mocha from './mocha'
import Milk from './milk'
var beverage = new Espresso()
beverage = new Mocha(beverage)
beverage = new Milk(beverage)
console.log(beverage.getDescription() + “ $” + beverage.cost())
// Espresso Coffee, Mocha, Milk $2.78- Open/Close Principle (SOLID)
- Favor composition over inheritance
- Low coupling
Closely related to the Publisher/Subscriber pattern.
Observers subscribe directly to the object being observed (the subject).
Useful for decoupling.
It's the responsibility of the subject to maintain the list of observers.
Subject's interface should include observing, unobserving and notifying methods.
WeatherDatadata holds state of weather
Each time data is update, we want it to be logged to the console
Data object with observing, unobserving and notifying methods.
class WeatherData {
constructor() {
this.observers = []
}
setMeasurements(temperature, humidity) {
this.temperature = temperature
this.humidity = humidity
this.notifyObservers({ temperature, humidity })
}
registerObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
this.observers = this.observers.filter(
observer => observer !== observerToRemove
)
}
notifyObservers(data) {
this.observers.forEach(function(observer) {
observer(data)
})
}
}class WeatherConsole {
constructor(weatherData) {
this.weatherData = weatherData
this.weatherData.registerObserver(this.log.bind(this))
}
log(data) {
console.log(
'Current conditions: ' +
data.temperature +
'F degrees and ' +
data.humidity +
'% humidity.'
)
}
}We know that many other types of data could apply the observer pattern
Then, it's convenient to encapsulated the observer behaviour as a separated responsibility.
class Observable {
constructor() {
this.observers = []
}
registerObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
this.observers = this.observers.filter(function(observer) {
return observer !== observerToRemove
})
}
notifyObservers(data) {
this.observers.forEach(function(observer) {
observer(data)
})
}
}
class WeatherData extends Observable {
setMeasurements(temperature, humidity) {
this.temperature = temperature
this.humidity = humidity
this.notifyObservers({ temperature, humidity })
}
}class WheatherConsole {
constructor(weatherData) {
this.weatherData = weatherData
this.observer = this.log.bind(this)
this.weatherData.registerObserver(this._observer)
}
log(data) {
console.log(
'Current conditions: ' +
data.temperature +
'F degrees and ' +
data.humidity +
'% humidity.'
)
}
destroy() {
this.weatherData.removeObserver(this._observer)
}
}We can also extract the "observer" behaviour, as we did with "observable"
class Observer {
constructor (observable) { // Expected any instance of Observer
this.observable = observable
this.observer = this.update.bind(this)
this.observable.registerObserver(this.observer)
}
update() // abstract method
destroy () {
this.observable.removeObserver(this.observer)
}
}class WheatherConsole extends Observer {
update (data) {
console.log('Current conditions: ' + data.temperature + 'F degrees and ' + data.humidity + '% humidity.');
}
}
class WheaterEmailer extends Observer {
update (data) {
this.sendEmail(
subject: 'Weather changed !',
body: 'Current conditions: ' + data.temperature + 'F degrees and ' + data.humidity + '% humidity.'
);
}
}import EventEmitter from 'events'
class WeatherData extends EventEmitter {
setMeasurements (temperature, humidity) {
this.temperature = temperature;
this.humidity = humidity;
this.emit('update', {temperature, humidity})
}
}
class Observer {
constructor (observable) { // Expected any instance of Observer
this.observable = observable
this.observer = this.update.bind(this)
this.observable.on('update', this.observer)
}
update() // abstract method
destroy () {
this.observable.removeObserver(this.observer)
}
}https://github.com/tcorral/Design-Patterns-in-Javascript/tree/es6/Observer
- Indirection (GRASP)
- Low coupling
- Favor composition over inheritance
Creational Pattern.
Wrap a constructor to return instances of classes (objects).
Factories simplify creation of complex objects or creation at once of large number of similar objects.
The knowledge os how objects are created is delegated to the factory, as a separated responsibility.
import Espresso from './espresso'
import Decaf from './decaf'
import DarkRoast from './dark-roast'
class CoffeeShop {
orderCoffee(type, condiments) {
var beverage
if (type == 'espresso') {
beverage = new Espresso()
} else if ((type = 'decaf')) {
beverage = new Decaf()
} else if ((type = 'darkroast')) {
beverage = new DarkRoast()
}
if (condiments.indexOf('mocha') !== -1) {
beverage = new Mocha(beverage)
}
if (condiments.indexOf('milk') !== -1) {
beverage = new Milk(beverage)
}
beverage.prepare()
return beverage
}
}CoffeeShop has got dependencies: Espresso, Decaf, DarkRoast, ...
CoffeeShop instantiates in different ways Espresso and Mocha.
Instantiation is conditional.
It's definitely an implementation, a responsibility.
class BeverageFactory {
static createCondimentedCoffee(beverage, condiments) {
if (condiments.indexOf('mocha') !== -1) {
beverage = new Mocha(beverage)
}
if (condiments.indexOf('milk') !== -1) {
beverage = new Milk(beverage)
}
}
static createCoffee(type, condiments) {
var beverage
if (type == 'espresso') {
beverage = new Espresso()
} else if ((type = 'decaf')) {
beverage = new Decaf()
} else if ((type = 'darkroast')) {
beverage = new DarkRoast()
}
var beverage = this.createCondimentedCoffee(beverage, condiments)
beverage.prepare()
return beverage
}
}Now CoffeeShop doesn't have to know anything about how to create and condiment.
If the way to prepare a coffee changes, CoffeeShop won't have to change.
class CoffeeShop {
prepareCoffee(type, condiments) {
var beverage = BeverageFactory.createCondimentedCoffee(type, condiments)
return beverage
}
}Our
BeverageFactorystill depends on many classes.
What if we need a "HealthyCoffeeShop" that uses different condiments?
var const COFFEE = {
espresso: 'createEspresso',
decaf: 'createDecaf',
darkroast: 'createDarkRoast'
}
class CoffeeFactory {
static createCoffee (type) {
return this[COFFEE[type]]()
}
static createExpresso() { return new Espresso() }
static createDecaf() { return new Decaf() }
static createDarkRoast() { return new DarkRoast() }
}var const CONDIMENT = {
milk: 'createMilk',
mocha: 'createMocha'
}
class CondimentFactory {
static createCondiment (beverage, condiments) {
return condiments.reduce(function (condiment, beverage) {
return this[CONDIMENT[condiment]](beverage)
}, beverage)
}
static createMilk(beverage) { return new Milk(beverage) }
static createMocha(beverage) { return new Mocha(beverage) }
}class BeverageFactory {
static coffeeFactory = CoffeeFactory
static condimentFactory = CondimentFactory
createCoffee(type, condiments) {
var beverage = this.coffeeFactory.createCoffee(type)
beverage = this.condimentFactory.createCondiment(beverage, condiments)
beverage.prepare()
return beverage
}
}class CoffeeShop {
constructor() {
this.factory = BeverageFactory
}
prepareCoffee(type, condiments) {
var beverage = this.factory.createCoffee(type, condiments)
return beverage
}
}class HealthyCondimentFactory extends BeverageFactory {
static createMilk(beverage) {
return new LowFatMilk(beverage)
}
}
class HealthyBeverageFactory extends BeverageFactory {
static condimentFactory = HealthyCondimentFactory
}
class HealthyCoffeeShop extends CoffeeShop {
constructor() {
super()
this.factory = HealthyBeverageFactory
}
}- Creator (GRASP)
- Pure Fabrication (GRASP)
- Open-Close Principle (SOLID)
Sometimes, we only need a unique instance of a class.
The Singleton Pattern ensures a class has only one instance, and provides a global point access to it.
Point access creates its own instance and returns always the first one created.
import Cache from './cache'
var cache = Cache.getInstance()
cache.set('myVar', 1)// cache.js
var uniqueInstance = null
class Cache {
set (key, data)
get (key)
has (key)
clear()
}
export default {
getInstance () {
if (!uniqueInstance) {
uniqueInstance = new Cache()
}
return uniqueInstance
}
}Note: Cache constructor remains private.
Instance getter is explicit.
Consumer is forced to always explicitly call the
getInstancefunction.
// cache.js
class Cache {
set (key, data)
get (key)
has (key)
clear()
}
export default new Cache()Usage:
import cache from './cache'
cache.set('myVar', 1)Simple import.
Not lazy. Instance will be created on import, not only if instance is required...
If use of
Cacheis conditional, it will be instantiated anyways...
Encapsulated method invocation: encapsulates a request as an object.
Allows to decouple the requester of an action from the object that performs the action.
Lets you parameterize other objects with different requests, queue, ...
Commands contains everything require for an operation and exposes an
executemethod.
class ICommand {
execute() {
throw new Error('ICommand.execute not implemented')
}
}
class TurnOnCommand extends ICommand {
// Command receiver
constructor(device) {
this.device = device
}
execute() {
device.turnOn()
}
}class RemoteControlButton {
// Command invoker
setCommand(command) {
this.command = command
}
onPress() {
this.command.execute()
}
}
class RemoteControl {
constructor() {
this.buttons = [
new RemoteControlButton(),
new RemoteControlButton(),
new RemoteControlButton(),
new RemoteControlButton()
]
}
setCommand(slot, command) {
this.buttons[slot].setCommand(command)
}
}class AlarmCommand {
constructor(device) {
this.alarm = alarm
}
}
class EnableAlarm extends AlarmCommand {
execute() {
this.alarm.enable()
}
}
class DisableAlarm extends AlarmCommand {
execute() {
this.alarm.disable()
}
}var alarms = [/* array of alarms */]
alarms.forEach(function(alarm){
alarmView.addButtons ([
new Button('enable', new EnableAlarm(alarm))
new Button('disable', new DisableAlarm(alarm))
new Button('reset', new ResetAlarm(alarm))
])
})Convert one interface into an another
Used to avoid extensive refactoring when a dependency changes
Consider a logger in charge of runtime messages
class LoggerFactory {
static getLogger() {
return window.console
}
}
var logger = LoggerFactory.getLogger()
logger.log('something happened')Another logger, used in another app logs remotely through a web service
class AjaxLogger {
static sendLog: function(...args) {
var data = this.urlEncode(args);
jQuery.ajax({
url: "http://example.com/log",
data: data
})
}
}We can't make the AjaxLogger to be the main logger of our application
But our app already uses console interface, and we want to avoid refactoring all
logger.logstatements
We can solve the problem implementing an
adapter
class AjaxLoggerAdapter {
static log(...args) {
AjaxLogger.sendLog(...args)
}
}
class LoggerFactory {
static getLogger() {
return AjaxLoggerAdapter
}
}Simple and easy-to-use interface exposed to hide complexity of the underlying system.
Conceals an implementation that may change.
Protects from effects of change of a subsystem.
$(element).click(onClick)function click(element, func) {
if (window.addEventListener) {
element.addEventListener('click', func, false)
} else if (window.attachEvent) {
element.attachEvent('onclick', func)
} else {
element['onclick'] = func
}
}$(element).css({ top: '30px', left: '100px' })function css(element, properties) {
Object.keys(properties).forEach(function(key) {
element.style[key] = properties[key]
})
}Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
Template Method let subclasses redefine certain steps of an algorithm without changing the algorithm structure.
Template Method will be called internally by the main algorithm, no by the client.
Can be used as hooks.
class Parser {
parse(data) {
var labels = this._parseLabels(data)
var names = this._parseNames(data)
var metas = this._parseMetas(data)
return { labels, names, metas }
}
_parseLabels(data) {
/* parsing algorithm */
} // Template method
_parseNames(data) {
/* parsing algorithm */
} // Template method
_parseMetas(data) {
/* parsing algorithm */
} // Template method
}
class CustomParser extends Parser {
_parseLabels(data) {
/* different parsing algorithm */
}
}class BaseController {
init () {
this._preInit()
// Main operations
this._postInit()
}
_preInit() // Template method
_postInit() // Template method
}
class MyController extends BaseController {
_preInit() { /* Operations to execute before main init */ }
}
==operand implicitly calls object.valueOf when objects are compared.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
valueOf() {
return this.firstName + ' ' + this.lastName
}
}
console.log(new Person('Evan', 'Graham') == 'Evan Graham') // true- Open/Close Principle (SOLID)
- Protected variations (GRASP)
Lets clients treat individual objects and compositions uniformly.
Treat single objects or collections of objects in the same way.
Used to create nested structures of nodes.
A node may be a leaf or a container.
Traversal of a tree is usually implicit.
In jQuery, single elements or collections of elements are treated in the same way.
// Collection of elements
$('.container').css({ opacity: 0.5 })
$('.container')
.find('div')
.css({ opacity: 0.5 })
$('.container')
.children()
.css({ opacity: 0.5 })
// Single element
$(element).css({ opacity: 0.5 })class Directory {
constructor(files) {
this.files = files
this.children = []
}
add(directory) {
this.children.push(directory)
child.parent = this
}
getFiles() {
const childrenFiles = this.children.reduce((files, directory) => {
return files.push(directory.getFiles())
}, [])
return [...this.files, ...childrenFiles]
}
}Use one object as the interface for another object.
Control access from clients to the object being proxied.
Clients may or may not know if they are using the proxy.
May throttle requests to the object of include cache for performance.
class Users {
getOne(id) {
return executeSql('SELECT * FROM Users WHERE id = ' + id)
}
getCount() {
return executeSql('SELECT COUNT(*) FROM Users')
}
}var cache = {}
var count = null
class UsersProxy {
constructor(users) {
this.users = users
}
getOne(id) {
if (cache[id]) {
return cache[id]
} else {
return (cache[id] = this.users.getOne(id))
}
}
getCount() {
if (count !== null) {
return count
} else {
return (count = this.users.getCount())
}
}
}class UsersPaginator {
constructor() {
this.page = 0
this.usersPerPage = 10
this.loadList()
}
nextPage() {
this.page++
this.loadList()
}
prevPage() {
this.page--
this.loadList()
}
loadList() {
let offset = this.usersPerPage * this.page
return executeSql(
'SELECT * FROM Users LIMIT ' + offset + ',' + offset + this.usersPerPage
)
}
}Note: What if user clicks "nextPage" button very fastly. We really need to load the list when he stops clicking...
class UsersPaginatorProxy extends UsersPaginator {
loadList() {
_.debounce(this.loadList.bind(this), 300)
}
}_.debounce Debouncing - CSS tricks
var proxied = jQuery.ajax // Preserving original function
jQuery.ajax = function() {
jQuery('#loading').dialog({ modal: true })
return proxied.apply(this, arguments)
}Decorator Pattern focuses on dynamically adding functions to an object, while Proxy Pattern focuses on controlling access to an object.
A compound pattern combines two or more patterns into a solution that solves a recurring or general problem.
Model-View-Controller splits the responsibilites of most common programs
Model on a side, View on another, Controller in between.
Model holds all the data, state, and application logic.
Model can send notifications of state changes to observers.
View are controls to display and edit data.
View is a representation of the model.
View never knows about the model and never calls it directly.
View gets the state and data it needs to display from the model.
Controller knows about Model and View and medidate between them.
Controller synchronizes Data with View.
Controller takes user input and figures how to translate it into request on the Model.
Controller injects data and state needed to the view directly from the model.
Controller separates the logic of control from the view so the view can be reused with other models.
It's the behaviour of the view, and can be swapped with another controller, maintaining the same view.
Controller is a concrete strategy of the view
It accesses the document component that contains other DOM components that contains other DOM components.
It communicates state changes to the View of Controller through events.
Let's do mp3 player.
It plays songs from a playlist (model)
It has 4 buttons (the view): stop, next song, previous song, shuffle.
Let's retrieve
Observablewe created for our WeatherData example.
class Observable {
constructor() {
this.observers = []
}
registerObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
this.observers = this.observers.filter(function(observer) {
return observer !== observerToRemove
})
}
notifyObservers(data) {
this.observers.forEach(function(observer) {
observer(data)
})
}
}class PlayList extends Observable {
constructor(songs) {
this.songs = songs
}
get length() {
this.songs.length
}
get current() {
this.current
}
play(songIndex) {
this.current = songIndex
// execute song play
}
}class PlayerCtrl {
constructor(playlist) {
this.playlist = playlist
this.view = new PlayerView(this, playlist)
}
next() {
this.changeSong(this.playlist.current + 1)
}
previous() {
this.changeSong(this.playlist.current - 1)
}
stop() {
this.changeSong(null)
}
shuffle() {
var index = Math.round(Math.random() * this.playlist.length)
this.changeSong(songIndex)
}
changeSong(index) {
const { playlist, view } = this
playlist.play(index)
view.toggleNext(playlist.current < playlist.length)
view.togglePrevious(playlist.current > 0)
view.toggleStop(playlist.current !== null)
view.togglePlay(playlist.current === null)
}
}class PlayerView {
constructor(controller) {
this.controller = controller
this.setEvents()
}
setEvents() {
$('.next').click(this.controller.next.bind(this.controller))
$('.prev').click(this.controller.prev.bind(this.controller))
$('.stop').click(this.controller.stop.bind(this.controller))
$('.play').click(this.controller.play.bind(this.controller))
$('.shuffle').click(this.controller.shuffle.bind(this.controller))
}
onItemChange(item) {
this.setTitle(item.title + ' - ' + item.author)
}
setTitle(title) {
/* change title in the view */
}
toggleNext(enable) {
/* enable/disable next button */
}
togglePrevious(enable) {
/* enable/disable previous button */
}
toggleStop(enable) {
/* enable/disable stop button */
}
togglePlay(enable) {
/* enable/disable play button */
}
}Involve object instantiation and all provide a way to decouple a client from objects it needs to instantiate.
- Singleton
- Factory
- Builder
Let you compose classes or objects into larger structures
- Proxy
- Decorator
- Adapter
- Façade
- Composite
- Bridge
Concerned with how classes and objects interact and distribute responsibility
- Template Method
- Iterator
- Command
- Observer
- State
- Strategy
- Mediator
