TU Wien:Objektorientierte Programmiertechniken VU (Puntigam)/Zusammenfassung

Aus VoWi
Zur Navigation springen Zur Suche springen

Diese Seite versucht das Skriptum kompakt zusammen zu fassen (und dabei auch die Wiederholungsfragen zu beantworten).

Status: Work in Progress, 2019W

Paradigmen der Programmierung[Bearbeiten | Quelltext bearbeiten]

Im Zusammenhang mit der Programmierung versteht man unter Paradigma eine bestimmte Denkweise oder Art der Weltanschauung.

Häufig unterscheidet man zwischen:

  • imperativ (auf Maschinenbefehlen aufbauend), unterteilt in prozedural und objektorientiert
  • deklarativ (auf formalen Modellen beruhend), unterteilt in funktional und logikorientiert

Diese einfache Einteilung ist kaum strukturiert und lässt weniger etablierte Paradigmen unbeachtet.

Berechnungsmodell und Programmstruktur[Bearbeiten | Quelltext bearbeiten]

Hinter jedem Programmierparadigma steckt ein Berechnungsmodell. Berechnungsmodelle haben immer einen formalen Hintergrund. Sie müssen in sich konsistent und in der Regel Turing-vollständig sein.

Formaler Hintergrund[Bearbeiten | Quelltext bearbeiten]

Funktionen
primitiv-rekursive Funktionen, Turing-vollständig: μ-rekursive Funktionen und λ-Kalkül
Prädikatenlogik
Heutzutage häufig bei Abfragesprachen von relationalen Datenbanken.
Constraint-Programmierung
Das Problem wird deklarativ mittels Einschränkungen beschrieben.
Temporale Logik und Petri-Netze
Freie Algebren
stark vereinfacht formulierte Algebren
Prozesskalküle
z.B. CSP und λ-Kalkül
Automaten
WHILE, GOTO und Co

Praktische Realisierung[Bearbeiten | Quelltext bearbeiten]

Es bestimmen immer wieder dieselben Eigenschaften den Erfolg oder Misserfolg eines Programmierparadigmas zu einem großen Teil mit:

Kombinierbarkeit
Bestehende Programmteile sollen sich möglichst einfach zu größeren Einheiten kombinieren lassen, ohne dass diese größeren Einheiten dabei ihre einfache Kombinierbarkeit einbüßen. (z.B. Funktionen sind gut kombinierbar)
Konsistenz
Alle Konzepte sollen in sich und miteinander konsistent sein, also gut zusammenpassen. Formalismen snd von Haus aus oft mit einander inkompatibel. Gute Sprachen und Paradigmen kommen mit wenigen konsistenten Konzepten aus.
Abstraktion
Programme sollen meistens nicht von Hardware oder (Betriebs-)System-Details abhängen, sondern möglichst portabel. Der erreichbare Abstraktionsgrad hat im Laufe der historischen Entwicklung von Programmierparadigmen ständig zugenommen und ist heute sehr hoch.
Systemnähe
Effizienz auf realer Hardware lässt sich scheinbar am leichtesten erreichen, wenn das Paradigma wesentliche Teile der Hardware und des Betriebssystems direkt widerspiegelt. Unverzichtbar ist Systemnähe dann, wenn Hardwarekomponenten direkt angesprochen werden müssen.
Unterstützung
Auch das beste Paradigma hat keinen Wert, wenn entsprechende Programmiersprachen, Entwicklungswerkzeuge sowie Bibliotheken vorgefertigter Programmteile fehlen. Ohne Personen, die an den Erfolg glauben und viel Zeit und Geld in die Entwicklung investieren, kann sich kein Paradigma durchsetzen.
Beharrungsvermögen
Größere Änderungen der Programmierparadigmen werden nur sehr langsam angenommen. Bei einem Paradigmenwechsel geht Wissen verloren und neue Erfahrungen müssen erst gemacht werden. Für einen Wechsel braucht es sehr überzeugende Gründe, wie etwa eine Killerapplikation, also einer erfolgreichen Software, die den Erfolg des Paradigmas so deutlich aufzeigt, dass das Potential zur Ersetzung althergebrachter Paradigmen in diesem Bereich unübersehbar ist.

Evolution und Struktur im Kleinen[Bearbeiten | Quelltext bearbeiten]

Widersprüchliche Ziele
  • Flexibilität und Ausdruckskraft
  • Lesbarkeit und Sicherheit
  • Verständlichkeit
Strukturierte Programmierung

Jedes Programm ist nur aus drei Kontrollstrukturen aufgebaut:

  • Sequenz
  • Auswahl (z.B. if und switch)
  • Wiederholung (z.B. Schleife, Rekursion)

Ziel ist, dass jede Kontrollstruktur einen wohldefinierten Einstiegs- und Austiegspunkt hat. Goto, ein Gegenpol zur strukturierten Programmierung, wird heute fast gar nicht mehr verwendet. Auch funktionale und logikorientierte Programmierung nutzen eine Variante der strukturierten Programmierung.

Seiteneffekte und Querverbindungen

Bei imperativer Programmierung werden Programmfortschritte über Seiteneffekte erzielt (z.B. Variablenzuweisungen, Ein- und Ausgaben). Auswirkungen versteckter Seiteneffekte und entsprechender Querverbindungen sind in der Praxis oft gravierend.

Die deklarative Programmierung strebt referentielle Transparenz als Eigenschaft aller Ausdrücke an. Ein Ausdruck ist referentiell transparent, wenn er durch seinen Wert ersetzt werden kann, ohne die Semantik des Programms dadurch zu ändern, z.B:

  • 3 + 4 → 7
  • f(x) + f(x) → 2 * f(x)

Wo es einfach geht setzt man auch in der imperativen Programmierung auf referentielle Transparenz (z.B. primitive Operationen wie +, -, * und /).

In neueren funktionalen Sprachen erlaubt man Ein- und Ausgaben nur gut sichtbar ganz oben in der Aufrufhierarchie und verbannt sie aus allen anderen Funktionen.

In der objektorientierten Programmierung, kann man Querverbindungen überschaubar halten, indem man sie lokal auf einzelne Objekte beschränkt.

First-Class-Entities

In funktionalen Sprachen sind Funktionen First-Class-Entities. Das bedeutet, sie können wie normale Daten verwendet werden (als Argument übergeben werden, von einer Funktion zurückgegeben werden, in Variablen abgelegt werden).

First-Class-Entities sind häufig viel komplizierter als vergleichbare Konzepte, weil die uneingeschränkte Verwendbarkeit eine große Zahl an zu berücksichtigenden Sonderfällen nach sich zieht.

Aber Funktionen höherer Ordnung (also Funktionen mit Funktionen als Parametern) sind wie Kontrollstrukturen verwendbar und geben dem λ-Kalkül seine Ausdrucksstärke.

Ein häufiger Gebrauch von Funktionen höherer Ordnung führt zu dem Paradigma, der applikativen Programmierung, bei der man quasi Schablonen von Programmteilen schreibt, die dann durch Übergabe von Funktionen (zum Füllen der Lücken in den Schablonen) ausführbar werden.

Programmorganisation[Bearbeiten | Quelltext bearbeiten]

Das wesentliche Ziel ist die Modularisierung von Programmen. Sie soll so erfolgen, dass größtmögliche Flexibilität und Wartbarkeit über einen langen Zeitraum erzielt wird.

Modularisierung[Bearbeiten | Quelltext bearbeiten]

Wir zerlegen das Programm in einzelne Modularisierungseinheiten, die nur lose voneinander abhängen und daher relativ leicht gegeneinander austauschbar sind.

Modul
Eine Übersetzungseinheit, also die Einheit, die ein Compiler in einem Stück bearbeitet, z.B. eine Klasse in Java. Getrennt voneinander übersetzte Module werden von einem Linker oder erst zur Laufzeit zum ausführbaren Programm verbunden.
Objekt
Im Gegensatz zu Modulen sind Objekte keine Übersetzungseinheiten und werden erst zur Laufzeit erzeugt.
Klasse
Eine Klasse wird häufig als Schablone für die Erzeugung neuer Objekte beschrieben.
Komponente
Eine Komponente ist ein eigenständiges Stück Software, das in ein Programm eingebunden wird. Für sich alleine ist eine Komponente nicht lauffähig, da sie die Existenz anderer Komponenten voraussetzt und deren Dienste in Anspruch nimmt.
Namensraum
Jede oben angesprochene Modularisierungseinheit bildet einen eigenen Namensraum und kann damit Namenskonflikte gut abfedern. Das gilt jedoch nicht für globale Namen, die außerhalb der Modularisierungseinheiten stehen, etwa für die Namen von Modulen, Klassen und Komponenten.

Parametrisierung[Bearbeiten | Quelltext bearbeiten]

Unter Parametrisierung versteht man im weitesten Sinne, dass man in den Modularisierungseinheiten Lücken lässt, die man erst später füllt.

Am einfachsten ist das Befüllen zur Laufzeit, wobei die Daten First-Class-Entities sind und die Lücken durch einfache Variablen dargestellt werden.

Man kann die Werte auf unterschiedliche Weise zu den Variablen bringen, z.B:

Konstruktor
Beim Erzeugen eines Objekts wird ein Konstruktor ausgeführt, der die Objektvariablen initialisiert. Das ist die häufigste und einfachste Form der Parametrisierung, nicht nur in objektorientierten Sprachen.
Initialisierungsmethode
Wenn Konstruktoren nicht verwendbar sind, beispielsweise wenn Objekte durch Kopieren erzeugt werden oder zwei zu erzeugende Objekte voneinander abhängen.
Zentrale Ablage
Man kann Werte an zentralen Stellen (z.B globalen Variablen oder Konstanten) ablegen, von wo sie bei der Objekterzeugung / Verwendung abgeholt werden. Mit letzterer Variante ist diese Technik auch für statische Modularisierungseinheiten verwendbar, die bereits zur Übersetzungszeit feststehen.

Diese Techniken eignen sich auch zur Dependency-Injection. Dabei überträgt man die Verantwortung für das Erzeugen und Initialisieren von Objekten an eine zentrale Stelle (z.B. eine Klasse), von der aus man die Abhängigkeiten zwischen den Objekten überblicken und steuern kann.

Generizität
Unter Generizität versteht man eine Form der Parametrisierung, bei der Lücken zumindest konzeptuell bereits zur Übersetzungszeit befüllt werden.
Annotationen
Annotationen sind optionale Parameter, die man zu unterschiedlichsten Sprachkonstrukten hinzufügen kann.
Aspekte
Ein Aspekt spezifiziert gewisse Punkte im Programm und was an diesen Stellen passieren soll. Ein Aspect Weaver modifiziert das Programm entsprechend den Aspekten. (z.B.: Logging, Debugging)

Parametrisierung steigert zwar die Flexibilität von Modularisierungseinheiten, aber wenn die Lücken sich ändern, dann muss sich auch das ändern, was zum Befüllen der Lücken verwendet wird. Solche notwendigen Änderungen behindern die Wartung gewaltig und sind bei weitverbreiteten Modularisierungseinheiten kaum durchführbar.

Ersetzbarkeit[Bearbeiten | Quelltext bearbeiten]

Eine Möglichkeit zur praxistauglichen nachträglichen Änderung von Modularisierungseinheiten verspricht die Ersetzbarkeit: Eine Modularisierungseinheit ist durch eine andere Modularisierungseinheit ersetzbar, wenn der Austausch keinerlei Änderungen an Stellen nach sich zieht, an denen sie verwendet wird.

Ersetzbarkeit benötigt klar definierten Schnittstellen, welche auf verschiedene Weisen spezifiziert werden können:

Signatur
In der einfachsten Form spezifiziert eine Schnittstelle nur, welche Inhalte der Modularisierungseinheit von außen zugreifbar sind. Wenn man sich nur auf Signaturen verlässt, kommt es leicht zu Irrtümern.
Abstraktion realer Welt
Durch zusätzliche Beschreibung anhand von Abstraktionen der realen Welt werden Irrtümer unwahrscheinlicher, können aber immer noch auftreten weil der Ansatz auf Intuition beruht.
Zusicherungen
Um Fehler auszuschließen ist eine genaue Beschreibung der erlaubten Erwartungen an eine Modularisierungseinheit nötig (Design by Contract).
Überprüfbare Protokolle
In jüngerer Zeit wurden Techniken entwickelt, die formale Beschreibungen erlaubter Erwartungen auf eine Weise ermöglichen, dass bereits der Compiler deren Konsistenz überprüfen kann.

In der objektorientierten Programmierung steht die Ersetzbarkeit ganz zentral im Mittelpunkt.

Typisierung[Bearbeiten | Quelltext bearbeiten]

