%% LyX 2.0.1 created this file. For more info, see http://www.lyx.org/. %% Do not edit unless you really know what you are doing. \documentclass[english,ngerman]{article} \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage{graphicx} \makeatletter %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands. \newcommand{\noun}[1]{\textsc{#1}} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands. \newenvironment{lyxcode} {\par\begin{list}{}{ \setlength{\rightmargin}{\leftmargin} \setlength{\listparindent}{0pt}% needed for AMS classes \raggedright \setlength{\itemsep}{0pt} \setlength{\parsep}{0pt} \normalfont\ttfamily}% \item[]} {\end{list}} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% User specified LaTeX commands. \usepackage{multicol} \makeatother \usepackage{babel} \begin{document} \title{Design Pattern und objekt--orientierte Programmierung} \author{Bernd Paysan} \maketitle \begin{abstract} \noun{Stephen Pelc} hat auf der EuroForth ein dickes Fass aufgemacht, und aufgerufen: ,,Standardize OOP now!{}`` In der darauf folgenden Diskussion hat sich gezeigt, dass jeder etwas anderes unter OOP versteht, und sich deshalb keine gemeinsame Praxis herauskristallisieren kann. Dieser Artikel unternimmt den Versuch, da etwas Licht ins Dunkel zu bringen. OOP ist ,,no silver bullet,`` wie \noun{Fred Brooks} schreibt, es löst nicht das Problem, dass Programmieren schwer ist. Es ist aber ein brauchbares Werkzeug für eine Reihe von Problemen, die man anders noch schwerer lösen kann. \end{abstract} \begin{multicols}{2} \section*{Einleitung} Die \noun{Sapir--Whorf}--Hypothese besagt, dass man nicht denken kann, was in der eigenen Sprache nicht ausgedrückt werden kann. Die Hypothese wurde ursprünglich bezüglich natürlicher Sprachen aufgestellt, und ist im Kern ein Konstrukt aus einer Zeit, in der andere Leute kulturelle Unterschiede mit Rassismus erklärt haben% \footnote{Das heißt, die beiden Herren wollten damit erklären, ,,warum Neger doof sind,`` statt erst mal herauszufinden, ob das überhaupt so ist, oder vielleicht gar nicht erklärt werden muss\ldots{}% }. Sie gilt heute als widerlegt --- in jeder natürlichen Sprache kann man alles ausdrücken, es ist nur manchmal umständlicher als in anderen Sprachen. Und vor allem ist es schwieriger, sich in einer Sprache auszudrücken, die man nur mäßig beherrscht --- das ist dann auch der Grund, warum die beiden Herren zu ihrer Hypothese gekommen sind. Für Programmiersprachen gilt diese Kritik aber nicht --- Programmiersprachen sind künstlich, sie eröffnen tatsächlich einen Horizont zu einer neuen Denkweise, nämlich wie man einen Computer programmieren kann. Das ist etwas grundsätzlich anderes als sich mit Menschen zu unterhalten, weshalb Computersprachen auch grundsätzlich anders strukturiert sind als natürliche Sprachen. Die Denkweisen, die man mit einer Computersprache erlernt, sind neu, und eröffnen deshalb tatsächlich neue Horizonte. Die Konzepte, wie man sich in einer Computersprache ausdrückt, um gewisse Probleme elegant zu lösen, nennt man ,,Design Pattern.`` Das war so Mitte der 90er--Jahre der Hype schlechthin, und ich habe mir damals natürlich das Buch der ,,Gang of Four`` \cite{gof} aus der Uni--Bibliothek ausgeliehen und gelesen. Ich habe auch das Buch von \noun{Christopher Alexander} \cite{ca} gelesen, ein Architekt, der das Konzept der Design Pattern im Zusammenhang mit Architektur entdeckt und beschrieben hat (schon in den 70ern), und dessen Buch Wegbereiter für diese Welle war --- und der sehr überrascht war, dass plötzlich eine Menge Informatiker sein Buch gelesen haben. Design Pattern werden durch die Möglichkeit einer Sprache vorgegeben --- das Design--Pattern--Buch, wenn man es so nennen will, für die natürlichen Möglichkeiten, die Forth von sich aus mitbringt, ist ,,Thinking Forth`` von \noun{Leo Brodie} \cite{tf}. Forth ist eine sehr flexible und dynamische Sprache, man kann es in alle möglichen Richtungen erweitern. Fügt man OOP zu Forth hinzu, so fügt man ein anderes Programmier--Paradigma hinzu, denn Forth selbst hat keine OOP--Fähigkeiten. Ich habe früher öfter gehört ,,Forth braucht kein OOP, wir haben ja CREATE DOES>``. Das ist so etwa, wie wenn man sagt ,,ich brauche keine Freundin, ich habe ja ein Fahrrad.`` Man kann mit CREATE DOES> ein OOP--System implementieren, aber es selbst \emph{ist} kein OOP. Genauso kann man mit dem Fahrrad zu seiner Freundin fahren, aber es \emph{ist} keine Freundin. Die Philosophen bezeichnen so eine Antwort als ,,Kategoriefehler.`` Es ist aber eine ganz normale Tatsache, dass man Dinge, die man nicht kennt, auch nicht vermisst. Insofern ist es nicht verwunderlich, dass der typische Forth--Programmierer mit CREATE DOES> und Fahrrad zufrieden ist ;-). CREATE DOES> ist eher so etwas wie eine Closure, also das Bündeln von Daten mit einer einzigen Funktion. OOP verspricht, komplexe Programme wartbar zu machen. Was wir durch OOP bekommen haben, sind riesige, schwerfällige Programme, die die Gigahertz--CPUs des 21. Jahrhunderts zum Schwitzen bringen, und die Terabyte--Festplatten füllen. Das ist aber kein Widerspruch, denn Programmierer neigen dazu, ihre Programme wachsen zu lassen, bis sie die Grenzen der Wartbarkeit erreicht haben. Im Umkehrschluss heißt das natürlich, dass Forth--Programme nur deshalb so kompakt und schnell sind, weil größere Programme in Forth einfach nicht mehr wartbar sind, und die Programmierer von sich aus darauf verzichten, sie mit Features zu überfrachten. Forth--Programmierer haben natürlich einen Ausweg aus dem Dilemma gefunden: es ist die Unzahl verschiedener, untereinander nicht kompatibler OOP--Erweiterungen, die auch OOP--basierte Forth--Programme ab einer gewissen Größe unwartbar machen (zumindest für Dritte, denn ab einer gewissen Größe braucht man Teams von Programmierern, um Programme weiter wachsen zu lassen), also dafür sorgen, dass sie klein und schnell bleiben. \noun{Stephen Pelc} hat dieses Problem auf der EuroForth angesprochen, und gefordert ,,Standardize OOP Now!{}`` Das Thema führte dann am Ende der Konferenz noch zu zwei Workshops und nach der Konferenz zu weiterer Diskussion per Mail, über die ich hier ausführlicher berichte: \noun{Andrew Haley} bringt dazu zwei Sachen ins Spiel: Zum einen die ,,Design Patterns,`` deren Implementierung mit einem brauchbaren OOP möglich sein muss, zum anderen das Metaobject Protocol \cite{mop}, mit dem die Common--Lisp--Leute ihr sehr ähnliches Problem gelöst haben (auch dafür gab es einen ganzen Sack voller untereinander inkompatibler OOP--Erweiterungen), und ein Paper, das einen interessanten Ansatz zur Verallgemeinerung bringt. Stephen gefällt Forth meets Smalltalk von \noun{Doug Hoffman,} er möchte das als Basis verwenden. Stephen verkennt mit seinem Vorschlag, dass gerade \noun{Doug Hoffman} zentrales Teil des Problems ist (auch wenn FMS nicht mehr so grauenhaft ist wie der Vorgänger Neon). Die Diskussion darüber ging dann auch noch per E--Mail nach dem Meeting weiter, wobei \noun{Klaus Schleisiek Manfred Mahlows} OOP als ,,Lösung`` anpries, und seine völlige Unkenntnis über Late Binding und vtables offenbarte. Genau das ist meiner Meinung nach das zentrale Kern--Problem von OOP in Forth: Zu viele Leute verstehen darunter lediglich Strukturen mit einem Namensraum für Funktionen. Nein, OOP ist ein ganzer Sack verschiedener Konzepte, weshalb es eben \emph{nicht} ausreicht, mal eben 10\% davon zu implementieren, denn die 10\%--Lösungen, die alle jeweils unterschiedliche 10\% berücksichtigen, die haben wir ja schon. Leon Wagner bestätigt diese These, denn seiner Meinung nach ist SWOOP völlig ausreichend, auch wenn es bei Late Binding ebenso unbrauchbar ist. Das Argument, das man immer hört, ist ,,wir/unsere Kunden schreiben damit ernsthafte Programme.`` Nein, das ist nicht die richtige Antwort, weil sich die Situation derzeit wohl so darstellt, dass jedes System eben seine eigenen Stärken und Schwächen hat, und deshalb für die zugehörige Anwendung maßgeschneidert ist --- damit aber nicht durch ein beliebiges anderes ersetzt werden kann. Ich habe mich jedenfalls mit ein paar Büchern eingedeckt, und denke jetzt erst mal darüber nach, wie man das Problem richtig löst. Code haben wir genug geschrieben, es ist jetzt Zeit, den wieder wegzuwerfen, und das inzwischen Gelernte anzuwenden --- oder, wenn da Defizite vorhanden sind, Neues zu lernen. Man muss sich auch im Klaren sein: Die bisher benutzten Systeme werden nicht verschwinden, ihre Syntax und Semantik wird erhalten bleiben. Andrews Vorschlag, ein erweiter- und modifizierbares Grundgerüst zu nehmen, auf dem man dann verschiedene Syntax und Semantik aufbauen kann, scheint mir der einzige brauchbare Ansatz zu sein. Solange ich aber von meinen Mit--Forthern beim Wort ,,vtable`` immer ein ,,das ist mir zu komplex`` höre, habe ich Bedenken --- das dabei entstehende System wird dann bestimmt als eierlegende Wollmilchsau angesehen, und der Forth--Philosophie widersprechend, wird also auch nicht akzeptiert. \section{Instanz--Variablen, Methoden, Vererbung und Polymorphismus} Die drei Grundkonzepte von OOP sind Instanz--Variablen, Polymorphismus und Vererbung. Der Satz ,,Ein Objekt besteht aus Variablen und Methoden, die auf diese Variablen zugreifen`` scheint überall angekommen zu sein, das ist der gemeinsame Minimal--Konsens, es ist das Verständnis einer um Funktionen erweiterten Datenstruktur, es ist aber nur ein Teil dessen, was OOP ausmacht. Ebenso Konsens scheint über die Fähigkeit zur Vererbung zu bestehen, also dass man diese Strukturen und Methoden nachträglich erweitern kann, wodurch eine Unterklasse entsteht, der zusätzliche Instanz--Variablen hinzugefügt werden können, und die die Methoden der Basisklasse überschreiben kann, wobei ,,überschreiben`` bedeutet, dass die veränderten Methoden nur für die Unterklasse gelten. Die in der Basisklasse verankerten und damit allen Unterklassen gemeinsamen Methoden nennt man ,,Interface,`` das ist eine Kollektion von \foreignlanguage{english}{Messages}, die man den Objekten schicken kann (man sendet eine \foreignlanguage{english}{Message} an ein Objekt, aufgerufen wird dann eine Methode --- \foreignlanguage{english}{Message} und Methode sind zwei verschiedene Sachen, der Operator, um eine \foreignlanguage{english}{Message} in eine Methode zu überführen, und damit ausführbar zu machen, heißt ,,bind``). Damit hat man schon ein Werkzeug, das über die natürlichen Fähigkeiten von Forth hinausgeht. \noun{Klaus Schleisiek} hat einmal einen Vortrag über die verschiedenen Adressräume von Microcontrollern und DSPs gehalten, und die Forth--übliche Lösung führt zu einer Explosion der Wörternamen und unwartbaren Programmen --- weil man sich bei jeder Adresse merken muss, ob die jetzt im Flash, im EEPROM, im internen RAM, im X-- oder Y--RAM eines DSPs, im Code--Memory einer \foreignlanguage{english}{Harvard}--Architektur, im externen RAM, im IO--Bereich, in einem über SPI oder I$^{2}$C adressierten externen IO--Baustein oder wo auch immer ist. Führt man hier ein bisschen OOP ein, kann man jeder Variablen die Methoden @ und ! zuordnen, und diese jeweils für die verschiedenen Speicherbereiche unterschiedlich implementieren. Die so definierten Variablen ,,wissen,`` wie sie auf @ und ! zu reagieren haben, der Programmierer muss das nicht mehr selbst machen. Die Variablen sind im Quelltext durch ihren Namen referenziert, ihre Klasse ist also bekannt und die Methoden können früh gebunden werden. Wartbarkeit ist wieder erreicht, durch das Prinzip ,,\foreignlanguage{english}{information hiding,}`` denn wo die Variable jetzt liegt, und wie sie angesprochen werden muss, ist ihr Privat--Wissen, dem Programmierer, der sie verwendet, kann das jetzt egal sein. Hier spricht man von \foreignlanguage{english}{,,interfaces}`` (das sind in diesem Beispiel @ und !) und ,,\foreignlanguage{english}{implementations}`` (das sind die jeweiligen Umsetzungen für unterschiedliche Adressräume). Der Code dazu sieht ungefähr so aus (Syntax: Bernd\-OOF, ich nehme an, dass die forthige Lösung für den Zugriff auf Flash und SPI schon implementiert ist, und nur noch der OOP--Wrapper darum gelegt wird, um das ganze wartbar zu machen). Zur Konflikt--Bereinigung zwischen den Methoden @ und ! und den Forth--Wörtern @ und ! gibt es das Präfix F, das explizit im Forth--Wörterbuch sucht. Man könnte auch dem Vorschlag von \noun{Doug Hoffman} folgen, und allen Messages einen : hinzufügen. Das ist aber ein Konzept aus einer anderen Sprache. In Forth sind Messages auch einfach nur Forth--Wörter. \begin{lyxcode} object~class~memvar ~~~cell~var~addr ~~~method~@ ~~~method~! how: ~~~:~init~(~addr~-{}-~)~addr~F~!~; ~~~:~!~(~n~-{}-~)~~addr~F~@~F~!~; ~~~:~@~(~-{}-~n~)~~addr~F~@~F~@~; class; \end{lyxcode} Ich verwende memvar im Folgenden auch gleich als Basisklasse, die hier nicht abstrakt ist, sondern konkret --- mit der einfachst möglichen Implementierung. \begin{lyxcode} memvar~class~flashvar how: ~~~:~!~(~n~-{}-~)~~addr~F~@~flash!~; class; \end{lyxcode} Das Flash beschreiben funktioniert anders, lesen kann man es aber wie normalen Speicher. \begin{lyxcode} memvar~class~spivar how: ~~~:~!~(~n~-{}-~)~~addr~F~@~spi!~; ~~~:~@~(~-{}-~n~)~~addr~F~@~spi@~; class; \end{lyxcode} SPI liest und schreibt man über einen seriellen Datenbus. Jetzt brauchen wir noch etwas Test--Code: \begin{lyxcode} here~0~,~memvar~:~a \$20~~~~~~spivar~:~b \$FFEA~~flashvar~:~c \$1234~a~! a~@~c~! c~@~b~! b~@~hex.~(~sollte~\$1234~geben~) \end{lyxcode} Die meisten mir bekannten Forth--OOP--Systeme implementieren das jetzt als Early Binding. Das, was daraus entsteht, ist kein OOP, sondern ,,operator overloading,`` der gleiche Operator auf verschiedene Datentypen angewandt wird unterschiedlich implementiert --- die Datentypen sind ihrerseits aber statisch, also von vornherein bekannt. Das ist natürlich noch nicht sonderlich flexibel --- normale Variablen kann man einfach auf den Stack legen, und generische Funktionen können einfach @ und ! darauf anwenden, um etwa +! zu implementieren: \begin{lyxcode} :~+!~(~n~addr~-{}-~)~~dup~>r~@~+~r>~!~; \end{lyxcode} Man kann nun natürlich einfach +! als Methode für jeden Speicherbereich implementieren, das +! darf auch ruhig jedes Mal gleich aussehen, aber wenn @ und ! früh gebunden werden, muss der Code jedes Mal neu übersetzt werden --- für jede Klasse einmal. Einfacher wäre es, @ und ! würden spät gebunden, also erst zur Laufzeit (in obigem Beispiel ist das so, weil Bernd\-OOF tatsächlich per Default spät bindet). Diese Eigenschaft nennt man ,,Polymorphismus``, Unterklassen haben unterschiedliche Gestalt, und das Senden der gleichen Message führt zu verschiedenen Aktionen --- wobei dem verwendenden Programm nicht bekannt sein muss, mit welcher Unterklasse es zu tun hat. Wir haben es hier also mit dynamischem Typing zu tun --- der Datentyp (die Klasse) des Objekts ist erst zur Laufzeit bekannt. Das (recht klassische und einfache) Design--Pattern, das hier verwendet wird, um das zu implementieren, ist das der ,,Indirektion.`` Man kann in der Informatik, so heißt es, jedes Problem durch eine zusätzliche Indirektion lösen --- also indem man einen Pointer benutzt, statt den Wert selbst. Die Indirektion, die hier nötig ist, ist die beim Aufruf der Methode. Naheliegend ist es, einfach eine Instanz--Variable für jede Message zu definieren, und dort das xt der Methode zu speichern --- wie bei einem \foreignlanguage{english}{deferred word}. Man kann das so machen, es verschwendet nur Speicher, denn die Methoden sind ja für alle Objekte einer Klasse gleich. Die Lösung dazu ist (wie in der Informatik üblich) eine Indirektion, die als ,,vtable`` oder ,,\foreignlanguage{english}{virtual method table}`` bezeichnet wird. Man gibt also jedem Objekt einer Klasse einen Pointer mit, auf eine Datenstruktur, in der die Methoden einer Klasse gemeinsam verwaltet werden. Üblicherweise ist das ein Array, d.h. jede Methode ist über einen Index schnell und ziemlich direkt zugreifbar, aber man kann da auch Hash--Tables oder Listen verwenden, wenn etwa das Ziel eine Implementierung ist, bei der jedes Objekt jede Message verstehen kann, und damit ein Array als vtable zu viel Speicherplatz kosten würde. Abbildung \ref{fig:Ein-Objekt-der} zeigt, wie das im Speicher aussieht. \begin{figure} \begin{centering} \includegraphics[scale=0.4]{2011-04/vtable} \par\end{centering} \caption{\label{fig:Ein-Objekt-der}Ein Objekt der Klasse memvar und seine vtable} \end{figure} Deshalb, um das nochmal deutlich zu machen: Eine vtable ist deshalb getrennt vom Objekt abgelegt, um Speicherplatz zu sparen. Die entscheidende Indirektion ist nicht der Pointer auf die vtable, sondern der Pointer auf die Methode, die aus einem Forth--Wort, das im Kontext des Objekts aufgerufen wird, eine polymorphe Message macht --- eine Art \foreignlanguage{english}{deferred word}, nur eben nicht als Instanz--Variable, sondern für alle Instanzen einer Klasse gemeinsam. Viele objektorientierte Sprachen verstehen unter ,,\foreignlanguage{english}{information hiding}``, dass der Zugriff auf im Objekt intern verwendete Funktionen von außen nicht gestattet ist. Das widerspricht der Forth--Philosophy ,,\foreignlanguage{english}{don't bury your tools}``, und muss zumindest für's Debugging abschaltbar sein. Meiner Meinung nach ist das auch ein Missverständnis, denn wirklich gemeint ist, dass man zur Kommunikation mit anderen Objekten ihre Interfaces, nicht ihre Implementierung verwenden soll. Das sind aber alles Regeln, keine Gesetze; Regeln, deren Befolgung meistens sinnvoll ist, und deren Verletzung begründet werden sollte. In Forth ist die Durchsetzung solcher Regeln durch den Compiler nicht üblich. \section{Design Pattern: Komposition statt Vererbung} Das Meta--Pattern schlechthin bei den Design--Pattern ist, statt Vererbung die Komposition von Objekten zu verwenden. Vererbung ist sinnvoll, um verschiedene Ausformungen einer Klasse zu bekommen; die Komposition von Objekten ist aber flexibler, und verknüpft ganz unterschiedliche Eigenschaften. Vererbung ist ein Kopiervorgang (ungeschlechtlich, wie die Fortpflanzung von Bakterien), Komposition ist im Verhältnis dazu wie Sex --- mit mehreren Teilnehmern, die ihre unterschiedliche Herkunft einbringen. Ich möchte das einmal am Beispiel eines Dialog--Fensters in MINOS erläutern, was damit gemeint ist. Die beiden Relationen, um die es hier geht, werden mit ,,is--a`` und ,,has--a`` bezeichnet. Ein Dialog--Fenster in MINOS \textbf{ist} eine Komponente (das ist die Klasse, von der es abgeleitet wird). Es \textbf{hat} mehrere Sachen: Zum einen ein Display--Objekt, in dem es dargestellt wird (das ist üblicherweise ein Fenster). Zum anderen hat es Widgets, in der Regel eine ganze Menge, die ihrerseits in horizontalen und vertikalen Boxen angeordnet sind. Auch für Widgets gelten derartige Beziehungen: Ein Widget ist z.B. ein Button. Es hat eine (oder mehrere miteinander verkettete) Aktion(en), die ausgeführt werden, wenn man darauf klickt. Und es hat natürlich auch ein Display, in dem es dargestellt wird. Die ,,is--a``--Beziehung wird durch Instantiierung und Vererbung erworben, einmal instantiiert lässt sie sich nicht mehr verändern (ein Button bleibt ein Button). Die ,,has--a``--Beziehung durch Zuweisungen, etwa durch Einhängen in die Liste einer Box. Letzteres erfolgt zur Laufzeit, und kann auch während der Laufzeit geändert werden (es können neue Elemente in eine Box eingehängt werden, oder Elemente daraus gelöscht werden). Das ist sehr dynamisch und sehr flexibel. Das dazu verwendete Konstrukt ist, wie nicht anders zu erwarten, eine Indirektion, ein Pointer auf ein Objekt. Die meisten dieser Design--Pattern funktionieren nur mit late binding, manche brauchen sogar Mehrfachvererbung. Ein umfassendes OOP--System sollte es ermöglichen, alle Pattern auszudrücken; wenn man nur spezielle Anwendungsfälle für einige der Pattern hat, reicht manchmal eine reduzierte Version. Ich liste diese Pattern in der Reihenfolge der Gang--of--Four auf, und erkläre kurz, was damit gemeint ist, sowie welche Fähigkeiten des OOP--Systems nötig sind, um diese Pattern zu implementieren. Da Forth eine sehr dynamische Sprache ist, und die Design--Pattern sich auf C++ und Java beziehen, fehlen Design--Pattern, die man in diesen Sprachen einfach nicht machen kann. Man muss also auch dieses hier als Minimal--Konsens verstehen, und durchaus in Erwägung ziehen, dass man das auch anders (und besser) machen kann. \subsection{Creational Patterns} Diese Pattern haben das Erzeugen von Objekten zum Thema \begin{description} \item [{Abstract~Factory}] Erzeugt verwandte oder voneinander abhängige Objekte, ohne die konkrete Klasse zu spezifizieren. Beispiel: Wir verwenden obige Klassen, um Variablen in verschiedenen Speicherbereichen zu verwalten, wollen aber den Ort unabhängig von den Variablen selbst spezifizieren. Dazu bauen wir uns einen globalen Status, der % entsprechend des aktuell verwendeten Speicherbereichs dem aktuell verwendeten Speicherbereich entsprechend gesetzt ist, und erzeugen in der Abstract Factory immer die passenden Objekte. Wenn unser ADC jetzt vom SPI--Bereich in den I$^{2}$C--Bereich verschoben wird (z.B. weil er beide Interfaces anbietet, der eine Controller aber SPI und der andere I$^{2}$C verwendet), dann ändern wir nur den Bereich, und lassen den Rest des Quellcodes unverändert. Dabei kann es verschiedene Klassen geben, die ausgewählt werden, etwa Klassen für Bytes, für 16-- und 32--Bit--Wörter, die alle gemeinsam abhängig vom gerade gewählten Speicherbereich erzeugt werden können. Das OOP--System muss Objekt--Pointer beherrschen und echten Polymorphismus, weil der Aufrufer der Abstract Factory nicht weiß, was für ein Objekt er bekommt. Es ist hilfreich zur Implementierung, wenn Klassen ebenfalls herumgereicht und als Pointer abgelegt werden können (etwa, um eine Klassenmatrix anzulegen). Im Beispiel oben werden die Objekte während der Compilation erzeugt und in anderen Teilen des Programms direkt als benannte Variable verwendet (und da das Binding nach der Erzeugung der Objekte geschieht, auch ggf. mit Early Binding), das ist etwas, was in C++ und Java so gar nicht geht. \item [{Builder}] Erzeugt komplexe Objekte, etwa zum Konvertieren von einem Dateiformat in ein anderes. Beispiel: Wir wollen ein RTF--Dokument in ASCII oder \LaTeX{} konvertieren. Dazu spezifizieren wir das Ziel, und der Builder erzeugt entweder ein ASCII-- oder ein \LaTeX{}--Dokument--Objekt. Beide Objekte nehmen Text und Formatierungsanweisungen des RTF--Readers entgegen, der ASCII--Builder baut aber nur die Strings in die Ausgabe ein, während der \LaTeX{}--Builder auch die Formatierungsanweisungen übersetzt. Auch hier muss das OOP--System Objekt--Pointer und Polymorphismus beherrschen. \item [{Factory~Method}] Erzeugt Objekte, wobei die Klasse durch einen Parameter spezifiziert ist, oder in Unterklassen variiert wird, ähnlich der Abstract Factory, nur dass hier eine Methode verwendet wird, um ein Objekt zu erzeugen. Die Anforderungen an das OOP--System sind die gleichen wie bei der Abstract Factory \item [{Prototype}] Hier spezifiziert man zunächst eine Instanz, den Prototypen (mit Variablen und Methoden), und erzeugt dann weitere Instanzen als Kopien davon. Die wesentliche Forderung an das OOP--System ist, dass es den clone--Operator implementieren kann, und dass Objekte während der Laufzeit neue Instanz--Variablen und Methoden bekommen können --- dann ist das Prototyp--Pattern direkt im System implementiert. \item [{Singleton}] Eine Klasse, die nur genau einmal instantiiert werden kann; dieses eine Objekt muss global verfügbar sein. Die Instantiierung erfolgt beim ersten Zugriff, nicht beim Programmstart. Die Forderung an das OOP--System hier ist, globale Variablen innerhalb einer Klasse zu ermöglichen, und Methoden, die schon vor der Instantiierung aufgerufen werden können --- diese Methoden können nicht late binding sein. \end{description} \subsection{Structural Patterns} \begin{description} \item [{Adapter}] Ein Adapter konvertiert die Aktionen eines Interfaces in ein anderes, um verschiedene bereits existierende Programme aneinander anzupassen. Beispiel: Eine Grafik--Ausgabe mit einem kartesischen Koordinatensystem soll mit einem Adapter in eine Turtle--Grafik umgesetzt werden, weil ein anderes existierendes Objekt eine Turtle--Grafik erwartet. Das Adapter--Objekt wird also die Position und Richtung der Turtle als Zustandsvariablen enthalten, und aus forward/backward eine entsprechende lineto--Message berechnen. Ein Adapter kann entweder durch Mehrfachvererbung (Class Adapter) oder durch Komposition (Object Adapter) erzeugt werden. \item [{Bridge}] So ähnlich wie ein Adapter, aber von vornherein im Entwurf so vorgesehen. Wenn die Turtle--Grafik im obigen Beispiel von vornherein Design--Ziel ist (wir wollen das als stabiles Interface für den User zur Verfügung stellen), und die Grafik--Ausgabe je nach Ausgabe--Device das mehr oder weniger direkt unterstützt (X Window System schlecht, OpenGL mäßig, PostScript gut), dann sind die Objekte, die das konvertieren, Bridges, nicht Adapter. \item [{Composite}] Erlaubt es, Objekte zusammenzusetzen, und diese zusammengesetzten Objekte ihrerseits genauso wie einzelne Objekte zu behandeln. Beispiel MINOS: Hier gibt es Widgets und Boxes, und natürlich sind Boxes ihrerseits wieder Widgets, die eben mehrere andere Widgets enthalten. Das erfordert Objekt--Pointer und Late Binding. \item [{Decorator}] Fügt zusätzliche Verantwortlichkeit dynamisch zu einem Objekt hinzu. Beispiel MINOS: Die Aktionen, die ein Widget hat, können etwa mit einem Tooltip--Objekt dekoriert werden. Die Aktion selbst ist unverändert (ein Klick produziert das Gleiche wie ohne Tooltip), aber wenn der Benutzer die Maus über dem Widget stehen lässt, wird ein Tooltip angezeigt. Auch hier ist man mit Objekt--Pointern und Late Binding dabei. \item [{Facade}] Stellt ein ,,einfaches`` Interface (in einer Klasse) zur Verfügung, die in Wahrheit über eine ganze Kollektion verschiedener Objekte implementiert wird. Beispiel MINOS: Ein Slider besteht aus Pfeilen nach oben und unten, einem abgesenkten Bereich und einem Slider darin, der den sichtbaren Ausschnitt des Dokuments repräsentiert. Nach außen stellt sich der Slider als ein Objekt dar, das die Position und den sichtbaren Ausschnitt des Dokuments erfasst und verändern kann. Je nach Anwendungsfall kann die Facade auch mit Objekt--Instanzvariablen und Early Binding oder mit Objekt--Pointern und Late Binding implementiert werden. \item [{Flyweight}] Verwendet Objekte gemeinsam, um eine große Zahl von Objekten effizient zu verwalten. Beispiel: Die vtable eines Objektes kann man ja auch als Objekt auffassen. Wie oben beschrieben, ist es sinnvoll, alle Objekte in einer Klasse auf dieselbe vtable zeigen zu lassen, statt die vtable für jedes Objekt zu duplizieren. In einem prototyp--basierten OOP ist es aber möglich, einzelne Objekte später zu ändern, und neue Methoden hinzuzufügen --- dann, aber erst dann, muss die vtable kopiert werden. Die Gang of Four gibt als Beispiel eine Textverarbeitung an, die jeden Buchstaben als Objekt (mit Bounding Box und grafischer Darstellung) speichert. Jeden Buchstaben für sich natürlich nur einmal, um Speicher zu sparen. Zwingend notwendig: Objekt--Pointer. \item [{Proxy}] Ein Platzhalter für ein Objekt, um den Zugang zu kontrollieren. Proxies werden auf vielerlei Weise eingesetzt; das Beispiel, das Andrew Haley erwähnte, ist, um das Erzeugen des Objekts zu verzögern. Eine Textverarbeitung kann einen Proxy nehmen, um das Laden und Darstellen eines Bildes zu verzögern --- solange der Benutzer die Seite mit dem Bild nicht anzeigt, existiert nur der Proxy, der die Größe des Bildes dem Umbruchalgorithmus übergibt. Erst, wenn die draw--Methode des Proxies aufgerufen wird, wird das eingebundene Bild komplett geladen und dargestellt (das dauert und kostet Speicherplatz, will man längere Dokumente bearbeiten, ist es sinnvoll, das nur bei Bedarf zu erledigen --- der Speicher nicht mehr sichtbarer Bilder kann dann auch wieder freigegeben werden). Auch die im Flyweight beschriebenen Buchstaben--Objekte werden ein Proxy--Objekt benötigen, das z.B. die Position speichert. Ein Proxy kann auch Zugangsbeschränkungen implementieren, etwa verhindern, dass eine Datenbank manipuliert wird, wenn sich der User nicht eingeloggt hat. Der Proxy implementiert in diesem Fall den Login--Vorgang, und reicht schreibende Zugriffe erst nach einem erfolgreichen Login auf das Datenbankobjekt durch. Ein Proxy kann aber auch verwendet werden, um Objekte über's Netz anzusprechen. Objekt--Pointer sind hier zwingend erforderlich; je nach Anwendungsfall kann von vornherein bekannt sein, welches Objekt der Proxy ersetzt (Early Binding möglich), oder auch nicht. Für den Anwendungsfall, mit einem Proxy Nachrichten über's Netz zu schicken, ist es sinnvoll, wenn das System Objekte serialisieren kann (also in einer Form abspeichern, die man anderswo wieder laden kann --- in Forth wäre das Sourcecode). \end{description} \subsection{Behavioral Patterns} \begin{description} \item [{Chain~of~Responsibility}] Mehrere Objekte in einer Kette bekommen die gleiche Eingabe serviert, bis eines darauf reagiert. Beispiel MINOS: Das Text--Eingabefeld muss auf Cursor--Tasten und ähnliches reagieren. Jeder derartigen Taste ist ein Objekt zugeordnet, das entsprechend reagiert. Beispiel Recognizer: Die Recognizer, die \noun{Matthias Trute} in amForth eingebaut hat, können als Chain of Responsibility aufgefasst werden --- jeder Recognizer sieht sich das Forth--Wort an, und reagiert entsprechend --- wenn es im Wörterbuch ist, wird es ausgeführt oder compiliert, wenn es eine Zahl ist, umgewandelt, und auf den Stack gelegt bzw. als Literal compiliert. Dieses Pattern benötigt Object--Pointer und Late Binding, es kann auch sinnvoll sein, deferred words als Instanz--Variablen zu ermöglichen. \item [{Command}] Macht aus einem Kommando ein Objekt. Die Methode, die ein command--Objekt zur Verfügung stellt, ist execute, es kann also ausgeführt werden. Was es dabei tut, ist seine Sache. Es kann auch mehrere Varianten von execute geben. Beispiel MINOS: Die Aktionen, die ein Widget hat, rufen ganz spezifischen Code auf (im Kontext eines anderen Objekts), haben aber ein gemeinsames Interface. Kommandos sind die OO--Version von Callbacks. Beispiel Forth: Wörter sind Kommandos; folgt man der Idee des ,,smart compile,``, dann ist ein Wort auch tatsächlich ein richtig komplexes Objekt, das ausgeführt, interpretiert, compiliert und mit postpone später compiliert werden kann. Das geht über die einfache Struktur eines Command--Objekts schon deutlich hinaus. Objekt--Pointer und Late Binding sind zwingend erforderlich, deferred words hilfreich. Auch hilfreich ist es, ein Stück Code im Kontext eines Objekt--Pointers auszuführen, also dort mehrere Methoden aufzurufen, ohne jedesmal zwischen dem Aufrufer und dem aufgerufenen Objekt umzuschalten (Multi--Message). \item [{Interpreter}] Erlaubt es, eine (einfache) Sprache als abstrakten Syntax--Baum zu implementieren. Dieses Pattern ist für Forth--Nutzer wohl eher uninteressant, da wir einfache Sprachen direkt in Forth implementieren, und komplexere Sprachen einen Parser--Generator benötigen, der von dem Pattern nicht umfasst wird. \item [{Iterator}] Ein Iterator durchläuft zusammengesetzte Objekte sequenziell, ohne die innere Struktur des Objekts offenzulegen (also unabhängig davon, ob es ein Array oder eine Liste ist). Der Iterator wandelt eine Klasse ab, also ist entweder Mehrfachvererbung oder die Zuordnung eines bereits definierten Interfaces zur Klasse hilfreich. Late Binding ist ebenso zwingend erforderlich. \item [{Mediator}] Ein Mediator ist ein Objekt, das die Kommunikation zwischen verschiedenen Objekten zentral behandelt, damit die einzelnen Objekte nichts voneinander wissen müssen. Beispiel MINOS: Das Komponenten--Objekt, das Dialoge implementiert, ist auch ein Mediator zwischen den einzelnen Objekten im Dialog. Die Kommandos werden alle an den Mediator gesendet, der weiß, wie die verschiedenen Objekte miteinander interagieren. Objekt--Pointer sind zwingend erforderlich, je nach notwendiger Flexibilität des Mediators kann aber Early Binding ausreichen. \item [{Memento}] Erfasst und speichert den Zustand eines Objekts, um es später zu restaurieren. Beispiel: Um die Undo--Operation in einem Editor zu implementieren, speichert das Memento bei jedem Backspace das gelöschte Zeichen und dessen Position im Text. Führt man später Undo aus, werden diese Zeichen wieder an der richtigen Stelle eingefügt. Objekt--Pointer und Late Binding sind erforderlich. \item [{Observer}] Wenn der Zustand eines Objekts sich ändert, werden alle abhängigen Objekte benachrichtigt. Beispiel Facebook: Wenn man einen Eintrag auf seine Pinwand schreibt, werden alle Freunde benachrichtigt, und bekommen den Eintrag in ihren Neuigkeiten zu sehen. Objekt--Pointer, Late Binding und zumindest Interfaces (ggf. auch Mehrfachvererbung) sind nötig. \item [{State}] Dieses Pattern beschreibt eine State--Maschine, wobei für jeden Zustand eine andere Klasse verwendet wird --- die Funktionen zum Ändern des Zustands sind die Methoden der Zustands--Objekte, sie ändern sich abhängig vom Zustand. Das State--Pattern kann direkt in einem OOP--System implementiert werden, wenn es möglich ist, die Klasse des Objekts zur Laufzeit zu ändern (durch Ändern des vtable--Pointers). \item [{Strategy}] Definiere eine Familie von Algorithmen, die austauschbar sind. Beispiel: Ein Roboter sucht sich einen Weg, und verwendet dabei je nach Situation unterschiedliche Algorithmen. Auf freiem Feld kann er etwa den kürzesten Weg zum Ziel berechnen, in einem Labyrinth muss er das Terrain erkunden und eine Karte erzeugen. Objekt--Pointer und Late Binding sind erforderlich. \item [{Template~Method}] Definiert das Skelett eines Algorithmus, der dann in Unterklassen verfeinert wird. Beispiel Forth: Compiler und Interpreter parsen jeweils ein Wort aus dem Eingabestrom, bearbeiten das dann aber unterschiedlich. Statt STATE @ IF .. ELSE .. THEN zu schreiben, definiert man einen Hook, der dann in den Unterklassen compiler und interpreter implementiert wird. Late Binding ist erforderlich. \item [{Visitor}] Eine Operation auf alle Elemente eines Composite. Beispiel MINOS: Bei der Formatierung der Objekte wird jedes Objekt in einer Box nach minimaler und maximaler Ausdehnung gefragt, und daraus berechnet, welche Position und Größe es in der Box haben wird. Objekt--Pointer und Late Binding sind erforderlich, die Möglichkeit, mehrere Methoden im Kontext eines Objekt--Pointers hintereinander aufzurufen, hilfreich. Es ist auch hilfreich, wenn man Teile eines Wortes innerhalb einer impliziten Schleife ausführen kann --- in MINOS verwende ich dafür Return--Stack--Tricks. Ebenfalls nützlich ist eine Tail--Call--Optimization, um Returnstack--Platz zu sparen. \end{description} \section{Was bleibt zu tun} Ich habe mein OOP (,,Bernd\-OOF``) schon ein paar Jahre vor der Veröffentlichung des Design--Pattern--Buchs geschrieben, und zum Teil erst hinterher so erweitert, dass die meisten Design--Pattern implementierbar sind. Manchmal nicht ganz so elegant, wie man sich das wünscht, manchmal auf eine sehr forthige Art, die zumindest den Compiler von VFX Forth überfordert (das Visitor--Pattern wird in MINOS durch eine implizite Schleife implementiert, die am Returnstack herummanipuliert). Die Implementierung eines OOP--Systems ist leider immer ,,Guru Code,`` wie Stephen Pelc es ausdrückt; es ist nichts offensichtlich, es muss alles umfangreich dokumentiert werden; da ein neues Programmier--Paradigma implementiert wird, kann man sich nicht auf gemeinsame Kenntnisse verlassen. Das ist bei praktisch keinem existierenden OOP--System wirklich der Fall, jedenfalls nicht in dem nötigen Ausmaß. Auch wenn Bernd\-OOF meiner Ansicht nach verwendbar ist, und die anderen OOP--Systeme für Forth zu viele Mängel haben, ist es keine Lösung für das Problem, das wir haben. Es ist auch nicht wirklich portabel, die Implementierung einer performanten Lösung ist nicht trivial, und ein signifikanter Teil des VFX--Compilers (der Inliner) musste komplett neu geschrieben werden, um das richtig zu compilieren (das war aber eine Aktion, die ohnehin notwendig war). Ich finde den Common--Lisp--Ansatz des Metaobjekt--Protokolls sehr interessant, und werde versuchen, etwas Ähnliches in Forth zu implementieren. Die Idee dahinter ist, das OOP--System selbst unter Verwendung objektorientierter Programmierung zu implementieren, und so die notwendige Flexibilität zu erreichen. Als Implementierungsbasis, also zum Bootstrappen des OOPs, werde ich mein Mini--OOP verwenden, die 12 Zeilen, die Vtables und Instanz--Variablen implementieren, mehr aber nicht. Ziel ist, dass man damit dann Syntax und Semantik bestehender OOP--Systeme, also etwa von SWOOP, FMS, Bernd\-OOF und anderen implementieren kann, und diese Systeme auch jederzeit erweitern kann (etwa um Mehrfachvererbung), ohne wie bisher intime Kenntnis über die jeweiligen Systeme zu haben --- nur die Kenntnis des Metaobjekt--Protokolls ist natürlich zwingend nötig. Es soll im so entstehenden System möglich sein, Klassen aus verschiedenen OOP--Systemen zu verknüpfen, und gemeinsam zu verwenden. Dabei muss es möglich sein, dass die Implementierung deutlich voneinander abweicht --- Bernd\-OOF z.B. hat eine C++/Java--artige Klassenhierarchie, in der jedes Objekt nur die Messages versteht, die es entweder geerbt oder selbst definiert hat. Andere Messages werden schon zur Compile--Zeit abgewiesen. FMS dagegen implementiert das Smalltalk--Model, bei der jedes Objekt prinzipiell jede Message versteht, zumindest über die ,,kannitverstan``--Methode, die aber erst zur Laufzeit ausgeführt wird, und eine Fehlermeldung produziert. Die vtables in Bernd\-OOF implementiert man am besten als Arrays (da sie immer dicht besetzt sind, die kompakteste Darstellung im Speicher), die vtables von FMS eher als Listen oder Hashes, da sie dünn besetzt sind (die meisten Klassen implementieren nur wenige Messages, und verstehen die vielen anderen Messages nicht). Es soll auch möglich sein, degenerierte Klassen und Objekte zu erzeugen, die eben nur Early Binding können und keinen vtable--Pointer haben, weil viele Forth--Programmierer offensichtlich damit glücklich sind. Die einzelnen Fähigkeiten eines kompletten OOP--Systems sollten getrennt voneinander implementierbar sein, und als Erweiterungen betrachtet werden können. Wenn ich also z.B. Multiple Inheritance brauche (Bernd\-OOF kann das derzeit nicht), dann lade ich es einfach dazu. Die notwendigen Primitives, also der Zugriff auf Instanz--Variablen über einen this/self--Pointer, die performante Ausführung von bind(message) über vtables (Arrays und Listen/Hashes), und die Erweiterbarkeit des äußeren Interpreters um Namensräume für Klassen müssen von der eigentlichen Syntax und Semantik des OOP--Systems getrennt werden können, damit man nur diesen Low--Level--Teil portieren und für jedes Forth--System extra warten muss. \begin{thebibliography}{1} \bibitem{gof}\noun{Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: }\emph{Design Patterns: Elements of Reusable Object--Oriented Software} \bibitem{ca}\noun{Christopher Alexander, Sara Ishikawa, Murray Silverstein, Max Jacobson, Ingrid Fiksdahl--King, Shlomo Angel: }\emph{A Pattern Language} \bibitem{tf}\noun{Leo Brodie,}\emph{ Thinking Forth} \bibitem{mop}\noun{Gregor Kiczales, Jim des Rivières, Daniel G. Bobrow, }\emph{The Art of the Metaobject Protocol} \end{thebibliography} \end{multicols} \end{document}