Was ist eigentlich Duck Typing?

Tags:

"Wenn ich einen Vogel sehe, der wie eine Ente läuft, wie eine Ente schwimmt und wie eine Ente schnattert, dann nenne ich diesen Vogel eine Ente."
- James Whitcomb Riley

Und was hat das jetzt mit Programmierung zu tun? Wikipedia fasst das ganz gut zusammen:

Duck-Typing ist ein Konzept der objektorientierten Programmierung, bei dem der Typ eines Objektes nicht durch seine Klasse beschrieben wird, sondern durch das Vorhandensein bestimmter Methoden.

Normalerweise zeige ich meine Beispiele ja in PHP auf, doch für diesen Artikel eignet sich Ruby wesentlich besser. Für PHPler:

Gucken wir uns mal diese äquivalenten Funktionen an:

# Ruby
def copy_file(obj, dest_path)
    src_path = obj.get_path
    puts "Copying file \"#{src_path}\" to \"#{dest_path}\"."
end

// PHP
function copyFile($obj, $destPath)
{
    $srcPath = $obj->getPath();
    echo "Copying \"$srcPath\" to \"$destPath\".";
}

Es kann also jeweils als erstes Argument ein Objekt übergeben werden, von welchem per Methode get_path/getPath() der aktuelle Speicherort einer Datei ermittelt und diese danach an den durch das zweite Argumente definierten Pfad kopiert wird. Letzteres ist im Beispiel nicht implementiert. Es wird nur eine Debug-Nachricht ausgegeben.

Der Unterschied zwischen beiden Programmiersprachen ist nun folgender:

In Ruby können wir also alle möglichen Typen von Objekten an die Funktion copy_file übergeben. Sie müssen einfach nur die Methode get_path besitzen, dann läuft alles gut. Das ist Duck Typing. Die Klassen/Typen dieser Objekte interessieren niemanden. Es zählt was sie können, nicht woher sie abstammen.

class Video
    def get_path
        "/local/path/video.mp4"
    end
end

class Image
    def get_path
        "/local/path/image.png"
    end
end

copy_file(Video.new, "/remote/path/video.mp4")
copy_file(Image.new, "/remote/path/image.png")

Um das selbe Verhalten in PHP zu ermöglichen, gibt es mehrere Möglichkeiten.

Type Hint mit Objekttyp

Das übergebene Objekt muss eine Instanz einer bestimmten Klasse sein.

function copyFile(Video $obj, $destPath)
{
    $srcPath = $obj->getPath();
    echo "Copying \"$srcPath\" to \"$destPath\".";
}

class Video
{
    public function getPath()
    {
        return '/local/path/video.mp4';
    }
}

copyFile(new Video(), '/remote/path/video.mp4');

Das Problem ist, dass ich so nur Video Objekte, aber keine z.B. Image Objekte verarbeiten kann. Ausserdem muss ich ausserhalb der Funktion abfangen, falls ein falsches Objekt übergeben wurde (try/catch). Schauen wir mal weiter.

Type Hint mit Interface

Das übergebene Objekt muss ein bestimmtes Interface implementieren.

function copyFile(Copyable $obj, $destPath)
{
    $srcPath = $obj->getPath();
    echo "Copying \"$srcPath\" to \"$destPath\".";
}

interface Copyable
{
    public function getPath();
}

class Video implements Copyable
{
    public function getPath()
    {
        return '/local/path/video.mp4';
    }
}

class Image implements Copyable
{
    public function getPath()
    {
        return '/local/path/image.png';
    }
}

copyFile(new Video(), '/remote/path/video.mp4');
copyFile(new Image(), '/remote/path/image.png');

Das schaut ja schon mal ganz gut aus. Wenn ich aber für jeden Kleinkram ein eigenes Interface schreiben muss, dann kann dies auch schnell nervig und unübersichtlich werden. Weil plötzlich zwingt mich ein Interface eine Methode zu implementieren, die für eine bestimmte Klasse nicht zutrifft, für die 30 anderen aber schon. Ausserdem haben wir immer noch das Problem, dass die Funktion nicht selber reagieren kann, falls ein Objekt mit falschem Typ übergeben wurde.

Gehen wir weiter zur Eigenbaulösung.

Selbständige Validierung der Argumente

Das Argument wird innerhalb der Funktion auf bestimmte Merkmale überprüft: Es muss ein Objekt sein und die Methode getPath() besitzen.

function copyFile($obj, $destPath)
{
    if (!is_object($obj) || !is_callable($obj, 'getPath')) {
        throw new InvalidArgumentException('Argu...');
    }

    $srcPath = $obj->getPath();
    echo "Copying \"$srcPath\" to \"$destPath\".";
}

class Video
{
    public function getPath()
    {
        return '/local/path/video.mp4';
    }
}

class Image
{
    public function getPath()
    {
        return '/local/path/image.png';
    }
}

copyFile(new Video(), '/remote/path/video.mp4');
copyFile(new Image(), '/remote/path/image.png');

Anhand des Beispiels sieht man auch, wieso Duck Typing auch als implizite Interfaces bezeichnet wird: Ein Argument muss nur genau diese Methoden implementieren, die auch wirklich im Funktionsrumpf genutzt werden.

Fazit

Ob Duck Typing schlussendlich etwas Gutes oder Schlechtes ist, darüber scheiden (und streiten) sich die Geister. Für- und Gegenargumente gibt es zahlreiche.

Ein Argument gegen Duck Typing ist, dass der Programmierer den Code viel besser kennen muss, da der Quelltext weniger hervorseh- und durchschaubar ist. So kann z.B. nicht direkt von der Funktionssignatur abgelesen werden, welche Parameter(typen) erwartet werden. Das Gegenargument dazu ist, dass der Programmierer den Code gefälligst kennen sollte, wenn er ihn pflegen will und das etwaige Probleme durch gutes Testing abgefangen werden können.

"Wenn ich einen Vogel sehe, der wie eine Ente läuft und wie eine Ente schnattert, dann könnte dies ein Drache sein, der eine Ente nachmacht."

So, jetzt wisst ihr Bescheid. ;-) Meinungen und etwaige Korrekturen gerne in den Kommentaren.

Ähnliche Artikel

Kommentare