Disclaimer: Like the utluiz, I am also Javeiro. In particular it schools strongly and statically typed languages; I believe in complex typing systems and compilers able to eliminate inconsistencies early to avoid running-time problems. Changing as kids, I’m a guy who tends more towards Scala than toward Python; so I defend mechanisms of Duck Typing and Late Binding is at least to play the role of devil’s advocate.
The answer of utluiz was quite complete in order to illustrate the advantages and disadvantages of the Duck Typing as well as systems modeling practices, however, I would like to bring out some of the typical problems that may arise with interfaces and abstract classes, with special focus on those that can be mitigated through Duck Typing.
Interfaces vs Duck Typing
Interfaces, traits and classes / abstract methods are ways to define contracts. These contracts are nothing more than syntactic agreements. Example:
If you want to participate in my race you have to have a method void correr(Point destino)
which will be fired at the start of the race and a method Point getPosicao()
to return me to your position at all times.
That way I give you an interface Corredor
which you must implement as you please. You register yourself in the race as a Corredor
and I am not interested in any other aspect of it besides the ones we previously agreed on.
Note that a syntactic agreement can be disregarded semantically, ie the method correr
, as long as it obeys the interface signature can do anything other than run. The "contract" gives a sense of security (through some level of coupling), but in the background the only thing it guarantees is the presence of certain methods in a given object.
The Duck Typing is also a syntactic agreement:
I expect any runner who registers in the race to implement the methods correr
and getPosicao
, you don’t necessarily have to sign any contracts with me right now, but you promise me that you are able to do this, otherwise things will get complicated at race time.
For this reason Duck Typing is commonly associated with runtime errors, dynamically typed languages, and late Binding. There are elegant ways to work with Duck Typing in statically typed languages, including getting errors at compile time (see the part about Duck Typing in Scala) but that’s not the point here.
There is also a certain distrust about the semantics of the methods exposed by the corridor. Like the method correr
does not follow a contract, it may have been written before the planning of my race, does it do exactly what I need? See that one way or another, with both approaches it is possible to disregard the contract semantically.
The main difference between one mechanism and another is:
With an interface you explicitly agree a priori that you will work according to my definition of what a runner does; that contract, from that moment on, will be part of your essential definition (and. g., Pessoa implements Corredor
).
VS., To be a runner you have to act like a runner as per my definition of a runner the moment you are running.
Multiple contracts
Interface has its advantages, but also its problems. Among them, possible explosions of complexity.
Imagine that a Triathlon athlete who swims, rides a bike and runs needs to fulfill a certain contract. He is still a runner, swimmer or cyclist by definition, soon ends up signing a contract that inherits from other contracts:
interface Triathloner extends Corredor, Nadador, Ciclista
Pessoa implements Triathloner
Note that we are signing a contract somewhat heavy here, the barriers between is-one and behaves like a began to burn up.
Imagine now that the same athlete wants to participate in another Triathlon-style competition, which replaces cycling with climbing. He doesn’t want to stop being a Triathlon athlete, so he ends up implementing two interfaces that he inherits from other contracts:
interface Triathloner extends Corredor, Nadador, Ciclista
interface TriathlonerII extends Corredor, Nadador, Escalador
Pessoa implements Triathloner, TriathlonerII
Again the complexity is increasing. Our interfaces are no longer making sense.
Optional operations
Imagine now a third situation, in a given event the athlete can choose between pedalar
or escalar
but must correr
and nadar
. How to model this contract? Should the athlete implement a specific interface for that event? We should load the generic interface and build a mechanism with new methods and late dispatching to distinguish the athlete who knows how to swim from the one who wants to swim in the competition? Must the athlete who does not know how to swim implement the method nadar
with an empty body (or perhaps an exception)?
But this example seems somewhat forced not? This is the exact case of the AWT Listeners API in Java. Here’s an example:
A Mouselistener should implement the methods mouseClicked
, mouseEntered
, mouseExited
, mousePressed
and mouseReleased
. Case a Listener
be interested in only part of the events he must inherit from the abstract class MouseAdapter
implementing all methods of MouseListener
(as well as of MouseMotionListener
, MouseWheelListener
and EventListener
) with empty bodies. This allows the user to override only the methods of their interest.
The problems here are obvious: Complete mixture between "being one" and "behaving like", abstract classes with all concrete methods and no body, as well as absolute confusion about which interfaces to implement and/or which Adapters extend:
interface MouseListener extends EventListener
interface MouseMotionListener extends EventListener
interface MouseWheelListener extends EventListener
interface MouseInputListener extends MouseListener, MouseMotionListener
abstract class MouseMotionAdapter implements MouseMotionListener
abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener
abstract class MouseInputAdapter extends MouseAdapter implements MouseInputListener
On the other side we have the API of Ajax jQuery waiting for any object (and optional) settings
. All properties and methods of this object are optional:
$.ajax();
And:
$.ajax({
type: "POST",
url: "some.php",
beforeSend: function( xhr ) {
xhr.overrideMimeType("text/plain; charset=x-user-defined");
},
dataType: "json",
data: { name: "John", location: "Boston" },
success: function( data ){
console.log(data);
}
});
Think complexity to develop a similar API without Duck Typing... The interface to callbacks and methods of handling, Patterns design as Builder going into action and so on.
And look how cool, with Duck Typing can even reuse the same object in the method jQuery.post. He will ignore the property type
and the method beforeSend
and will use the rest of the properties without you having to modify the object in any way.
System of Structural Types
There is a middle ground between the advantages of Duck Typing ("local" contracts, weaker coupling, simplicity, etc.) and compilation-time type checking?
It exists. It’s called structural types. Here’s an example in Scala:
def registraCorredor(corredor: {def correr(destino: Point): Unit}) {
corrida.registrar(corredor);
}
In such a case a corridor may be any object having a method correr
; the runner type need not implement any interface, however if the method correr
does not exist in the object corredor
passed as parameter to the function registraCorredor
compiler will error. Combine this with other "magic" of Scala like the apply function and default values for parameters and we have the best of both worlds.
Related: It is wrong to use class inheritance to group common behaviors and attributes?
– Math