Skip to content

Instantly share code, notes, and snippets.

@zaneli
Created July 14, 2013 12:46
Show Gist options
  • Save zaneli/5994163 to your computer and use it in GitHub Desktop.
Save zaneli/5994163 to your computer and use it in GitHub Desktop.
「CoffeeScript によるデザインパターン(Observer)」ブログ用
window.console =
log: (msg) ->
idName = "__console_log"
$("body").append "<textarea id=\"#{idName}\">" if $("##{idName}").size() is 0
$("##{idName}").val($("##{idName}").val() + "#{msg}\n")
warn: (msg) ->
idName = "__console_warn"
$("body").append "<textarea id=\"#{idName}\">" if $("##{idName}").size() is 0
$("##{idName}").val($("##{idName}").val() + "#{msg}\n")
# テストケースで配列のindexOfが使用できなかったための回避コード
# 参考: http://qiita.com/Oakbow/items/3374175d76d82792134d
unless "indexOf" of Array::
Array::indexOf = (find, i) ->
i = 0 if i is `undefined`
i += @length if i < 0
i = 0 if i < 0
n = @length
while i < n
return i if i of this and this[i] is find
i++
-1
mixOf = (base, mixins...) ->
class Mixed extends base
for mixin in mixins by -1
for name, method of mixin::
Mixed::[name] = method
Mixed
class Subject
constructor: -> @observers = []
notifyObservers: -> observer?.call?(@) for observer in @observers
addObserver: (observers...) ->
for observer in observers
if typeof(observer) is "function"
@observers.push observer
else
console.warn observer?.constructor?.name + " is not a function"
deleteObserver: (observers...) ->
for observer in observers
index = @observers.indexOf(observer)
@observers.splice(index, 1) if index >= 0
class @Employee extends (mixOf Subject)
constructor: (name, @title, salary) ->
super()
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
@notifyObservers()
@getSalary = -> salary
@payroll = ->
console.log "#{@getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{@getSalary()}です!"
@taxMan = ->
console.log "#{@getName()}に新しい税金の請求書を送ります!"
class @Employee
constructor: (name, @title, salary, payroll) ->
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
payroll.update(@)
@getSalary = -> salary
class @Payroll
update: (changedEmployee) ->
console.log "#{changedEmployee.getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{changedEmployee.getSalary()}です!"
class @Employee
constructor: (name, @title, salary) ->
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
@notifyObservers()
@getSalary = -> salary
@observers = []
notifyObservers: -> observer?.update?(@) for observer in @observers
addObserver: (observers...) ->
for observer in observers
if observer?.update?
@observers.push observer
else
console.warn observer?.constructor?.name + " has no method 'update'"
deleteObserver: (observers...) ->
for observer in observers
index = @observers.indexOf(observer)
@observers.splice(index, 1) if index >= 0
class @Payroll
update: (changedEmployee) ->
console.log "#{changedEmployee.getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{changedEmployee.getSalary()}です!"
class @TaxMan
update: (changedEmployee) ->
console.log "#{changedEmployee.getName()}に新しい税金の請求書を送ります!"
class @Employee
constructor: (name, @title, salary) ->
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
@notifyObservers()
@getSalary = -> salary
@observers = []
notifyObservers: -> observer?.call?(@) for observer in @observers
addObserver: (observers...) ->
for observer in observers
if typeof(observer) is "function"
@observers.push observer
else
console.warn observer?.constructor?.name + " is not a function"
deleteObserver: (observers...) ->
for observer in observers
index = @observers.indexOf(observer)
@observers.splice(index, 1) if index >= 0
@payroll = ->
console.log "#{@getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{@getSalary()}です!"
@taxMan = ->
console.log "#{@getName()}に新しい税金の請求書を送ります!"
class Subject
constructor: -> @observers = []
notifyObservers: -> observer?.call?(@) for observer in @observers
addObserver: (observers...) ->
for observer in observers
if typeof(observer) is "function"
@observers.push observer
else
console.warn observer?.constructor?.name + " is not a function"
deleteObserver: (observers...) ->
for observer in observers
index = @observers.indexOf(observer)
@observers.splice(index, 1) if index >= 0
class @Employee extends Subject
constructor: (name, @title, salary) ->
super()
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
@notifyObservers()
@getSalary = -> salary
@payroll = ->
console.log "#{@getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{@getSalary()}です!"
@taxMan = ->
console.log "#{@getName()}に新しい税金の請求書を送ります!"
extend = (obj, mixin) ->
obj[name] = method for name, method of mixin
obj
include = (mixins...) ->
extend @.constructor.prototype, mixin for mixin in mixins
notifyObserversFunc = notifyObservers: ->
if not @observers? then return
observer?.call?(@) for observer in @observers
addObserverFunc = addObserver: (observers...) ->
@observers ?= []
for observer in observers
if typeof(observer) is "function"
@observers.push observer
else
console.warn observer?.constructor?.name + " is not a function"
deleteObserverFunc = deleteObserver: (observers...) ->
if not @observers? then return
for observer in observers
index = @observers.indexOf(observer)
@observers.splice(index, 1) if index >= 0
class @Employee
constructor: (name, @title, salary) ->
@getName = -> name
@setSalary = (newSalary) ->
salary = newSalary
@notifyObservers()
@getSalary = -> salary
include.call(@, notifyObserversFunc, addObserverFunc, deleteObserverFunc)
@payroll = ->
console.log "#{@getName()}のために小切手を切ります!"
console.log "彼の給料はいま#{@getSalary()}です!"
@taxMan = ->
console.log "#{@getName()}に新しい税金の請求書を送ります!"
package views
import org.specs2.mutable.Specification
import play.api.test.TestServer
import play.api.test.Helpers.{ running, HTMLUNIT }
class ObserverSpec extends Specification {
private val testPort = 3333
"Observer" should {
"add payroll observer" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll);
e.setSalary(200);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliのために小切手を切ります!
彼の給料はいま200です!
zaneliのために小切手を切ります!
彼の給料はいま300です!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
"add taxMan observer" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(taxMan);
e.setSalary(200);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliに新しい税金の請求書を送ります!
zaneliに新しい税金の請求書を送ります!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
"add payroll and taxMan observers" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll, taxMan);
e.setSalary(200);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliのために小切手を切ります!
彼の給料はいま200です!
zaneliに新しい税金の請求書を送ります!
zaneliのために小切手を切ります!
彼の給料はいま300です!
zaneliに新しい税金の請求書を送ります!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
"add payroll and taxMan observers, delete taxMan observer" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll, taxMan);
e.setSalary(200);
e.deleteObserver(taxMan);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliのために小切手を切ります!
彼の給料はいま200です!
zaneliに新しい税金の請求書を送ります!
zaneliのために小切手を切ります!
彼の給料はいま300です!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
"add invalid observers" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll, taxMan, null, "foo", 1);
e.setSalary(200);
e.deleteObserver(taxMan);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliのために小切手を切ります!
彼の給料はいま200です!
zaneliに新しい税金の請求書を送ります!
zaneliのために小切手を切ります!
彼の給料はいま300です!
300
"""
browser.$("#__console_warn").getValue must_== """undefined is not a function
String is not a function
Number is not a function
"""
}
}
// 追加していないオブザーバを削除しても影響が無いことの確認
"delete not added observer" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll);
e.setSalary(200);
e.deleteObserver(taxMan);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliのために小切手を切ります!
彼の給料はいま200です!
zaneliのために小切手を切ります!
彼の給料はいま300です!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
// addObserver ではなく observers = で直接オブザーバを上書きして不正なオブザーバを設定してもエラーにならないことの確認
"rewrite observers" in {
running(TestServer(testPort), HTMLUNIT) { browser =>
browser.goTo(s"http://localhost:${testPort}/observer")
browser.executeScript("""
var e = new Employee("zaneli", "プログラマ", 100);
e.addObserver(payroll);
e.observers = [taxMan, null, "foo", 1]
e.setSalary(200);
e.deleteObserver(taxMan);
e.setSalary(300);
console.log(e.getSalary());
""")
browser.$("#__console_log").getValue must_== """zaneliに新しい税金の請求書を送ります!
300
"""
browser.$("#__console_warn").getValue must beNull
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment