This comes from the more pure concept of interface in which you consider that two different objects have the same interface if they respond to the same messages, to the same way of interacting, of talking to them.

The duck analogy comes from saying that if something looks like a duck, then it's a duck. So in Ruby you don't need to define interfaces the way C#/Java does. Interfaces exist but you don't define them, they are already there, conceptually.
The biggest advantage I see about this is that the effort needed to assure that your design is low coupled is greatly reduced. Any object can be substituted by any other that implements the required methods without extra work. This dramatically improves your efficiency when making changes and evolving your design and testing gets easier.
Let's compare a bit C# and Ruby interfaces:
interface Cuackable
{
void Cuack();
}
class ADuck : Cuackable
{
public void Cuack()
{
//cuack the ADuck way
}
public void Swim()
{
//swim the ADuck way
}
}
class BDuck : Cuackable
{
public void Cuack()
{
//cuack the BDuck way
}
public void Swim()
{
//swim the BDuck way
}
}
So If you need the
Cuackable interface from your client code, you declare a variable or parameter that will eventually hold an ADuck or BDuck implementation.The compiler knows you can use
ADuck or BDuck because they declare they implement the needed interface.You know you can use
ADuck or BDuck because they implement the method signatures that you need. So there's a mismatch between the way you think of the interface and the way the compiler does (Ruby doesn't have this mismatch, we'll see this later).What happens if now you realize that
Swim should be part of another interface? Well then you'll have to define a new Swimable interface and define Swim as it's only member. You'll also need to implement that interface in each of the classes that implement the method and you'll also need to make the clients use that interface.
interface Cuackable
{
void Cuack();
}
interface Swimable
{
void Swim();
}
class ADuck : Cuackable, Swimable
{
public void Cuack()
{
//cuack the ADuck way
}
public void Swim()
{
//swim the ADuck way
}
}
class BDuck : Cuackable, Swimable
{
public void Cuack()
{
//cuack the BDuck way
}
public void Swim()
{
//swim the BDuck way
}
}
Now imagine that you have a third client method that requires to use both
Cuack and Swim. In that case you'd have to pass the same object in two parameters to the client method:
void ClientMethod(Cuackable cuackable, Swimable swimable)
{
//code
cuackable.Cuack();
//...
swimable.Swim();
//more code
}
//later you could call ClientMethod with the same object if you think that's correct...
aDuck = new ADuck();
ClientMethod(aDuck, aDuck)
//or with different objects if that's what's intended...
ClientMethod(new ADuck(), new BDuck());
Another alternative for the first example, although not the same, is creating another new interface that joins
Cuackable and Swimable, a good name would be, ermmm... Duck. Then now you'd be implementing three different interfaces, all describing the different ways your client code can see your objects.This grows exponentially being the number of possible interfaces 2^n - 1, where n is the number of public methods. Of course your clients see them only in the subset of 2^n - 1 that is useful to them, but this will be still a big number in non trivial systems, always assuming that you really believe in object polymorphism and the interface segregation principle which encourages having small interfaces.
As you can see, such a big number of interfaces is impractical to handle in strongly typed languages so we usually change the way we design to cope with this programming infrastructure problem and we start violating principles of good design. The most common solution to this is having huge interfaces with clients that, of course, just use a subset of them. This is, among other problems, a manteinance nightmare. But a smaller nightmare than the more correct alternative of having one interface for each method.
Ruby's duck typing
Let's see the previous code in it's Ruby version:
class ADuck
def cuack
#cuack the ADuck way
end
def swim
#swim the ADuck way
end
end
class BDuck
def cuack
#cuack the BDuck way
end
def swim
#swim the BDuck way
end
end
def client_method(can_cuack_and_swim)
#code
can_cuack_and_swim.cuack
#...
can_cuack_and_swim.swim
#more code
end
client_method(ADuck.new)
#or you can split it in two like the previous C# example
def client_method(can_cuack, can_swim)
#code
can_cuack.cuack
#...
can_swim.swim
#more code
end
client_method(ADuck.new, BDuck.new)
As you can see, we don't define interfaces, they are implicitly there because as we said earlier, interfaces are defined by the clients.
client_method just asks that the object that is passed as a parameter implements the required method. If you create a new class that implements those required methods, then you implement client_method required interface, your client sees a duck because your object behaves the way it expects, like a duck, no more extra work for creating and mantaining your classes interfaces. That's simplicity and programmer friendliness, from there you get easier abstraction, free low coupling and the resultant increase in quality and productivity in the right way.





2 comments:
Very good article.
Just for correctness with best practices, you should change your clientMethod (camel case) to client_method (snake case) on the ruby example. :-D
But I think is worth to mention what are the "supposedly bad" consequences of not explicitly declaring the interfaces (according to strongly-type-minded developers).
In languages like C# or Java where you have the extra step of compiling your code, the compiler would warn you if your ADuck class happens to declare its use of the Cuackable interface, but you forgot to actually implement the cuack method.
In Ruby, you'll notice your error at runtime, when the client_method calls cuack on your ADuck instance.
Yeah... that can get ugly on a production system.
But (here comes MY opinion):
1. Thanks to ruby's clear and concise syntax, and some best practices you come to learn after a period of ruby programming, I tend to think that this king of human errors are much less frequent.
2. You should ALWAYS have tests. It doesn't matter in what language you're developing, automated tests should be there to give you a safe net over your code. In languages like C# and Java this tests will more likely test your apps behavior, because the compiler already tested your syntax. But in languages like Ruby, these tests will test both aspects of your code. And if you test well, this tests will be responsible for raising that forgotten cuack method on your ADuck class BEFORE you go to production. You'll be safe on production, a happier developer and you'll dream like an angel knowing your apps tests pass.
Thanks Diego I already updated the post.
I agree with you and I think that most people that don't think Ruby is a serious language usually see only one side of the coin about this aspect of the C#/Java vs Ruby comparison. There's usually not much serious evaluation of both alternatives.
I think this is mostly due to not seeing the value of low coupled designs. If you can't see this you also can't see the good consequences of not declaring interfaces and you tend to conclude that the way languages like C#/Java handle interfaces is necessarily better.
Post a Comment