Refactoring Conditional Dispatcher To Command
Upcoming SlideShare
Loading in...5
×
 

Refactoring Conditional Dispatcher To Command

on

  • 1,360 views

 

Statistics

Views

Total Views
1,360
Views on SlideShare
1,344
Embed Views
16

Actions

Likes
0
Downloads
4
Comments
0

2 Embeds 16

http://melbournepatterns.org 13
http://www.slideshare.net 3

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Refactoring Conditional Dispatcher To Command Refactoring Conditional Dispatcher To Command Presentation Transcript

  • Refactoring Conditional Dispatcher with Command (based on Refactoring to Patterns) Sreenivas Ananthakrishna
  • let’s start with an example...
  • http://www.flickr.com/photos/fastlanedaily/509830016/ http://www.flickr.com/photos/martineian/485029758/ The ATM!
  • so what can the ATM do?
  • so what can the ATM do? ➡ Display the login screen when card is inserted
  • so what can the ATM do? ➡ Display the login screen when card is inserted ➡ Authenticate the user
  • so what can the ATM do? ➡ Display the login screen when card is inserted ➡ Authenticate the user ➡ Allow authenticated user to withdraw $$$
  • ATM Overview
  • Services Authentication View ATM Controller Account
  • View ➡ renders the screen Services ➡ authenticates the user ➡ transfer/ debit money from account Controller ➡ handles requests from view
  • let’s focus on building the ATMController...
  • and in TDD fashion, the test come first!
  • 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
  • now, the implementation
  • class ATMController def initialize view @view = view end def handle event, params @view.render :login, {:account_id => params[:account_id]} end end
  • second test
  • it quot;should raise error for unknown eventquot; do view = mock(View) view.should_not_receive(:render) controller = ATMController.new view lambda {controller.handle(:foo, {})}. should raise_error(RuntimeError, quot;cannot handle event fooquot;) end
  • implementation
  • class ATMController def initialize view @view = view end def handle event, params if event == :card_inserted @view.render :login, {:account_id => params[:account_id]} else raise quot;cannot handle event #{event}quot; end end end
  • third test
  • it quot;should display withdraw menu when user has authenticatedquot; do view = mock(View) view.should_receive(:render).with(:withdraw_menu) authentication_service = mock(AuthenticationService) authentication_service.should_receive(:authenticate). with(1234, 5678). and_return(true) controller = ATMController.new view, authentication_service controller.handle :authenticate, {:account_id => 1234, :pin => 5678} end
  • implementation
  • 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
  • addition of new dependencies has broken other tests!
  • so let’s fix it!
  • 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
  • so, as the controller keeps handling new events...
  • so, as the controller keeps handling new events... ๏ handle method keeps getting bloated
  • so, as the controller keeps handling new events... ๏ handle method keeps getting bloated ๏ which means higher complexity
  • 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
  • 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
  • let’s see how we can simplify by refactoring to the Command
  • refactoring mechanics
  • step 1: compose method
  • 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
  • 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
  • step 2: extract class
  • Before def handle_authenticate params if @authentication_service. authenticate(params[:account_id], params[:pin]) @view.render :withdraw_menu end end
  • After def handle_authenticate params action = AuthenticateAction.new @view, @authentication_service action.execute params end
  • extract superclass
  • 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
  • configure the controller with map of actions
  • 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
  • now, even the tests are simpler!
  • 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
  • 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?