Skip to content

Instantly share code, notes, and snippets.

@mcmire
Last active August 29, 2015 13:57
Show Gist options
  • Save mcmire/9750213 to your computer and use it in GitHub Desktop.
Save mcmire/9750213 to your computer and use it in GitHub Desktop.
Design patterns: JavaScript vs. Ruby
# 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: -> # ...
// 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 () {
// ...
}
# 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
# 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, ' ')
// 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);
# 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
# 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: -> # ...
// 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 () {
// ...
}
# 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
# 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()
// 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();
}
# 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