Duck Typing, typing for behaviors

0
358

If we tried to define each object starting from the methods it contains and not from the class from which it is born? With this starting point the idea of Duck Typing is born, a typing style that finds in Ruby a perfect and fitting partner.

If it looks like a duck …

The term used to represent this practice comes from an interesting quotation attributed to James Whitcomb Riley, American poet: «When I see a bird that walks like a duck, I call that bird a duck . » .

Applying this quote to the world of object-oriented programming, we could write that if an object behaves in a manner conforming to specifications, then that object is not dissimilar to all those adhering to the same specifications, regardless of their ‘structural’ similarities.

It therefore follows that in the practice of duck-typing there should not be a check on the type of object on which the method is to be invoked, but only on the fact that this method exists or not.

The duck-typing in Ruby

Ruby lends itself very naturally to this development practice; let’s immediately look at a simple example by creating two classes that expose the same methods to a specific behavior:

<pre class=”brush: php; html-script: true”>
class Man
def walk
@distance = @ distance.to_i + 1
end
end

class Turtle
def walk # turtles sometimes can boost
@distance = @ distance.to_f + (rand (30)> 1? 0.01: 10)
end
end

def marathon (p1, p2, distance = 100)
puts “Beginning a race between a # {p1.class} and a # {p2.class}”
loop do
step1, step2 = p1.walk, p2.walk
next if (step1 <= distance && step2 <= distance)
puts (
if (step1 + step2> distance * 2) then “Draw”
elsif (step1> distance) then “Player 1 is the winner”
else “Player 2 is the winner”
end); break
end
end

marathon (Man.new, Turtle.new)
marathon (Man.new, Man.new)
marathon (Turtle.new, Man.new)
</pre>

In this case, any instance of any object can participate in the marathon provided that it contains the walk method and that this behaves in a standard way both in terms of input parameters and output values.

We can summarize what has just been said by introducing the concept of behavior (behavior); in this case the man and the tortoise share a similar behavior: ‘walker’; one can therefore say that each of the two objects behaves like a walker: in English acts_as_walker.

A very elegant way in Ruby to manage the behaviors lies in the use of the Mixin technique: a module containing the behavioral logic that can be activated through the invocation of a class method; let’s see how:

<pre class=”brush: php; html-script: true”>
module Walker
def self.included (base)
base.send (: extend, WalkerClassMethods)
base.send (: include, WalkerInstanceMethods)
end

module WalkerClassMethods
def acts_as_walker (pr)
define_method (: walk) {@distance = @ distance.to_f + pr.call}
end
end

module WalkerInstanceMethods
def acts_as_walker ?; respond_to? (: walk) end
end
end

Object.send (: include, Walker)

class Man
acts_as_walker proc {1}
end

class Turtle
acts_as_walker proc {rand (30)> 1? 0.01: 10}
end
# .. the ‘marathon’ method is identical to the previous one
</pre>

This approach also inserts a useful control tool that gives the possibility to methods such as marathon being able to discriminate at runtime which instances have the behavior walker and which are not.

It should be noted that the control method acts_as_walker?does not make any kind of validation on the nature of the class but only with regard to the effective adherence of the application to the requested behavior (which in this case is resolved in certifying the presence of the method walk).

We rewrite the method marathon also taking into account behavior control:

<pre class=”brush: php; html-script: true”>
def marathon (p1, p2, distance = 100)
puts “Beginning a marathon between a # {p1.class} and a # {p2.class}”

if! [p1, p2] .all? {| p | p.acts_as_walker?}
puts “Match invalid! At least one of the players can not walk!”
return
end

loop do
step1, step2 = p1.walk, p2.walk
next if (step1 <= distance && step2 <= distance)
puts (
if (step1 + step2> distance * 2) then “Draw”
elsif (step1> distance) then “Player 1 is the winner”
else “Player 2 is the winner”
end); break
end
end
</pre>

Now let’s be honest with the actual functioning of what I just wrote adding the following instructions at the end of the script:

<pre class=”brush: php; html-script: true”>
class Chair
def initialize (name, price)
@name, @price = name, price
end
end
marathon (Chair.new (“Steel Chair”, 10), Turtle.new)
</pre>

By executing the code so far produced we should obtain a result similar to the following:

<pre class=”brush: php; html-script: true”>
Beginning a marathon between in Man and Turtle
Player 1 is the winner
Beginning a marathon between in Man and a Man
Draw
Beginning a marathon between in Turtle and a Man
Player 2 is the winner
Beginning a marathon between a Chair and a Turtle
Match invalid! At least one of the players can not walk!
</pre>

Duck Typing, typing for behaviors

Even the chairs can walk

The chair object does not walk, so it does not conform to behavior acts_as_walking; but what would happen if instead some of its instances were? Suppose we have to manage a catalog of chairs that also includes some extravagant laboratory experiments such as the ‘robot chair’, equipped with autonomous walking; the duck-typing approach, not being based on the class but on the behavior, also allows us to manage these cases intervening directly on the interested instance; we continue at the end of the previous listing adding:

<pre class=”brush: php; html-script: true”>
wooden_base = Chair.new (“Continuous Arm Windsor Chair”, 600)
massage = Chair.new (“Massage Chair”, 1500)
simple = Chair.new (“Simple Chair”, 30)
robot_chair = Chair.new (“WL-16 Robot Chair”, 9999)

def robot_chair.walk
@distance = @ distance.to_f + 2
end

marathon (wooden_base, Man.new)
marathon (robot_chair, Man.new)
</pre>

By executing the script we will notice how while the wooden chair is not suitable for walking, and therefore not even competing in a marathon, the robotic chair is recognized as adherent to the behavior and therefore admitted to the race.

This result can be obtained by intervening as a single instance in order to make it conform to the specifications of the established behavior.

Conclusions

The duck typing expertly mixes very powerful characteristics and dark sides; this technique embodies a simple, streamlined and elegant methodology to capture the essences of business objects and share them horizontally within an application.

The benefits of this approach are many: every single instance in every moment has the prerequisites to be perfectly adherent to the tasks it must honor. On the other hand, all this flexibility brings with it a price to pay: every developer on the project must be aware of the functioning of all implemented behaviors, both in case he wants to add someone to his own objects or to avoid that some developed methods override behaviors existing.

LEAVE A REPLY

Please enter your comment!
Please enter your name here