What are the advantages and disadvantages of Duck Typing?

Asked

Viewed 1,081 times

13

Duck Typing is a principle that states that one should not use inheritance or interface to define which methods a class should have.

Example:

# ruim
class Animal
  # método abstrato
  def speak
  end
end

# estendendo superclasse por herança
class Duck < Animal
  def speak
    puts "Quack! Quack"
  end
end

# estendendo superclasse por herança
class Dog < Animal
  def speak
    puts "Bau! Bau!"
  end
end

# bom
class Duck
  def speak
    puts "Quack! Quack"
  end
end

class Dog
  def speak
    puts "Bau! Bau!"
  end
end

What advantages and problems this approach can bring?

2 answers

15


As a Java developer, I am not the #1 fan of this technique. : b

But I can see some of the impacts Duck Typing may have.

Perks

  • Flexibility: facilitates refactoring, since it is not necessary to reshape a hierarchy of classes and interfaces when some kind of behavior changes. It is a type of "inexpensive polymorphism", allowing you to even use existing classes in generic methods without having to change them to implement interfaces or inherit classes.
  • Weaker coupling: interfaces and abstract classes are kind of contracts and force a strong bond between subclasses and superclasses. Without it, you don’t get so dependent on these calls.
  • Freedom of modeling: classes do not need to comply with predefined contracts, so the developer can postpone design decisions and change the behavior of generic methods at any time.

Disadvantages

  • Error-prone: Every freedom has its price. Each method will be responsible for verifying the type it receives, since there is no contract to guarantee the behavior of the object received. Any part of the code that calls a method that accesses undeclared attributes or methods has the potential to generate errors, since the developer needs to know the code that is running or make assumptions about it.
  • Strong coupling: Not using interfaces or abstract classes does not ensure weak coupling. After all, you may end up calling methods indiscriminately and end up with a strong coupling. And to make matters worse, it gets harder to identify that.
  • Difficulty tracking: not having a "contract" of use, makes it unpredictable who will perform what, so if you need to refactor a code you can go through difficulties to identify which classes also need to adjust.
  • Less readability: The developer has more freedom, but without discipline an inexperienced developer will end up with unreadable code. Imagine maintaining a system where there are no common types, each class is different and the developer calls at all times random methods on objects with no defined type.
  • Performance: in some languages, such as Java that uses reflection, the performance of Duck Typing is less than a normal call because the compiler cannot optimize the call. However, in dynamic languages (interpreted) there is usually no difference. In C++, from what I understand, there are no performance problems because the compiler can identify whether the classes actually have the methods invoked in this way.

Considerations

From my point of view, use Duck Typing can be advantageous for projects whose priority is to be flexible and adaptable, as well as having focused and experienced developers in a small team.

However, with more "new" people or for large and long-term projects, working with a little more "bureaucracy", that is, a well-defined class and interface model, probably brings more benefits and avoids chaos.

  • 4

    I agree with you @utluiz, and not only you, but any developer (99%) "born" In object-oriented languages, I would not be predisposed to this type of approach, besides this type of "technique" also goes against the principles of modeling object-oriented projects, where everything is "loose". I believe that as you said it can work for small projects (don’t forget that "small" projects grow). But when it is necessary a better organization, this is not welcome. (Sorry I had to give a hint of opinion, hehe)

13

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, MouseWheelListenerand 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.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.