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/




               ...
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
 authent...
ATM Overview
Services



                    Authentication

View     ATM
       Controller
                       Account
View
  ➡    renders the screen
Services
  ➡    authenticates the user
  ➡    transfer/ debit money from account
Controller...
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 ...
now, the implementation
class ATMController
  def initialize view
    @view = view
  end

  def handle event, params
      @view.render :login, {:...
second test
it quot;should raise error for unknown eventquot; do
  view = mock(View)
  view.should_not_receive(:render)

  controller ...
implementation
class ATMController
  def initialize view
    @view = view
  end

  def handle event, params
    if event == :card_inserte...
third test
it quot;should display withdraw menu when user has authenticatedquot; do
  view = mock(View)
  view.should_receive(:render...
implementation
class ATMController
  def initialize view, authentication_service
    @authentication_service = authentication_service
   ...
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(:log...
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...
so, as the controller keeps handling new
                 events...

๏   handle method keeps getting bloated
    ๏   which...
so, as the controller keeps handling new
                 events...

๏   handle method keeps getting bloated
    ๏   which...
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[:ac...
After
def handle event, params
    case event
      when :card_inserted
        handle_card_inserted params
      when :au...
step 2: extract class
Before

def handle_authenticate params
    if @authentication_service.
                authenticate(params[:account_id], p...
After

def handle_authenticate params
    action = AuthenticateAction.new @view, @authentication_service
    action.execut...
extract superclass
class Action
  def execute params
    raise quot;not implemented!quot;
  end
end



class AuthenticateAction < Action
  de...
configure the controller
  with map of actions
class ATMController
  def initialize map
    @actions = map
  end

  def handle event, params
      if @actions.include? e...
now, even the tests are simpler!
describe quot;an instance of quot;, ATMController do
  it quot;should execute the action for the eventquot; do
      param...
some points for discussion


•   Do we need a command pattern in
    dynamic languages ?
    •   can we get away with usin...
Upcoming SlideShare
Loading in …5
×

Refactoring Conditional Dispatcher To Command

1,042 views

Published on

Published in: Business, Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,042
On SlideShare
0
From Embeds
0
Number of Embeds
27
Actions
Shares
0
Downloads
8
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Refactoring Conditional Dispatcher To Command

  1. 1. Refactoring Conditional Dispatcher with Command (based on Refactoring to Patterns) Sreenivas Ananthakrishna
  2. 2. let’s start with an example...
  3. 3. http://www.flickr.com/photos/fastlanedaily/509830016/ http://www.flickr.com/photos/martineian/485029758/ The ATM!
  4. 4. so what can the ATM do?
  5. 5. so what can the ATM do? ➡ Display the login screen when card is inserted
  6. 6. so what can the ATM do? ➡ Display the login screen when card is inserted ➡ Authenticate the user
  7. 7. so what can the ATM do? ➡ Display the login screen when card is inserted ➡ Authenticate the user ➡ Allow authenticated user to withdraw $$$
  8. 8. ATM Overview
  9. 9. Services Authentication View ATM Controller Account
  10. 10. View ➡ renders the screen Services ➡ authenticates the user ➡ transfer/ debit money from account Controller ➡ handles requests from view
  11. 11. let’s focus on building the ATMController...
  12. 12. and in TDD fashion, the test come first!
  13. 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
  14. 14. now, the implementation
  15. 15. class ATMController def initialize view @view = view end def handle event, params @view.render :login, {:account_id => params[:account_id]} end end
  16. 16. second test
  17. 17. 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
  18. 18. implementation
  19. 19. 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
  20. 20. third test
  21. 21. 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
  22. 22. implementation
  23. 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
  24. 24. addition of new dependencies has broken other tests!
  25. 25. so let’s fix it!
  26. 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. 27. so, as the controller keeps handling new events...
  28. 28. so, as the controller keeps handling new events... ๏ handle method keeps getting bloated
  29. 29. so, as the controller keeps handling new events... ๏ handle method keeps getting bloated ๏ which means higher complexity
  30. 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. 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. 32. let’s see how we can simplify by refactoring to the Command
  33. 33. refactoring mechanics
  34. 34. step 1: compose method
  35. 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. 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
  37. 37. step 2: extract class
  38. 38. Before def handle_authenticate params if @authentication_service. authenticate(params[:account_id], params[:pin]) @view.render :withdraw_menu end end
  39. 39. After def handle_authenticate params action = AuthenticateAction.new @view, @authentication_service action.execute params end
  40. 40. extract superclass
  41. 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
  42. 42. configure the controller with map of actions
  43. 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
  44. 44. now, even the tests are simpler!
  45. 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. 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?

×