Fassade-Pattern
Das Fassade-Pattern bietet dem Zugreifer cController das Interface icCounter an, welches bereits Daten und Funktionsimplementierungen enthält.
Was sich hinter der Fassade icCounter verbirgt, ist für den Zugreifer nicht wissenswert und auch nicht sichtbar.
Wie bei den vorherigen Beispielen enthält diese Implementierung keine virtuellen Funktionen oder Funktionszeiger. Es ergibt sich nur eine Abhängigkeit (Include-Pfad) und damit eine geringe / lose Kopplung. Das Interface (Fassade) enthält ein Zähler- und ein Grenzwert-Prüfobjekt, die gemeinsam die komplette Funktionalität der Fassade realisieren.
Bild 10: Fassade-Pattern – Interfacerealisierung
Interfaceklasse mit rein virtuellen Funktionen
Ein klassischer, aus der objektorientierten Welt stammender Interfaceansatz ist die Verwendung von rein virtuellen Funktionen (nur Deklarationen ohne Implementierungen) im Interface. Des Weiteren enthält das Interface icCounter keine Daten.
Der Zugriff auf das Interface erfolgt im cController durch einen Zeiger / eine Referenz vom Typ des Interfaces. Dieser Zeiger / diese Referenz muss später auf ein Objekt der Interface-realisierenden Klassen zeigen.
In C gibt es keine virtuellen Funktionen, daher müssen dort die Mechanismen des C++ Compilers manuell mit Hilfe von Funktionszeigertabellen nachgebildet werden.
Was sich hinter dem Interface verbirgt, ist für den Zugreifer cController zunächst nicht wissenswert und auch nicht sichtbar. Erst beim Initialisieren der Zeiger / Referenzen müssen konkrete Objekte von cUpCounter und cDownCounter vorhanden sein.
Bild 11: Virtual Interface – Interfacerealisierung
Interfaceklasse mit nicht nur rein virtuellen Funktionen
Diese Implementierungsvariante basiert auf dem C++ Idiom Non-Virtual Interface (NVI). Bei der Implementierung von rein virtuellen Interfaces ergeben sich im Falle mehrerer Implementierungen typischerweise redundante Programmcode-Anteile.
Das Idiom Non-Virtual Interface implementiert diesen gemeinsamen Code bereits in der Interface-Funktion. Nur die kleinen varianten Anteile der typspezifischen Implementierung sind im Interface als rein virtuelle Funktionen deklariert (isInRange()) und (count_raw()) und bereits in anderen implementierten Funktionen (count()) aufgerufen.
Nur die beiden typspezifischen virtuellen Funktionen isInRange() und count_raw() sind jeweils in den Klassen cUpCpounter und cDownCounter individuell implementiert.
Bild 12: Non-Virtual Interface
In den Beispielen mit virtuellen Funktionen in Interfaces oder/und Klassen führt das Ergebnis auf dem Taget zu einer dynamischen Bindung. Dadurch entstehen verschieden Aufwände, die aber im Vergleich zum Nutzen in dem meisten Fällen vernachlässigbar sind:
1. Programmspeicher (und Compilezeit)
Zur Compilezeit erzeugt die Toolkette zu jeder Klasse, die eine oder mehrere virtuelle Funktionen deklariert oder/und implementiert, eine VMT (Virtual Method Table). Diese legt der Linker / Lokator üblicherweise in den Programmspeicher. Die VMT enthält die Funktionseinsprung-Adressen zu den klassenspezifischen Funktionsimplementierungen.
2. Datenspeicher und Laufzeit
Objekte, instanziiert aus einer Klasse mit virtuellen Funktionen, enthalten als zusätzliches, erstes Attribut die Einsprung-Adresse in dessen Klassen-VMT. Dieses Attribut fügt der Compiler automatisch hinzu, und der Konstruktor initialisiert es ebenfalls automatisch.
3. Laufzeit
Beim Aufruf einer virtuellen Funktion für ein bestimmtes Objekt über einen Zeiger oder eine Referenz wird über dessen VMT-Einsprung-Adresse die Funktionsadresse aus der VMT gelesen und anschließend zu dieser Funktionsadresse gesprungen (-> eine In-Direktion mehr als beim Aufruf nicht-virtueller Funktionen). Dies ist die Funktionalität der dynamischen Bindung. Sie erlaubt so die Programmierung der dynamischen Polymorphie. Diesen Mechanismus führt die C++ Toolkette automatisch aus. In C ist die dynamische Bindung mit etwas mehr Programmieraufwand manuell nachbildbar.