Praktisch alle aktuellen Programmiersprachen verwenden Typen. Viele Operationen sind nur für Werte bestimmter Typen definiert.

Typprüfungen
Typen können zur Laufzeit (dynamisch) oder beim Compilieren (statisch) überprüft werden. Explizite Typen erleichtern das Lesen und Verstehen von Programmen. Compiler können viele Typen herleiten (Typinferenz).
Typen und Entscheidungsprozesse
  • Einiges ist bereits in der Sprachdefinition / Implementierung festgelegt.
  • Zum Zeitpunkt der Erstellung von Übersetzungseinheiten werden die meisten wichtigen Entscheidungen getroffen, auf die man beim Programmieren Einfluss hat.
  • Manche wichtigen Entscheidungen werden durch Parametrisierung erst bei der Einbindung vorhandener Module, Klassen oder Komponenten getroffen.
  • Vom Compiler getroffene Entscheidungen sind eher von untergeordneter Bedeutung und betreffen meist nur Optimierungen.
  • Zur Laufzeit kann man die Initialisierungsphase von der eigentlichen Programmausführung unterscheiden.
Planbarkeit
Wenn man weiß, dass eine Variable vom Typ int ist, braucht man kaum mehr Überlegungen darüber anzustellen, welche Werte in der Variablen enthalten sein könnten. Um sich auf einen Typ festzulegen muss man voraussehen (also planen), wie bestimmte Programmteile im fertigen Programm verwendet werden.
abstrakte Datentypen
Der Begriff abstrakter Datentyp bezieht sich in erster Linie auf die Trennung der Innenansicht von der Außenansicht einer Modularisierungseinheit (Data-Hiding) zusammen mit Kapselung.
Man kann die Signatur der Modularisierungseinheit als strukturellen Typ betrachten. Unterschiedliche Modularisierungseinheiten können aber auch zufällig die gleiche Signatur haben. Die meisten Programmiersprachen verwenden nominale Typen, welche neben der Signatur auch einen eineutigen Namen haben.
Untertypen
Sind durch das Ersetzbarkeitsprinzip definiert.
Generizität
F-gebundene Generizität nutzt Untertypbeziehungen zur Beschreibung von Einschränkungen und wird z.B. in Java eingesetzt. Higher-Order-Subtyping auch Matching beschreibt die Einschränkungen über Untertypen-ähnliche Beziehungen (wird z.B. in Haskell verwendet).
rekursive Datenstrukturen
Der einzig sinnvolle Weg zur Beschreibung unbeschränkter Strukturen führt über eine induktive Konstruktion.

Objektorientierte Programmierung[Bearbeiten | Quelltext bearbeiten]

Basiskonzepte[Bearbeiten | Quelltext bearbeiten]

Jedes Objekt besitzt folgende Eigenschaften:

Identität
Zustand
Verhalten

Polymorphismus[Bearbeiten | Quelltext bearbeiten]

In einer objektorientierten Sprache hat eine Variable (oder ein formaler Parameter) gleichzeitig folgende Typen:

Deklarierter Typ
Der Typ, mit dem die Variable deklariert wurde.
Dynamischer Typ
Der spezifischste Typ, des in der Variablen gespeicherten Werts.
Statischer Typ
Wird vom Compiler (statisch) ermittelt und liegt irgendwo zwischen deklariertem und dynamischem Typ.


Vorgehensweisen in der Programmierung[Bearbeiten | Quelltext bearbeiten]

Faktorisierung
Die Zerlegung eines Programms in Einheiten mit zusammengehörigen Eigenschaften.
Entwicklungsprozesse
Verantwortlichkeiten
Die Verantwortlichkeiten einer Klasse können beschrieben werden durch:
  • was ich weiß — Zustand der Objekte
  • was ich mache — Verhalten der Objekte
  • wen ich kenne — sichtbare Objekte, Klassen, etc.
Klassen-Zusammenhalt
Grad der Beziehungen zwischen den Verantwortlichkeiten der Klasse (sollte stark sein).
Objekt-Kopplung
Abhängigkeit der Objekte voneinander (sollte schwach sein).

Untertypen und Vererbung[Bearbeiten | Quelltext bearbeiten]

Ersetzbarkeitsprinzip[Bearbeiten | Quelltext bearbeiten]

Ein Typ U ist Untertyp eines Typs T, wenn jedes Objekt von U überall verwendbar ist, wo ein Objekt von T erwartet wird.


Strukturelle Untertypbeziehungen

Alle Untertypbeziehungen sind stets reflexiv, transitiv, antisymmetrisch.

Kovarianz
Der deklarierte Typ eines Elements im Untertyp ist ein Untertyp des deklarierten Typs des entsprechenden Elements im Obertyp.
Kontravarianz
Der deklarierte Typ eines Elements im Untertyp ist ein Obertyp des deklarierten Typs des Elements im Obertyp.
Invarianz
Der deklarierte Typ eines Elements im Untertyp ist äquivalent zum deklarierten Typ des entsprechenden Elements im Obertyp.


Dynamisches Binden

Ersetzbarkeit und Objektverhalten[Bearbeiten | Quelltext bearbeiten]

Es gibt verschiedene Arten von Zusicherungen:

Vorbedingung
Müssen von Clients erfüllt werden. Beschreiben haupsächlich, welche Eigenschaften die Argumente von Methoden erfüllen müssen.
Nachbedingung
Müssen vom Server erfüllt werden. Beschreibt Methodenergebnis / Änderungen / Eigenschaften des Objektzustands.
Invariante
Grundsätzlich ist der Server verantwortlich, bei direktem Schreibzugriff allerdings auch Clients.
History-Constraint
Server-kontrolliert: ähneln Invarianten (z.B. Wert einer Variable darf nur größer werden)
Client-kontrolliert: (z.B. initialize darf nur einmal aufgerufen werden)


Abstrakte Klassen
Sind eher stabil als konkrete.

Vererbung versus Ersetzbarkeit[Bearbeiten | Quelltext bearbeiten]

Es gibt unter anderem folgende drei Arten von Beziehungen zwischen Klassen:

Untertypen
Vererbung
Is-a-Beziehung
Vererbung und Codewiederverwendung
Fehlervermeidung

Exkurs: Klassen und Vererbung in Java[Bearbeiten | Quelltext bearbeiten]

Klassen in Java
Vererbung und Interfaces in Java
Pakete und Zugriffskontrolle in Java

Generizität und Ad-hoc-Polymorphismus[Bearbeiten | Quelltext bearbeiten]

Generizität[Bearbeiten | Quelltext bearbeiten]

Generische Klassen, Typen und Methoden enthalten Parameter, für die Typen eingesetzt werden. Andere Arten generischer Parameter unterstützt Java nicht. Daher nennt man generische Parameter einfach Typparameter.

Verwendung von Generizität im Allgemeinen[Bearbeiten | Quelltext bearbeiten]

Für die Übersetzung generischer Klassen und Methoden in ausführbaren Code gibt es im Großen und Ganzen zwei Möglichkeiten, die homogene und die heterogene Übersetzung. In Java wird die homogene Übersetzung verwendet. Dabei wird jede Klasse (egal ob generisch oder nicht-generische), in genau eine Klasse mit JVM-Code übersetzt.

Bei der heterogenen Übersetzung wird für jede Verwendung einer generischen Klasse oder Methode mit anderen Typparametern eigener übersetzter Code erzeugt.

Typabfragen und Typumwandlungen[Bearbeiten | Quelltext bearbeiten]

Verwendung dynamischer Typinformation[Bearbeiten | Quelltext bearbeiten]

Typumwandlungen und Generizität[Bearbeiten | Quelltext bearbeiten]

Kovariante Probleme[Bearbeiten | Quelltext bearbeiten]

Binäre Methoden

Überladen versus Multimethoden[Bearbeiten | Quelltext bearbeiten]

Kreuz und quer[Bearbeiten | Quelltext bearbeiten]

Ausnahmebehandlung[Bearbeiten | Quelltext bearbeiten]

Nebenläufige Programmierung[Bearbeiten | Quelltext bearbeiten]

Annotationen und Reflexion[Bearbeiten | Quelltext bearbeiten]

Aspektorientierte Programmierung[Bearbeiten | Quelltext bearbeiten]

In der objektorientierten Programmierung funktioniert die Aufteilung gut für die Kernfunktionalitäten (Core-Concerns), die sich leicht in eine Klasse packen lassen. Oft gibt es aber Anforderungen, die alle Bereiche eines Programms betreffen (Cross-Cutting-Concerns, Querschnittsfunktionalitäten).

Join-Point
Ein Join-Point ist eine identifizierbare Stelle während einer Programmausführung, z.B. der Aufruf einer Methode oder der Zugriff auf ein Objekt.
Pointcut
Ein Pointcut ist ein Programmkonstrukt, das einen Join-Point auswählt und kontextabhängige Information dazu sammelt, z.B. die Argumente eines Methodenaufrufs oder das Zielobjekt.
Advice
Ein Advice ist jener Programmcode, der vor (before()), um (around()) oder nach (after()) dem Join-Point ausgeführt wird.
Aspect
Ein Aspekt ist wie eine Klasse das zentrale Element in AspectJ. Er enthält alle Deklarationen, Methoden, Pointcuts und Advices.


Software-Entwurfsmuster[Bearbeiten | Quelltext bearbeiten]

Entwurfsmuster (Design-Patterns) dienen der Wiederverwendung kollektiver Erfahrung. Wir wollen exemplarisch einige häufig verwendete Entwurfsmuster betrachten.

Grundsätzliches[Bearbeiten | Quelltext bearbeiten]

Jedes Entwurfsmuster besteht hauptsächlich aus diesen vier Elementen:

  • Name
  • Problemstellung
  • Lösung
  • Konsequenzen

Erzeugende Entwurfsmuster[Bearbeiten | Quelltext bearbeiten]

Factory Method[Bearbeiten | Quelltext bearbeiten]

Der Zweck einer Factory-Method (auch Virtual-Constructor) ist die Definition einer Schnittstelle für die Objekterzeugung, wobei Unterklassen entscheiden, von welcher Klasse die erzeugten Objekte sein sollen. Die tatsächliche Erzeugung der Objekte wird in Unterklassen verschoben.


Anzahl der benötigten Klassen: steigt
Anzahl der benötigten Objekte: bleibt gleich

Prototype[Bearbeiten | Quelltext bearbeiten]

Das Entwurfsmuster Prototype dient dazu, die Art eines neu zu erzeugenden Objekts durch ein Prototyp-Objekt zu spezifizieren. Neue Objekte werden durch Kopieren dieses Prototyps erzeugt.

Object.clone() erzeugt flache Kopien von Objekten (d.h. der Wert jeder Variable ist identisch). Wenn die Werte nicht identisch, sondern nur gleich sein sollen (tiefe Kopie), muss clone überschrieben werden.


Anzahl der benötigten Klassen: bleibt gleich
Anzahl der benötigten Objekte: steigt evtl. leicht

Singleton[Bearbeiten | Quelltext bearbeiten]

Das Entwurfsmuster Singleton sichert zu, dass eine Klasse nur eine Instanz hat und erlaubt globalen Zugriff auf dieses Objekt.


Anzahl der benötigten Klassen: bleibt gleich
Anzahl der benötigten Objekte: sinkt

Entwurfsmuster für Struktur und Verhalten[Bearbeiten | Quelltext bearbeiten]

Decorator[Bearbeiten | Quelltext bearbeiten]

Das Entwurfsmuster Decorator, auch Wrapper genannt, gibt Objekten dynamisch zusätzliche Verantwortlichkeiten. Decorators stellen eine flexible Alternative zur Vererbung bereit.


Anzahl der benötigten Klassen: steigt
Anzahl der benötigten Objekte: steigt


Proxy[Bearbeiten | Quelltext bearbeiten]

Ein Proxy, auch Surrogate genannt, stellt einen Platzhalter für ein anderes Objekt dar und kontrolliert Zugriffe darauf.

  • Remote-Proxies: Platzhalter für Objekte, die in anderen Namensräumen existieren
  • Virtual-Proxies: erzeugen Objekte bei Bedarf
  • Protection-Proxies: kontrollieren Zugriffe auf Objekte
  • Smart-Referenzes: können bei Zugriffen zusätzliche Aktionen ausführen


Anzahl der benötigten Klassen: steigt leicht
Anzahl der benötigten Objekte: steigt evtl. leicht

Template-Method[Bearbeiten | Quelltext bearbeiten]

Eine Template-Method definiert das Grundgerüst eines Algorithmus in einer Operation, überlässt die Implementierung einiger Schritte aber einer Unterklasse. Template-Methods erlauben einer Unterklasse, bestimmte Schritte zu überschreiben, ohne die Struktur des Algorithmus zu ändern.