13. describe quot;an instance of quot;, ATMController do
it quot;should render login when card is insertedquot; do
view = mock(View)
view.should_receive(:render).
with(:login, {:account_id => 1234})
controller = ATMController.new view
controller.handle :card_inserted, {:account_id => 1234}
end
end
23. class ATMController
def initialize view, authentication_service
@authentication_service = authentication_service
@view = view
end
def handle event, params
case event
when :card_inserted
@view.render :login, {:account_id => params[:account_id]}
when :authenticate
if @authentication_service.
authenticate(params[:account_id], params[:pin])
@view.render :withdraw_menu
end
else
raise quot;cannot handle event #{event}quot;
end
en
26. it quot;should render login when card is insertedquot; do
view = mock(View)
view.should_receive(:render).with(:login, {:account_id =>
1234})
authentication_service = mock(AuthenticationService)
authentication_service.should_not_receive(:authenticate)
controller = ATMController.new view , authentication_service
controller.handle :card_inserted, {:account_id => 1234}
end
27. so, as the controller keeps handling new
events...
28. so, as the controller keeps handling new
events...
๏ handle method keeps getting bloated
29. so, as the controller keeps handling new
events...
๏ handle method keeps getting bloated
๏ which means higher complexity
30. so, as the controller keeps handling new
events...
๏ handle method keeps getting bloated
๏ which means higher complexity
๏ adding new events requires changing the
controller implementation
31. so, as the controller keeps handling new
events...
๏ handle method keeps getting bloated
๏ which means higher complexity
๏ adding new events requires changing the
controller implementation
๏ addition of new receivers also affects
existing test cases
32. let’s see how we can simplify
by refactoring to the Command
35. Before
def handle event, params
case event
when :card_inserted
@view.render :login, {:account_id => params[:account_id]}
when :authenticate
if @authentication_service.
authenticate(params[:account_id], params[:pin])
@view.render :withdraw_menu
end
else
raise quot;cannot handle event #{event}quot;
end
end
36. After
def handle event, params
case event
when :card_inserted
handle_card_inserted params
when :authenticate
handle_authenticate params
else
raise quot;cannot handle event #{event}quot;
end
end
38. Before
def handle_authenticate params
if @authentication_service.
authenticate(params[:account_id], params[:pin])
@view.render :withdraw_menu
end
end
41. class Action
def execute params
raise quot;not implemented!quot;
end
end
class AuthenticateAction < Action
def initialize view, authentication_service
@view = view
@authentication_service = authentication_service
end
def execute params
if @authentication_service.
authenticate(params[:account_id], params[:pin])
@view.render :withdraw_menu
end
end
end
43. class ATMController
def initialize map
@actions = map
end
def handle event, params
if @actions.include? event
@actions[event].execute params
else
raise quot;cannot handle event #{event}quot;
end
end
end
45. describe quot;an instance of quot;, ATMController do
it quot;should execute the action for the eventquot; do
params = {'foo' => 'bar'}
action = mock(Action)
action.should_receive(:execute).with(params)
controller = ATMController.new({:foo_event => action})
controller.handle(:foo_event, params)
end
it quot;should raise error for unknown eventquot; do
controller = ATMController.new({})
lambda {controller.handle(:foo, {})}.
should raise_error quot;cannot handle event fooquot;
end
end
46. some points for discussion
• Do we need a command pattern in
dynamic languages ?
• can we get away with using a block/
closure
• What are the different ways in which these
commands could be configured?