Last active
August 29, 2015 13:57
-
-
Save mcmire/9750213 to your computer and use it in GitHub Desktop.
Design patterns: JavaScript vs. Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# If you wanted to make a constructor that did some extra stuff | |
# on the newly created object before giving it back to you | |
# you might be tempted to write it like you would in Ruby: | |
class Modal | |
@load: -> | |
modal = new this | |
modal.load | |
modal | |
load: -> # ... | |
# But this is hard to test because Modal is actually a function | |
# here and to stub Modal.load you have to blow away Modal. | |
# Instead it's better to "go Java" and use a separate function: | |
LoadedModal = -> | |
modal = new Modal | |
modal.load | |
modal | |
class Modal | |
load: -> # ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In plain JavaScript you are not so tempted to write this since | |
// it doesn't look like Ruby, but, don't do this: | |
function Modal() { | |
// ... | |
} | |
Modal.prototype.load = function () { | |
// ... | |
} | |
Modal.load = function () { | |
var modal = new this; | |
modal.load(); | |
return modal; | |
} | |
// Instead, "go Java" and use a separate function: | |
function LoadedModal() { | |
var modal = new Modal(); | |
modal.load(); | |
return modal; | |
} | |
function Modal() { | |
// ... | |
} | |
Modal.prototype.load = function () { | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# If you wanted to make a constructor that did some extra stuff | |
# on the newly created object before giving it back to you | |
# this is how you say it in Ruby: | |
class Modal | |
def self.load | |
new.tap do |modal| | |
modal.load | |
end | |
end | |
def load | |
# ... | |
end | |
end | |
# This is how the equivalent JavaScript above would look like in Ruby, | |
# just for comparison. It isn't idiomatic, though. | |
def LoadedModal | |
Modal.new.tap do |modal| | |
modal.load | |
end | |
end | |
class Modal | |
def load | |
# ... | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The decorator pattern can be achieved in JavaScript using inheritance: | |
class Person | |
constructor: (@attributes = {}) -> | |
@firstName = attributes.firstName | |
@lastName = attributes.lastName | |
class PersonDecorator extends Project | |
getFullName: -> | |
[@firstName, @lastName].join(' ').replace(/[ ]+/g, ' ') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The decorator pattern can be achieved in JavaScript using inheritance. | |
// Note this gets a little hairy in plain JavaScript. | |
// Stolen from TypeScript | |
function inherits(childCons, parentCons) { | |
function F() { | |
this.constructor = childCons; | |
} | |
F.prototype = parentCons.prototype; | |
childCons.prototype = new F; | |
} | |
function Person(attributes) { | |
attributes = attributes || {}; | |
this.firstName = attributes.firstName; | |
this.lastName = attributes.lastName; | |
} | |
function PersonDecorator(/* attributes */) { | |
Person.apply(this, arguments); | |
} | |
PersonDecorator.getFullName = function () { | |
return [this.firstName, this.lastName].join(' ').replace(/[ ]+/g, ' '); | |
} | |
inherits(PersonDecorator, Person); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Here's how you're used to seeing the decorator pattern in Ruby: | |
class Person | |
def initialize(attributes = {}) | |
@first_name = attributes[:first_name] | |
@last_name = attributes[:last_name] | |
end | |
end | |
require 'delegate' | |
class PersonDecorator < SimpleDelegator | |
def full_name | |
[@first_name, @last_name].join(' ').squeeze(' ') | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# If you want to wrap a block of code that chooses a constructor | |
# and uses it depending on some argument, you might be tempted | |
# to write this since it looks Ruby-like: | |
class Parser | |
@create: -> | |
parser = new this | |
switch type | |
when 'csv' then new CsvParser(parser) | |
when 'json' then new JsonParser(parser) | |
constructor: -> # ... | |
anotherMethod: -> # ... | |
# But this is hard to test since Parser is itself a function | |
# and therefore must be blown away if you want to stub Parser.create. | |
# Instead, we've got to go Java and use another object: | |
Parser = (type) -> | |
parser = new BaseParser | |
case type | |
when 'csv' then new CsvParser(parser) | |
when 'json' then new JsonParser(parser) | |
class BaseParser | |
constructor: -> # ... | |
anotherMethod: -> # ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In plain JavaScript you are not so tempted to write this as in CoffeeScript | |
// since it doesn't look like Ruby, but, don't do this: | |
function Parser() { | |
// ... | |
} | |
Parser.prototype.anotherMethod = function () { | |
// ... | |
} | |
Parser.create = function (type) { | |
var parser = new this; | |
switch (type) { | |
case 'csv': new CsvParser(parser); break; | |
case 'json': new JsonParser(parser); break; | |
} | |
} | |
// This is more idiomatic: | |
function Parser(type) { | |
var parser = new BaseParser; | |
switch (type) { | |
case 'csv': new CsvParser(parser); break; | |
case 'json': new JsonParser(parser); break; | |
} | |
} | |
function BaseParser() { | |
// ... | |
} | |
BaseParser.prototype.anotherMethod = function () { | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Here's how you're used to seeing the factory pattern in Ruby: | |
class Parser | |
def self.create | |
parser = new | |
case type | |
when 'csv' then CsvParser.new(parser) | |
when 'json' then JsonParser.new(parser) | |
end | |
end | |
def initialize | |
# ... | |
end | |
def another_method | |
# ... | |
end | |
end | |
# For comparison here's the JavaScript above translated into Ruby: | |
def Parser(type) | |
parser = BaseParser.new | |
case type | |
when 'csv' then CsvParser.new(parser) | |
when 'json' then JsonParser.new(parser) | |
end | |
end | |
class BaseParser | |
def initialize | |
# ... | |
end | |
def another_method | |
# ... | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Objects are singletons in JavaScript so you can just make an object, | |
# although you might find it helpful to define 'init' methods: | |
keyboard = | |
init: -> | |
@addEvents() | |
this | |
addEvents: -> | |
$(window).on 'keyup', -> # ... | |
player = | |
init: -> | |
@moveTo(23, 89) | |
this | |
moveTo: (x, y) -> | |
# ... | |
game = | |
init: -> | |
keyboard.init() | |
player.init() | |
# Modules are inherently untestable though so if you like, you can | |
# use classes, make instances of them and then memoize those instances: | |
class Keyboard | |
constructor: -> | |
@addEvents() | |
addEvents: -> | |
$(window).on 'keyup', -> # ... | |
class Player | |
constructor: -> | |
@moveTo(23, 89) | |
moveTo: (x, y) -> | |
# ... | |
class Game | |
getKeyboard: -> | |
@_keyboard ?= new Keyboard() | |
getPlayer: -> | |
@_player ?= new Player() | |
# Or, just only ever make one instance of that class: | |
class Keyboard | |
constructor: -> | |
@addEvents() | |
addEvents: -> | |
$(window).on 'keyup', -> # ... | |
class Player | |
constructor: -> | |
@moveTo(23, 89) | |
moveTo: (x, y) -> | |
# ... | |
class Game | |
constructor: -> | |
@keyboard = new Keyboard() | |
@player = new Player() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Objects are singletons in JavaScript so you can just make an object, | |
// although you might find it helpful to define 'init' methods: | |
window.keyboard = { | |
init: function () { | |
this.addEvents(); | |
return this; | |
}, | |
addEvents: function () { | |
$(window).on 'keyup', function () { | |
// ... | |
} | |
} | |
} | |
window.player = { | |
init: function () { | |
this.moveTo(23, 89); | |
return this; | |
}, | |
moveTo: function (x, y) { | |
// ... | |
} | |
} | |
window.game = { | |
init: function () { | |
keyboard.init(); | |
player.init(); | |
} | |
} | |
// Modules are inherently untestable though so if you like, you can | |
// use classes, make instances of them and then memoize those instances. | |
// Note that this is more clumsy in plain JavaScript than CoffeeScript: | |
function Keyboard() { | |
this.addEvents(); | |
} | |
Keyboard.prototype.addEvents = function () { | |
$(window).on 'keyup', function () { | |
// ... | |
} | |
} | |
function Player() { | |
this.moveTo(23, 89); | |
} | |
Player.prototype.moveTo = function (x, y) { | |
// ... | |
} | |
function Game() { } | |
Game.prototype.getKeyboard = function () { | |
if (this._keyboard == null) { | |
this._keyboard = new Keyboard; | |
} | |
return this._keyboard; | |
} | |
Game.prototype.getPlayer = function () { | |
if (this._player == null) { | |
this._player = new Player; | |
} | |
return this._player; | |
} | |
// Or, just only ever make one instance of that class: | |
function Keyboard() { | |
this.addEvents(); | |
} | |
Keyboard.prototype.addEvents = function () { | |
$(window).on 'keyup', function () { | |
// ... | |
} | |
} | |
function Player() { | |
this.moveTo(23, 89); | |
} | |
Player.prototype.moveTo = function (x, y) { | |
// ... | |
} | |
function Game() { | |
this.keyboard = new Keyboard(); | |
this.player = new Player(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# In Ruby we have the Singleton module, although you really don't | |
# have to use it. You *could* use modules: | |
module Keyboard | |
class << self | |
def init | |
add_events | |
end | |
def add_events | |
# pretending we can do this in Ruby | |
$(window).on :keyup do | |
# ... | |
end | |
end | |
end | |
end | |
module Player | |
class << self | |
def init | |
move_to(23, 89) | |
end | |
def move_to(x, y) | |
# ... | |
end | |
end | |
end | |
module Game | |
class << self | |
def init | |
Keyboard.init | |
Player.init | |
end | |
end | |
end | |
# Modules are inherently untestable so instead it's more idiomatic | |
# to create instances and then memoize them: | |
class Keyboard | |
def initialize | |
add_events | |
end | |
def add_events | |
# pretending we can do this in Ruby | |
$(window).on :keyup do | |
# ... | |
end | |
end | |
end | |
class Player | |
def initialize | |
move_to(23, 89) | |
end | |
def move_to(x, y) | |
# ... | |
end | |
end | |
class Game | |
def keyboard | |
@_keyboard ||= Keyboard.new | |
end | |
def player | |
@_player ||= Player.new | |
end | |
end | |
# Although if you like you can just only ever create one instance of the class: | |
class Keyboard | |
def initialize | |
add_events | |
end | |
def add_events | |
# pretending we can do this in Ruby | |
$(window).on :keyup do | |
# ... | |
end | |
end | |
end | |
class Player | |
def initialize | |
move_to(23, 89) | |
end | |
def move_to(x, y) | |
# ... | |
end | |
end | |
class Game | |
attr_reader :keyboard, :player | |
def initialize | |
@keyboard = Keyboard.new | |
@player = Player.new | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment