« HE:labs
HE:labs

Reducing cost of change - Part 1: Duck Typing

Postado por Oswaldo Ferreira em 23/03/2015

As we already know, software is something that changes a lot. But, as developers, we can manage a path through a loosely coupled codebase that doesn't make we cry everytime we need to change it.

Today we gonna learn a technique known as duck typing.

Definition

Duck types are defined as a public and common interface that are used by multiple objects.

Duck typed objects are chamaleons that are defined more by their behavior than by their class.

  • Sandi Metz

So it's how the technique gets it's name, "If a an object quacks like a duck and walks like a duck, it's a duck". When combined with SRP (Single Responsibility Principle), Duck Typing can make a great flexibility improvement of your application.

Understanding by Examples

Let's be abstract here. It's time to learn how and when to apply Duck typing.

In this example, we are dealing with an application that abstractedly prepares a party:

 1 class Party
 2   attr_reader :name, :theme, :allowed_age_group
 3 
 4   def self.components
 5     [Organizer, Designer, DiskJokey, BarMan]
 6   end
 7 
 8   def prepare
 9     self.class.components.each do |component|
10       if component.is_a? Organizer
11         component.create_facebook_event(name)
12       elsif component.is_a? Designer
13         component.create_ticket_art(name, theme)
14       elsif component.is_a? DiskJokey
15         component.prepare_musical_base(theme)
16       elsif component.is_a? BarMan
17         component.buy_drinks(allowed_age_group)
18       end
19     end
20   end
21 end
22 
23 class Organizer
24   def create_facebook_event(name)
25     # ...
26   end
27 end
28 
29 class Designer
30   def create_ticket_art(name, theme)
31     # ...
32   end
33 end
34 
35 class DiskJokey
36   def prepare_musical_base(theme)
37     # ...
38   end
39 end
40 
41 class BarMan
42   def buy_drinks(allowed_age_group)
43     # ...
44   end
45 end

I know what are you thinking, it's like a seven errors game. Take a look on the code before proceeding. Did it? Alright. Time to see what's wrong here.

  • Party knows each and every component that prepares the party (Dependency).
  • Party knows the interface of every component object, and which messages it should send to them (Dependency).
  • Party knows what parameters every component messages need (Dependency).
  • Those if's and elses makes our eyes bleed (That's bad).

There's a duck type hiding there, we just need some time to see it. Every party component prepares a party right? So there's a common interface there.

So the improved code:

 1 class Party
 2   attr_reader :name, :theme, :allowed_age_group
 3 
 4   def prepare
 5     PartyPreparer.prepare(self)
 6   end
 7 end
 8 
 9 class Organizer
10   def prepare_party(party)
11     create_facebook_event(party.name)
12   end
13 
14   private
15 
16   def create_facebook_event(name)
17     # ...
18   end
19 end
20 
21 class PartyPreparer
22   def self.prepare(party)
23     components.each { |component| component.prepare_party(party) }
24   end
25 
26   def self.components
27     [Organizer, Designer, DiskJokey, BarMan]
28   end
29 end
30 
31 
32 class Designer
33   def prepare_party(party)
34     create_ticket_art(party.name, party.theme)
35   end
36 
37   def create_ticket_art(name, theme)
38     # ...
39   end
40 end
41 
42 class DiskJokey
43   def prepare_party(party)
44     prepare_musical_base(party.theme)
45   end
46 
47   private
48 
49   def prepare_musical_base(theme)
50     # ...
51   end
52 end
53 
54 class BarMan
55   def prepare_party(party)
56     buy_drinks(party.allowed_age_group)
57   end
58 
59   private
60 
61   def buy_drinks(allowed_age_group)
62     # ...
63   end
64 end

Once you have a duck type in mind, define its interface, implement that interface where necessary, and then trust those implementers to behave correctly

  • Sandi Metz

Now our party components know how to prepare a party with a common interface. In addition to that, party now only knows that it has a preparer. We now trust on the preparer to behave correctly when it's called.

The perception to see when we can apply a duck type comes with time. It's easy to implement it, but not so easy to realize when to use it. The first insights should come when we see a lot of type checking, like those:

 1 def prepare
 2   @components.each do |component|
 3     if component.is_a? Organizer
 4       component.create_facebook_event(name)
 5     elsif component.is_a? Designer
 6       component.create_ticket_art(name, theme)
 7     elsif component.is_a? DiskJokey
 8       component.prepare_musical_base(theme)
 9     elsif component.is_a? BarMan
10       component.buy_drinks(allowed_age_group)
11     end
12   end
13 end

Conclusion

So these are the points that we improved on our older code:

  • Created a PartyPreparer class that encapsulates party preparing behavior.
  • Created a common interface for all components that responds preparer.
  • Simplified Party class. It doesn't need to know how to prepare itself.

Notice that we moved unstable methods interface to private in each component. Those components methods can change any time, also they can horizontaly grow. The Designer class could have a new #send_to_graphic method. That method would also be called into Designer#prepare_party, and it would not affect any of previous behavior.

So it's pretty much that for the first post of the "Reducing cost of changes" series. We learned how to improve our public interfaces realizing how to apply Duck typing technique.

Compartilhe

Sabia que nosso blog agora está no Medium? Confira Aqui!