-
Technisches
Gebiet der Erfindung
-
Diese
Erfindung bezieht sich auf Computersprachen im Allgemeinen und insbesondere
auf ein Verfahren und eine Vorrichtung zum Programmieren in objekt-basierten
oder objekt-orientierten Sprachen.
-
Hintergrund
der Erfindung
-
Höhere Programmiersprachen
versetzten Entwickler in die Lage, einen Kode in kleinere Einheiten
zu unterteilen. In nicht-objekt-orientierten Sprachen ist die kleinste
Einheit der Unterteilung eine Prozedur oder Funktion. In objekt-orientierten Sprachen ist
die kleinste Einheit der Unterteilung typischerweise eine Methode
einer Klasse.
-
Viele
moderne Programmiersprachen weisen ferner Mechanismen auf, die es
Entwicklern ermöglichen,
diese Unterteilungseinheiten wie Werte zu behandeln, die auch wie
Werte verwendet werden können
(übergeben,
in Datenstrukturen gespeichert, etc.) und auch zum „Aufruf" einer Kodeeinheit,
die der Wert repräsentiert,
verwendet werden können.
Verschiedene Sprachen stellen dabei verschiedene Mechanismen bereit.
-
In
nicht-objekt-orientierten Programmiersprachen ist das am besten
bekannte dieser Konstrukte der Funktionenzeiger, wie er in C und
C++ gefunden wird. In funktionalen Programmiersprachen (LISP, ML,
Scheme, etc.) ist diese Art der Programmierung üblich. In objekt-orientierten
Programmiersprachen ist kein adäquater
Mechanismus für
diesen Zweck entwickelt worden.
-
Damit
ein Programmiersystem (Sprache und Laufzeit) es Entwicklern ermöglicht,
Methodenreferenzen zu erzeugen und zu verwenden, adressiert die
vorliegende Erfindung folgende Designziele:
- a)
Definition einer Einheit, die die Gestalt der zu referenzierenden
Methode definiert;
- b) Erhalt einer Referenz auf eine Methode;
- c) Speichern dieser Referenz in einer beliebigen Datenstruktur;
- d) Übergeben
der Referenz an einen von Dritten geschriebenen Kode;
- e) Ermöglichung
des Aufrufs der Methode durch jeden, der die Methodenreferenz besitzt;
und
- f) Ermöglichung
der Bestimmung des Zielobjekt und der referenzierten Methode durch
den Besitzer der Methodenreferenz.
-
Des
Weiteren erreicht das Programmiersystem der vorliegenden Erfindung
die oben genannten Ziele auf eine streng typisierte Art und Weise,
die es ermöglicht
für:
- a) Entwickler, Probleme der Unverträglichkeit
von Typen („type
mismatch") früher (zur
Kompilierzeit) denn später
(zur Laufzeit) zu erkennen; und
- b) Entwicklungswerkzeuge, dem Entwickler Informationen über die
Gestalt von Komponenten, die Delegate verwenden, zu präsentieren.
-
Frühere Lösungen ähnlicher
Problemstellungen
-
Eine
Reihe von Produkten beinhalten C und C++ ähnliche Funktionenzeiger, wie
z.B. Microsoft® Visual
C++, Visual Basic und Visual Basic A, welche alle von der Microsoft
Corporation erhältlich
sind. Das Windows® API macht ebenfalls intensiven
Gebrauch von Funktionenzeigern. Funktionenzeiger dieser Art sind
aufgrund einer Reihe von Gründen
nachteilig:
- a) sie sind nicht typensicher („type safe") und resultieren
in einer Typumwandlung („casting");
- b) sie sind nicht sicherheitsbewusst – ein Funktionenzeiger ist
lediglich ein Zeiger, der versehentlich geändert und dann aufgerufen werden
kann, welches zu einem Absturz des Systems führt; und
- c) sie erlauben es nicht das Zielobjekt und die Zielmethode
aufzurufen.
-
Microsoft
Java VM und ähnliche
Produkte haben ein „Java
Core Reflection"-System, welches eine
primitive Form der Methodenreferenz aufweist. Ein Entwickler kann
eine „java.lang.reflect.method" Instanz erhalten.
Diese Form der Referenz ist inhärent
spät-gebunden
(„late
bound") und daher
ineffizient.
-
Zusammenfassung
der Erfindung
-
Einem
Aspekt der vorliegenden Erfindung zufolge wird ein Computerprogrammprodukt
gemäß Anspruch
1 vorgeschlagen.
-
Einem
anderen Aspekt der vorliegenden Erfindung zufolge wird ein objektbasiertes
Programmiersystem gemäß Anspruch
8 vorgeschlagen.
-
Einem
weiteren Aspekt der vorliegenden Erfindung zufolge wird ein Verfahren
zur Programmierung eines Computers gemäß Anspruch 19 vorgeschlagen.
-
Kurze Erläuterung
der Zeichnungen
-
1 zeigt
ein Blockdiagramm der Hauptkomponenten der vorliegenden Erfindung.
-
2 zeigt
die für
die vorliegende Erfindung passenden Plattformen und Träger.
-
3 ist
ein Beispiel J++ Computerprogramm gemäß der vorliegenden Erfindung.
-
4 and 5A zeigen
Beispieldeklarationen eines früh-gebundenen
Delegaten gemäß der vorliegenden
Erfindung.
-
5B zeigt
ein Beispiel der Konstruktion eines spät-gebundenen Delegaten basierend
auf einem Methodennamen-String.
-
6 zeigt
eine Beispieldefinition der Klasse „Delegate" gemäß der vorliegenden
Erfindung.
-
7 ist
eine Beispieldefinition der Klasse „MulticastDelegate".
-
8 zeigt
ein Ausführungsbeispiel,
welches Microsoft® Visual J++ um die Einbeziehung
einer „com.ms.lang.Callable" Schnittstelle erweitert.
-
9 zeigt
eine Beispiel-Ereignis(„Event")-Klasse gemäß der vorliegenden
Erfindung.
-
10 zeigt
einen Beispiel-Ereignis-Handhabungs(„Event-Handling")-Kode gemäß der vorliegenden
Erfindung.
-
11 zeigt
einen Beispiel-Ereignis-Handhabungs(„Event-Handling")-Kode gemäß der vorliegenden
Erfindung.
-
12 zeigt
einen Beispiel-Ereignis-Handhabungs(„Event-Handling")-Kode gemäß der vorliegenden
Erfindung.
-
13 zeigt
einen Beispiel-Ereignis-Handhabungs(„Event-Handling")-Kode gemäß der vorliegenden
Erfindung.
-
14A-14B zeigt ein Beispiel-Ereignis-Handhabungs(„Event-Handling")-Programm unter Verwendung von Delegaten
gemäß der vorliegenden
Erfindung.
-
15A-15B zeigt ein Beispiel-Ereignis-Handhabungs(„Event-Handling")-Programm gemäß der vorliegenden Erfindung.
-
16 zeigt
ein Beispiel eines „single-cast event-handling"-Programms gemäß der vorliegenden
Erfindung.
-
17 zeigt
eine Definition der Klasse „Event" gemäß der vorliegenden
Erfindung.
-
18 zeigt
eine Beispieldefinition einer „event-handler"-Deklaration gemäß der vorliegenden Erfindung.
-
19 zeigt
eine Beispieldefinition einer „CancelEvent"-Klasse gemäß der vorliegenden
Erfindung.
-
20 zeigt
eines Deklaration eines „event handlers" gemäß der vorliegenden
Erfindung.
-
Detaillierte
Beschreibung der Erfindung
-
In
der folgenden detaillierten Beschreibung der bevorzugten Ausführungsformen
wird auf die begleitenden Zeichnungen, die einen Teil hiervon bilden,
Bezug genommen, in welchen spezifische Ausgestaltungen, in denen
die Erfindung genutzt werden könnte,
mittels Darstellungen gezeigt werden. Es ist verständlich,
dass andere Ausgestaltungen benutzt und strukturelle Änderungen
vorgenommen werden können,
ohne den Rahmen der vorliegenden Erfindung zu verlassen.
-
EINFÜHRUNG
-
Die
vorliegende Erfindung sieht einen Mechanismus, der im Folgenden „Delegat" (in den Ansprüchen „Delegierter", im Englischen „delegate") genannt wird, zur
Referenz einer Methode in objekt-basierter Programmierung vor. Während ein Funktionenzeiger
eine Referenz auf eine Funktion beinhaltet, beinhaltet ein Delegat
eine Referenz auf eine Methode. Die beiden unterscheiden sich dadurch,
dass der Funktionenzeiger nur eine Referenz (eine Funktion) beinhaltet,
während
der Delegat eine Referenz auf ein Objekt und eine Methode des Objekts
beinhaltet.
-
Eine
nützliche
Eigenschaft eines hier beschriebenen Delegaten ist, dass der Delegat
den Typ (Klasse) des Objekts, auf das es sich bezieht, nicht kennt
oder sich nicht darum sorgt.
-
Alles
was interessiert ist stattdessen, dass die Parameterliste der referenzierten
Methode kompatibel mit dem Delegaten ist. Dies macht Delegaten sehr
nützlich
für anonyme
Anwendungen der Benachrichtigung – ein Aufrufer, der den Delegaten
aufruft, muss nicht genau wissen, welche Klasse oder welches Element
aufgerufen wird.
-
Ähnlich einem
Funktionenzeiger ist ein Delegat ein Wert. Wie andere Werte kann
er Variablen zugewiesen, von einer Prozedur zu einer anderen übergeben
werden etc.. Ein Delegat kann also verwendet/angewendet/aufgerufen
werden. Kein oder mehrere Argumente können einem Delegaten während der
Ausführung
dieser Operation übergeben
werden.
-
Delegate
unterscheiden sich von Funktionenzeigern dadurch, dass Delegate
sicher oder streng typisiert sein können. Des Weiteren, während Funktionenzeiger
kontextfrei sind – wenn
eine Funktion durch einen Funktionenzeiger aufgerufen wurde, hat
die Funktion keinen Kontext außer
dem, der durch Parameter hindurchgereicht wurde – trägt ein Delegat seinen Kontext
mit sich. Wenn eine Methode durch einen Delegaten aufgerufen wird,
findet der Aufruf auf das Objekt statt, auf dass sich der Delegat bezieht,
und stellt so eine Methode mit einem bekannten Kontext zur Verfügung.
-
Wie
ausführlicher
weiter unten beschrieben werden wird, nimmt die Erfindung eine Reihe
von verschiedenen Formen an, wie sie in 1 dargestellt sind.
Wie darin gezeigt, ist das hier beschriebene Ausführungsbeispiel
des Delegaten in Form einer Programmiersprache 10, eines
Computerprogramms 12, eines Kompilierers („Compilers") 14, eines
Java-Virtuelle-Maschinen-Interpretierers
(„Java
Virtual Machine Interpreter")
und eines Ereignismodells („event
model") 18 ausgeführt. Wie
in 2 gezeigt, können
diese verschiedenen Ausgestaltungen auf einem Rechnersystem 20 ausgeführt werden
(beinhaltend: konventionelle Rechenkomponenten, wie z. B., aber
nicht darauf beschränkt,
eine CPU, einem Speicher, einer Tastatur, einer Maus), wie beispielsweise auf,
aber nicht darauf beschränkt,
einem IBM-kompatiblen Personalcomputer, und gespeichert oder andersartig
in maschinenlesbarer Form ausgestaltet werden, auf verschiedenen
Trägermedien
wie z.B. Festplatten 22, Floppy-Disketten 24,
CD-ROM 26 oder als eine elektronischen Datenübertragung 28.
-
BEISPIELAUSGESTALTUNG
DER ERFINDUNG IN J++
-
Das
folgende Ausführungsbeispiel
der Erfindung beschreibt eine Implementierung von Delegaten in Microsoft® Visual
J++, Version 6.0, der Microsoft Corporation. Die vorliegende Erfindung
ist jedoch in keinster Weise auf die J++Sprache beschränkt, sondern
ist eher allgemeiner auf jedes objektorientiertes Programmiersystem,
wie z. B. C++ oder einem objekt-basierten Programmiersystem wie Microsoft® Visual
Basic, anwendbar. Die Formulierung „objekt-basiert" meint dabei eine
Programmiersprache, in der Kode in Objekten organisiert werden, aber
welche nicht notwendigerweise Vererbung („inheritance") unterstützen, wie
Microsoft® Visual
Basic.
-
Vor
Beschreibung der Erfindung im Detail, wird ein einfaches Beispiel
eines J++ Computerprogramms 30, welches Delegate verwendet,
in 3 präsentiert,
wobei eine Delegateninstanz eine Methode der Instanz kapselt. In
diesem Fall, wird die Methode „Hallo" unter Verwendung
eines Delegaten „f" aufgerufen. Ferner
definiert die Deklaration des Delegaten die Arten der Methoden,
die der Delegat in der Lage ist zu kapseln.
-
Das
Beispielprogramm der 3 illustriert, wie eine Instanz
f des Delegaten „SimpleDelegate" realisiert wird
(„f=new
SimpleDelegate(o.hello)"),
und im weiteren Verlauf aufgerufen wird („f.invoke()"). Wie ausführlicher
weiter unten beschrieben werden wird, muss der Typ des Delegaten „SimpleDelegate" mit dem Typen der
darauf verwiesenen Methode „hallo" übereinstimmen, um gültig und
ausführbar
zu sein. In diesem Fall benötigen
sowohl „hallo" als auch „SimpleDelegate" keine Eingabeparameter
und haben eine Ausgabe „void". Wie ebenfalls weiter
unten erläutert
werden wird macht dieses Erfordernis den Delegaten typensicher („type-safe").
-
Unten
angeschlossen befindet sich eine Beschreibung von Delegaten, als
eine Erweiterung der J++ Sprache, wie sie in Microsoft® Visual
J++, Version 6.0 implementiert ist. Die Java-Sprachenspezifikation,
wie sie von Sun Microsystems veröffentlicht wurde,
wird hierin alternativ als „JLS" bezeichnet und wird
ferner hierin teilweise zitiert.
-
Delegatendeklarationen
im einem J++ Ausführungsbeispiel
-
Eine
Delegatendeklaration gemäß dem folgenden
Ausführungsbeispiel
der Erfindung, spezifiziert eine neue Klasse, die entweder von „Delegate" oder „MulticastDelegate" abgeleitet wird.
Die Delegatendeklaration („DelegateDeclaration") nimmt die Form
an:
DelegateModifiersoptdelegate ResultType
Indentifier (FormalParameterListopt) Throwsopt
-
Die
relevanten Produkte für
Klassendeklarationen sind:
ClassModifiersopt class
Indentifier Superopt Interfacesopt ClassBody
-
Eine „DelegateDeclaration" resultiert in einer Klasse,
die, wenn sie unter Verwendung der Java-Sprachensyntax des vorliegenden
Beispiels deklariert wird, die folgenden Attribute aufweist:
- 1) Für „ClassModifiersopt" weist
die Klassendeklaration „DelegateModifiersopt" plus
die folgenden zusätzlichen
Modifikatoren auf, wobei angenommen wird, dass sie im gegebenen
Kontext spezifiziert seien: „static", „final".
- 2) Für „Indentifier"- hat den „Indentifier" der „DelegateDeclaration".
- 3) Für „Superopt",
es hat „extends com.ms.lang.MulticastDelegate", wenn der Multicast-Modifikator
spezifiziert ist, und „extends com.ms.lang.Delegate", wenn der Multicast-Modifikator
ausgelassen wurde.
- 4) Für „Interfacesopt",
es hat „".
-
Es
folgt, dass, wenn ein Delegat in einem benannten Packet (s. §7.4.1 der
JLS) mit vollständig qualifiziertem
Namen P (siehe §6.7
der JLS) deklariert wurde, der Delegat dann den vollständig qualifizierten
Name „P.Identifier" aufweist. Wenn der
Delegat in einem unbenannten Packet (s. §7.4.2 der JLS) ist, dann weist
der Delegat den vollständig
qualifizierten Namen „Identifier" auf. Im Beispiel
der 4 ist der Delegat „EmptyMethod" 32 in einer
Kompilationseinheit ohne Packetangabe deklariert und daher ist „EmptyMethod" sein vollständig qualifizierter
Name, wogegen im Beispielkode 34 der 5A der
vollständig
qualifizierte Name des Delegaten „EmptyMethod" „vista.EmptyMethod" lautet.
-
Im
vorliegenden Ausführungsbeispiel,
erzeugt der Kompilierer 14 (ausführbar auf einer digitalen Computerplattform)
einen Kompilierzeitfehler:
- 1) wenn der „Identifier", der einen Delegaten
benennt, als Name irgendeines anderen Delegatentyps, eines Klassentyps
oder eines Interfacetyps, deklariert im selben Packet (s. §7.6 der
JLS), auftritt; oder
- 2) wenn der „Identifier", der einen Delegaten
benennt, auch als Typ durch eine „single-type-import" Deklaration (s. §7.5.1 der
JLS) in der Kompiliereinheit (s. §7.3 der JLS), die die Delegatendeklaration
enthält,
deklariert ist.
-
Wirkungsbereich
des Delegatentypennamens
-
Der „Identifier" in einer Delegatendeklaration spezifiziert
den Namen des Delegaten.
-
Dieser
Delegatenname hat als seinen Wirkungsbereich („scope", s. §6.3 der JLS) das gesamte Packet,
in dem der Delegat deklariert ist.
-
Delegaten-Modifikatoren
-
Eine
Delegatendeklaration kann Delegatenmodifikatoren beinhalten.
-
„DelegateModifiers" können Null
oder eines der folgenden sein:
public private static final
multicast.
-
Wenn
zwei oder mehrere Delegatenmodifikatoren in einer Delegatendeklaration
auftreten, dann ist es üblich,
aber nicht notwendig, dass sie in der Reihenfolge auftreten, die
konsistent mit der oben bei der Produktion für „DelegateModifier" gezeigten Reihenfolge
ist.
-
Eine
Delegatendeklaration, die den multicast Modifikator beinhaltet,
muss einen Ergebnistypen („ResultType)" von „void" haben; andernfalls
tritt ein Kompilierfehler auf.
-
Der
Zugriffsmodifikator „public" wird in §6.6 der
JLS diskutiert. Der Kompilierer dieses Ausführungsbeispiels der Erfindung
erzeugt einen Kompilierzeitfehler wenn:
- 1)
mehr als ein Delegatenmodifikator verwendet wird;
- 2) wenn derselbe Modifikator mehr als einmal in einer Delegatendeklaration
auftritt, oder wenn eine Delegatendeklaration mehr als einen der
Zugriffsmodifikatoren „public" oder „private" aufweist; oder
- 3) wenn der „private" Modifikator in einer
Delegatendeklaration auftritt, die nicht in einer Klasse gekapselt
ist.
-
Delegaten
sind implizit „static". Es ist erlaubt, aber
es wird streng als eine Frage des Stils davon abgeraten, den Modifikator „static" redundant innerhalb einer
Delegatendeklaration zu spezifizieren. Ferner sind Delegaten implizit „final". Es ist erlaubt,
aber es wird streng als eine Frage des Stils davon abgeraten, den
Modifikator „final" redundant in einer
Delegatendeklaration zu spezifizieren.
-
Gemäß dem vorliegenden
Ausführungsbeispiel
können
Delegatendefinitionen dort auftreten, wo immer Klassendefinitionen
auftreten. Wie Klassendefinitionen, kann eine Delegatendefinition
in einer Klassendefinition gekapselt sein. Ferner sind Delegate
immer auf oberster Ebene.
-
Die
Elemente („Member") eines Delegaten beinhalten
immer Elemente, die von der Basisklasse vererbt wurden (entweder „Delegate" – oder „MulticastDelegate"-Klasse), und zudem
eine spezielle, automatisch erzeugte „invoke" Methode (beschrieben weiter unten)
aufweisen, die zum Aufruf einer gekapselten Methode verwendet werden
kann. Eine Delegatendeklaration resultiert weiter in einer Klasse mit
einem speziellen, automatisch erzeugten Konstruktor.
-
Die Aufruf-Methode
-
Die
Elemente eines Delegaten beinhalten eine automatisch erzeugte „invoke"-Methode, die zum
Aufruf einer gekapselten Methoden verwendet werden kann. Die Signatur
und der Ergebnistyp der „invoke"-Methode sind durch
die Delegatendeklaration bestimmt. Insbesondere ist die Signatur
der „invoke"-Methode:
ResultType
invoke (FormalParameterListopt) Throwsopt,
wobei ResultType, FormalParameterListopt (so vorhanden) und Throwsopt (so
vorhanden) diejenigen der Delegatendeklaration sind.
-
Delegateninstantiierung
-
Delegaten
haben zwei Konstruktoren, und zwar:
- 1) Einen „Instanz"-Konstruktor, dessen
Form durch die „Delegate"-Basisklasse definiert ist. Die Argumente
dieses Konstruktors, ein „Object" und ein „String" Methodenname spezifizieren
kombiniert mit dem Typen des Delegaten ein Element des indizierten
Objektes, das mit der Delegatendefinition übereinstimmt, eindeutig. Der
neu konstruierte Delegat kapselt diese Methode.
- 2) Einen speziellen „Methodendesignator"-Konstruktor, der
die Methodenauflösung
zur Kompilierzeit denn zur Ausführzeit
ermöglicht.
Der Methodendesignatordelegat ist nicht durch die „Java Core
Reflection" dargelegt.
-
Die
Klassen- und Instanzenkonstruktoren sind in keinster Weise besonders;
sie werden im Detail in der Spezifikation der Basisklasse „Delegate" im Folgenden diskutiert.
Ein Methodendesignatorausdruck wird zur Indizierung einer Klassen-
oder einer Instanzmethode zum Zwecke der Delegateninstantiierung
verwendet.
-
Die „ClassInstanceCreationExpression" nimmt folgende Form
an:
new ClassType (ArgumentListopt)
DelegateInstantiationExpression
-
Die „DelegateInstantiationExpression" nimmt folgende Form
an:
new DelegateType(MethodDesignator)
-
Der „MethodDesignator" nimmt folgende Form
an:
MethodName
Primary.Identifier
super.Identifier
-
Der „DelegateType" ist eine Klasse,
die sich von „com.ms.lang.Delegate" herleitet.
-
Ein
Methodendesignator ist zur Designation einer Methode nicht ausreichend.
Zur Benennung einer bestimmten Methode denn (möglicherweise) eines Satzes
von Methoden ist es notwendig, sich auf den Ergebnistypen und die
Signatur (Anzahl und Typen der Argumente) des Delegaten, der mit
dem Methodendesignator realisiert wird, zu beziehen. Aus diesem
Grunde werden der „MethodDesignator" und die neue Instanzspezifikationsform
als eine Einheit beschrieben.
-
Delegateninstantiierung
gebraucht Suchregeln, die denen der Methodenaufrufausdrücke (s. §15.11 der
JLS) ähnlich
sind. Eine Methode zu benennen ist aufgrund der Möglichkeit
des Methodenüberladens
und des Instanzmethodenüberschreibens komplex.
Die Bestimmung der Methode, die durch einen Methodenaufrufausdruck
aufgerufen werden wird, umfasst mehrere zusätzliche Schritte. Unten werden
die Schritte, die in der Delegateninstantiierung, sowohl zur Kompilierzeit
als auch zur Laufzeit, gemäß dem vorliegenden
Ausführungsbeispiel
der Erfindungen) involviert sind, beschrieben.
-
Früh-gebundene
und spät-gebundene
Delegate
-
Im
Beispiel der 3 ist die Instantiierung („f=new
SimpleDelegate(o.hello)")
ein früh-gebundener
(„early-bound") Delegat, bei dem
die Methode „hallo" zur Kompilierzeit
bekannt ist. In einige Fällen ist
die Methode, an die der Delegat gebunden sein sollte, während der Übersetzung
nicht bekannt. Der Anwender einer interaktiven Entwicklungsumgebung könnte beispielsweise
den Namen einer Methode zum Programmprototyping eingeben oder der
Name der aufzurufenden Methode könnte
auf irgendeine andere Art berechnet oder eingegeben werden. In diesem
Falle kann die Instanz eines Delegaten durch Verwendung eines Zwei-Argumenten-Konstruktors gebildet
werden.
-
Das
erste Argument des Konstruktors spezifiziert das Objekt, in der
die Methode aufgerufen werden soll. Das zweite Argument des Konstruktors
spezifiziert den Namen der Methode. Das im ersten Argument spezifizierte
Objekt wird überprüft, um sicherzustellen,
dass es eine Methode des angegebenen Namens aufweist, mit derselben
Signatur (d.h. Ergebnistyp und Argumenttyp) mit der die Delegatenklasse
deklariert war. Wenn nein, tritt eine Ausnahme („exception") auf; wenn ja, wird ein neues Delegatenobjekt
gebildet und zurückgegeben.
-
Die
Instantiierung eines solchen „spät-gebundenen
(„late-bound") Delegaten, wie
er unmittelbar zuvor beschrieben wurde, nimmt beispielsweise die
Form „g=new
SimpleDelegate(o,"hello")" an, wobei die Methode „hallo" ein String ist,
der während
der Ausführung
des Programms definiert werden könnte. In
diesem Fall des spät-gebunden
Delegaten nimmt der Aufruf die Form „g.dynamicInvoke(Object⌷args)" an. Zur Verdeutlichung
konstruiert das Beispielprogramm 36 der 5B einen
spät-gebunden
Delegaten auf einem String „Methodennamen".
-
Kompilierer-Ausführung
-
Wie
oben dargelegt umfasst das Ausführungsbeispiel
der vorliegenden Erfindung einen Kompilierer 14. Mehrere
Schritte werden zur Ausführung eines
Delegateninstantiiierungsausdrucks zur Kompilierzeit verlangt. Erstens
muss der Kompilierer bestimmen, welche Klasse oder welche Schnittstelle („interface") zu durchsuchen
ist. Zweitens muss der Kompilierer nach einer Methode suchen, die
passend und verfügbar
ist. Drittens muss der Kompilierer überprüfen, ob die Methode geeignet
ist. Diese Prozesse werden im Folgenden im Detail beschrieben.
-
Kompilierzeit – Schritt
1: Bestimme die zu durchsuchende Klasse oder Schnittstelle:
-
Der
erste Schritt zur Verarbeitung eines Delegateninstantiierungsausdrucks
zur Kompilierzeit ist es den Namen der Methode herauszufinden und
welche Klasse oder welches Interface auf Definitionen von Methoden
dieses Namens zu überprüfen ist.
Es gilt mehrere Fälle
zu berücksichtigen,
welche von der Gestalt des Methodendesignators abhängen:
- 1) Wenn die Ausgestaltung „MethodName" ist, dann gibt es
zwei Unterfälle:
- a) Wenn es ein einfacher Name ist, das heißt nur ein „Identifier", dann ist der Name
der Methode der „Identifier" und die zu durchsuchende
Klasse oder die Schnittstelle ist diejenige, deren Deklaration den
Methodendesignator enthält.
- b) In allen anderen Fällen
hat der qualifizierte Name die Ausgestaltung „FieldName.Identifier"; dann ist der Name
der Methode der „Identifier" und die zu durchsuchende
Klasse oder das Interface ist der deklarierte Typ des Feldes, bezeichnet mit „FieldName".
- 2) Wenn die Ausgestaltung „Primary.Identifier" ist, ist der Name
der Methode der „Identifier" und die zu durchsuchende
Klasse oder das Interface ist der Typ des „Primary"-Ausdrucks.
- 3) Wenn die Form „super.Identifier" ist, dann ist der
Name der Methode der „Identifier" und die zu durchsuchende
Klasse ist die Oberklasse der Klasse, deren Deklaration die Delegateninstantiierung
enthält.
Ein Kompilierzeitfehler tritt auf, wenn solch ein Methodendesignator
in einem Interface oder in der Klasse „Object" oder in einer statischen („static") Methode, einem
statischen („static") Initialisierer
oder einem Initialisierer für eine
statische („static") Variable erscheint.
Es folgt, dass ein Methodendesignator dieser Art nur in einer Klasse
verschieden der Klasse „Object" erscheinen kann,
und zwar nur im Hauptteil einer Instanzmethode, im Hauptteil eines
Konstruktors oder eines Initialisierers für eine Instanzvariable.
-
Kompilierzeit – Schritt
2: Bestimme Methodensignatur:
-
Der
zweite Schritt durchsucht die Klasse oder das Interface des vorangegangenen
Schrittes nach Methodendeklarationen. Dieser Schritt verwendet den
Namen der angegebenen Methode und die Typen der formalen Parameter
der Delegatendefinition zur Bestimmung von Methodendeklarationen,
die sowohl passend als auch verfügbar
sind, d.h. Deklarationen, die mit dem Typ des Delegaten, der instanziiert
wird, übereinstimmen,
und die korrekt aus dem Delegateninstantiierungsausdruck aufgerufen
werden können.
Da die Übereinstimmungsregeln
strikt sind, kann es nur eine solche Methodendeklaration geben.
-
Eine
Methode stimmt entweder mit dem Delegatentyp überein oder nicht. Es ist möglich, eine Delegateninstanz
des Typs T, die die Methode M kapselt zu instanziieren, wenn und
nur wenn M mit T übereinstimmt.
-
Sei
M die Methode and T der Typ des Delegaten. M entspricht T wenn und
nur wenn alles des Folgenden zutrifft:
- 1) M
und T haben die gleiche Anzahl von formalen Parametern;
- 2) Der Typ jedes der Parameter von M entspricht identisch den
Typen der Parameter von T;
- 3) Der Ergebnistyp von M entspricht identisch dem Ergebnistyp
von T; und
- 4) M ist nicht zum Auswerfen von mehr Ausnahmen als T deklariert.
Wenn M eine „throws" Klausel aufweist,
die irgendwelche überprüften Ausnahmetypen
aufführt,
dann muss T ebenso eine „throws" Klausel aufweisen
und für
jede der überprüften Ausnahmetypen,
die in der „throws" Klausel von M aufgeführt ist,
muss die gleiche Ausnahmeklasse oder eine ihrer Oberklassen in der „throws" Klausel von T vorkommen.
-
Es
ist zu beachten, dass, da die Übereinstimmungsregeln
striktes Übereinstimmen
(identische Typen für
Parameterlisten und Ergebnistypen) erfordern, es im besten Falle
genau eine übereinstimmende
Methode für
jedes gegebene Ziel gibt.
-
Die
Klasse oder das Interface, das durch den hierin beschriebenen Prozess
beschrieben wird, wird nach einer Methodendeklaration durchsucht,
die dem Delegatentyp entspricht; Methodendefinitionen, vererbt von
Oberklassen und Oberschnittstellen werden in diese Suche eingebunden.
Ob eine Methodendeklaration verfügbar
für einen
Methodenaufruf ist, hängt
von dem Zugriffmodifikator („public", „none", „protected" oder „private") in der Methodendeklaration
ab und davon, wo der Methodendesignator erscheint. Wenn die Klasse
oder das Interface keine Methodendeklaration aufweist, die sowohl
passend als auch verfügbar
ist, dann wird ein Kompilierzeitfehler erzeugt.
-
Kompilierzeit – Schritt
3: Ist die gewählte
Methode geeignet?
-
Wenn
es eine passende und verfügbare
Methodendeklaration für
einen Delegateninstantiierungsausdruck gibt, wird diese die Kompilierzeitdeklaration
(„compile-time
declaration") für den Delegateninstantiierungsausdruck
genannt. Mehrere weitere Überprüfungen der
Kompilierzeitdeklaration müssen
vorgenommen werden. Ein Kompilierzeitfehler wird erzeugt, wenn:
- 1) Der Methodendesignator einen MethodenNamen
der Ausbildung „Identifier" aufweist und der Methodendesignator
innerhalb einer „static" Methode, einem „static" Initialisierer oder
einem Initialisierer für
eine „static" Variable auftritt.
- 2) Wenn der Methodendesignator einen MethodenNamen der Form „TypeName.Identifier" aufweist.
-
Die
folgende Kompilierzeitinformation wird dann mit dem Methodenaufruf
zur Verwendung während
der Laufzeit verknüpft:
- 1) Eine Referenz auf die Kompilierzeitdeklaration (die
Methode).
- 2) Die Klasse oder das Interface, dass die Kompilierzeitdeklaration
beinhaltet (das Ziel).
- 3) Der Aufrufmodus, der wie folgt berechnet wird:
- a. Wenn die Kompilierzeitdeklaration den „private" Modifikator aufweist, dann ist der
Aufrufmodus „nonvirtual".
- b. Andernfalls, wenn der Teil des Methodenaufrufs vor der linken
Klammer in der Gestalt „super.Identifier" vorliegt, denn ist
der Aufrufmodus „super".
- c. Andernfalls, wenn die Kompilierzeit-Deklaration in einem
Interface auftritt, denn ist der Aufrufmudus „interface".
- d. Sonst ist der Aufrufmodus „virtual".
-
Laufzeit-Ausführung
-
Die
vorliegende Erfindung legt weiter eine Java-Virtuelle-Maschinen-Übersetzersoftware (1)
zur Implementierung von Delegaten der vorliegenden Erfindung vor.
-
Mehrere
Schritte sind erforderlich, um einen Delegateninstantiierungsausdruck
während
der Laufzeit auszuführen.
Zunächst
muss der Kompilierer die Verfügbarkeit
des Zieltyps und der Zielmethode bestimmen. Zweitens muss der Kompilierer
die aufzurufende Methode ausfindig machen, wobei die Möglichkeit
des Überschreibens
der Instanzmethode zu berücksichtigen
ist.
-
Laufzeit – Schritt
Nr. 1: Überprüfe Zugänglichkeit
von Typ und Methode:
-
Sei
C die den Delegateninstantiierungsausdruck enthaltende Klasse und
sei T die Klasse oder das Interface, dass die Kompilierzeitdeklaration
für den
Delegateninstantiierungsausdruck enthält und m sei der Name der Methode,
wie er zur Kompilierzeit bestimmt wurde.
-
Die
Java-Virtuelle-Maschinen-Übersetzersoftware 16 stellt
sicher, als Teil einer Verbindung („linkage"), dass die Methode m immer noch im
Typ T existiert. Wenn dies nicht wahr ist, wird eine „IllegalArgumentException" (welche eine Unterklasse
der Laufzeitausnahme „RuntimeExecption" ist) erzeugt. Wenn
der Aufrufmodus „interface" ist, dann muss die Virtuelle-Maschinen-Software 16 auch überprüfen, ob
der Zielreferenztyp immer noch das spezifizierte Interface implementiert.
Implementiert der Zielreferenztyp nicht mehr das Interface, dann
wird ein „IncompatibleClassChangeError" erzeugt.
-
Die
Virtuelle-Maschinen-Software 16 muss ferner sicherstellen,
dass während
der Verbindung der Typ T und die Methode m verfügbar sind.
-
Für den Typen
T:
- 1) Wenn T im gleichen Packet wie C ist,
dann ist T verfügbar.
- 2) Wenn T in einem anderen Packet als C ist und T ist „public", dann ist T verfügbar.
-
Für die Methode
m:
- 1) Wenn m „public" ist, dann ist m verfügbar. (Alle Elemente
von Schnittstellen sind „public" (s. §9.2 der
JLS)).
- 2) Wenn m „protected" ist, dann ist m
verfügbar, wenn
und nur wenn entweder T im selben Packet wie C ist oder C ist T
oder eine Unterklasse von T.
- 3) Wenn m den vorgegebenen (Packet-)Zugang aufweist, dann ist
m verfügbar
wenn und nur wenn T im selben Packet wie C ist.
- 4) Wenn m „public" ist, dann ist m
verfügbar
wenn und nur wenn C T ist.
-
Wenn
weder T noch m verfügbar
sind, dann wird eine „IllegalAcessException" produziert (s. §12.3 der
JLS).
-
Laufzeit – Schritt
Nr. 2: Lokalisiere die aufzurufende Methode:
-
Die
Strategie für
die Methodensuche hängt von
dem Aufrufmodus ab. Im vorliegenden Ausführungsbeispiel ist es nicht
möglich,
eine statische Methode mit einem Delegaten zukapseln, und daher
ist es klar, dass eine Instanzmethode von dem Delegaten gekapselt
werden muss und dass es eine Zielreferenz gibt. Wenn die Zielreferenz
null ist, wird zu diesem Zeitpunkt eine „NullPointerExeception" geworfen. Ansonsten
soll die Zielreferenz sich auf das Zielobjekt beziehen und wird
als Wert des Schlüsselwortes
this, wenn der Delegat aufgerufen wird, verwendet. Die anderen vier
Möglichkeiten
des Aufrufmodus werden als nächstes
von der Java-Virtuelle-Maschinen-Übersetzersoftware 16 berücksichtigt,
und zwar wie folgt.
-
Wenn
der Aufrufmodus „nonvirtual" ist, ist ein Überschreiben
nicht erlaubt.
-
Methode
m der Klasse T ist diejenige, die aufgerufen wird. Sonst ist der
Aufrufmodus „interface", „virtual" oder „super" und Überschreiben
kann vorkommen. Eine dynamischer Methodennachschlag wird verwendet.
Der dynamische Methodensuchprozess startet von Klasse S, die wie
folgt festgelegt wird:
- 1) Wenn der Aufrufmodus „interface" oder „virtual" ist, dann ist S
anfangs die aktuelle Laufzeitklasse R des Zielobjekts. Wenn das
Zielobjekt ein Feld ist, ist R die Klasse des Objektes. (Beachte,
dass für
den Aufrufmodus „interface", R notwendigerweise
T implementiert; für
den Aufrufmodus „virtual", ist R notwendigerweise
entweder T oder eine Unterklasse von T)
- 2) Wenn der Aufrufmodus „super" ist, dann ist S anfangs
die Oberklasse der Klasse C, die die Delegateninstantiierung enthält.
-
Die
dynamische Methodensuche verwendet folgende Prozedur, um Klasse
S und dann die Oberklasse von Klasse S, wenn benötigt, nach Methode m zu durchsuchen:
- 1) Wenn Klasse S eine Deklaration einer Methode mit
Namen m mit demselben Deskriptor (gleiche Anzahl von Parametern,
die gleichen Parametertypen, und den gleichen Rückgabetypen) wie vom Methodenaufruf
zur Kompilierzeit bestimmt (wie oben beschrieben) enthält, dann
ist dies die Methode die vom Delegaten gekapselt werden soll, und
die Prozedur terminiert.
- 2) Sonst, wenn S nicht T ist, wird die gleiche Nachschlagprozedur
unter Verwendung der Oberklasse von S ausgeführt; was immer herauskommt
ist das Ergebnis der Suche.
-
Diese
Prozedur wird eine brauchbare Methoden gefunden haben, wenn es die
Klasse T erreicht, da ansonsten eine „IllegalAccessException" durch die oben beschriebenen Überprüfungen geworfen worden
wäre. Es
wird vermerkt, dass der Suchprozess, während er hier explizit beschrieben
wurde, oftmals implizit implementiert sein wird.
-
Laufzeit – Schritt
Nr. 3: Sicherheitsüberprüfung:
-
Das
hier beschriebene Ausführungsbeispiel von
Delegaten weist Prozeduren zur Sicherstellung dessen auf, dass Delegaten
nicht verwendet werden, um vertrauenswürdigen Kode zu untergraben.
Ohne spezielle Sicherheitsvorkehrungen können vertrauensunwürdige Methoden
Delegate potentiell so verwenden, dass vertrauenswürdige Methoden
auf solche Art aufgerufen werden, dass keine vertrauensunwürdige Klasse
im Aufrufstapelspeicher erscheint. Bei normaler Java-Sicherheit
würde die
vertrauenswürdige
Methode erfolgreich sein, wenn keine vertrauensunwürdige Methode
im Aufrufstapelspeicher wäre.
Sie würde
potentiell fehlschlagen, wenn die originale vertrauensunwürdige Methode
im Aufrufstapelspeicher wäre.
Ein gutes Beispiel ist die Methode „ java.io.File.delete"; wenn ein vertrauensunwürdiger Kode
die Methode aufrufen würde,
würde eine
SicherheitsAusnahme („SecurityException") geworfen werden.
Wenn jedoch ein vertrauensunwürdiger Kode
diese vertrauenswürdige
Methode in einem Delegaten kapseln und einem vollständig vertrauenswürdigen Kode,
der dann den Delegaten aufrufen würde, übergeben würde, würde die Methode (java.io.File.delegate)
gelingen. Infolgedessen führt
das hier beschriebene Ausführungsbeispiel
eine spezielle Sicherheitsüberprüfung bei
Instantiierung des Delegaten durch, wie im Folgenden ausgeführt wird. Diese
Sicherheitsüberprüfung wird
während
der Laufzeit durchgeführt.
-
Das
hier beschriebene Ausführungsbeispiel der
Erfindung erweitert das Standard-Java-Sicherheitsmodell um die Zuordnung
einer Menge von Sicherheitspermissions für jede Javaklasse. Für eine gegebene
Klasse bestimmt der zugeordnete Satz von Sicherheitspermissions
die vertrauenswürdigen Operationen
(wie z. B. Datei-Input-Output und Drucken), die die Methoden erlaubt
sind auszuführen. Vertrauensunwürdige Klassen
weisen einen leeren Satz von Sicherheitspermissions auf, während voll vertrauenswürdige Klassen
einen vollständigen
Satz von Sicherheitspermissions aufweisen (d.h. sie weisen jede
Permission auf).
-
Um
als Delegateninstantiierung während
der Laufzeit erfolgreich zu sein, müssen die Permissions von C
(der Klasse, die die Delegateninstantiierung beinhaltet) eine Obermenge
der Permissions, die von der m beinhaltenden Klasse besessen werden,
sein, was während
der Laufzeit bestimmt wird. Wenn dies nicht der Fall ist, wird eine „IllegalAccessException" geworfen. Ferner
erfordert die Sicherheitsüberprüfung, dass
Klassen, die von einem Delegaten oder einer Multidelegatenklasse
herrühren „final" sind, was eine Suche
in der Aufrufkette zum Erkennen der Einheit, welche den Delegaten
erzeugt, erlaubt. Eine direkte Konsequenz ist es, dass vertrauensunwürdige Klassen
nur Delegate auf vertrauensunwürdige
Methoden erzeugen können,
während
vollständig
vertrauenswürdige
Klassen (d.h. Systemklassen) Delegaten auf jede verfügbare Methode
erzeugen können.
-
Kompilierer-
gegen Interpretiereraktivitäten
-
Wie
oben beschrieben sind die Kompilierer- 14 und Interpretierersoftware 16 separate
Softwarekomponenten. Jedoch können
diese Komponenten in einigen Fällen
die gleichen Funktionen ausführen, wie
das Einbinden von Delegaten, abhängig
von dem Modus der Operation. Ferner können die Funktionen der Kompilierer- 14 und
der Interpretierersoftware 16 in einigen Fällen ausgetauscht
oder miteinander geteilt werden. Demzufolge ist die Erfindung in
keinster Weise auf die hier beschriebene bestimmte Unterteilung
von Funktionen im Ausführungsbeispiel
beschränkt.
Die Kompilierer- 14 und Interpretierersoftware 16 sollte
man sich eher so vorstellen, als teilten sie die gemeinsame Aufgabe
der Ausführung
objekt-basierten Computerkodes zur Vorbereitung seiner Ausführung von
einem Computer.
-
Die Klasse „com.ms.lang.Delegate"
-
Wie
oben ausgeführt
rühren
alle Delegaten von dieser Basisklasse „Delegate" her. Ein Objekttyp „Delegate" kapselt eine aufrufbare Einheit: Eine
Instanz und eine Methode einer Instanz. Eine Beispieldefinition 38 der
Klasse „Delegate" ist in 6 gezeigt.
-
Der „protected
Delegate(Object target, String methodName)"-Konstruktor initialisiert ein neu erzeugtes „Delegate"-Objekt so, dass
es das durch Argumente spezifizierte Ziel und die Methode kapselt. Wenn „target" Null ist, wird eine „NullPointerException"-Ausnahme ausgegeben.
Das „methodName"-Argument muss eine
Methode bestimmen, die passend, verfügbar und geeignet, wie oben
definiert, ist. Wenn keine übereinstimmende
Zielmethode gefunden werden kann, wird eine „IllegalArgumentException"-Ausnahme zur Laufzeit geworfen. Wenn
eine Methode nicht gefunden werden kann, wird ein „NoSuchMethodError" geworfen.
-
Das
Ergebnis von „public
final boolean equals (Object obj)" ist „true", wenn und nur wenn das Argument nicht „null" und ein „Delegate"-Objekt ist, dass
die gleiche Zielmethode und Aufrufliste wie dieses Delegatenobjekt
aufweist. Es ist zu beachten, dass zwei Delegaten nicht vom selben
Typ sein müssen,
um sich zu entsprechen, wie unter „Delegate.equals" definiert. Dieser
Ausdruck überschreibt
die „equals"-Methode des Objekts
(s. §20.1.3
der JLS).
-
Der „Ausdruck
public static final Delegate combine(Delegate a, Delegate b)" kombiniert die beiden
gegebenen Delegate zur Ausbildung eines einzigen Delegaten. Wenn
sowohl a als auch b Null sind, ist auch das Ergebnis Null. Wenn
entweder a oder b Null sind, ist das Ergebnis der Delegat, der nicht
Null ist. Wenn weder a noch b Null sind, ist das Ergebnis ein neuer
Delegat mit einer neuen Aufrufliste, die aus der Aneinanderhängung der
Aufruflisten von a und b, in dieser Reihenfolge, gebildet wird.
Es wird nicht als Fehler angesehen, wenn die Aufrufliste doppelte
Einträge – ein Eintrag,
der auf dieselbe Methode auf dasselbe Objekt verweist – aufweist.
Wenn weder a noch b Null sind, dann müssen die Delegaten von gleichem
tatsächlichem
Typ sein. Ansonsten wird die Ausnahme „IllegalArgumentException" vom Kompilierer 14 geworfen.
Wenn weder a noch b Null sind, dann müssen die Delegaten a und b
von einem tatsächlichen
Typ sein, der von der Klasse „MulticastDelegate" abstammt. Andernfalls
wird die Ausnahme „MulticastNotSupportedException" vom Kompilierer 14 geworfen.
-
Der
Ausdruck „public
static final Delegate combine(Delegate⌷delegates)" gibt einen Delegaten mit
einer Aufrufliste zurück,
die aus der Aneinanderhängung
der Aufruflisten von jedem der Delegaten im Delegatenfeld, in Reihenfolge,
gebildet wird. Ist die resultierende Aufrufliste leer, ist das Ergebnis
Null. Das Delegatenfeld kann Einträge von Null aufweisen; solche
Einträge
werden ignoriert. Wenn der Delegatenfeldparameter Null oder leer
ist, ist das Ergebnis Null. Die Elemente des Delegatenfeldes, die
nicht Null sind, müssen
vom selben tatsächlichen
Typ sein; ansonsten wird eine Ausnahme „IllegalArgumentException" geworfen. Existieren
mehrere Elemente im Delegatenfeld, die nicht Null sind, dann müssen diese
Elemente von einem tatsächlichen
Typus sein, der aus der Klasse „MulticastDelegate" stammt. Ansonsten
wird eine „MulticastNotSupportedException"-Ausnahme vom Kompilierer 14 geworfen.
-
Der
Ausdruck „public
static final Delegate remove(Delegate source, Delegate value)" gibt einen Delegaten
mit einer Aufrufliste zurück,
die durch Löschen
des letzten Auftretens (wenn vorhanden) des durch den Wertparameter
gegebenen Delegaten aus der Aufrufliste des durch den Quellenparameter
bestimmten Delegaten gebildet wird. Der Delegat, der von der Aufrufliste
herausgenommen wird, ist der letzte Delegat, für den der folgende Ausdruck
wahr ist: "value.equals(delegate)", wobei Delegat das
in Frage kommende Aufruflistenelement ist. Wenn der Wertparameter
Null oder wenn der durch den Wertparameter gegebene Delegat nicht
in der Aufrufliste der Quelle erscheint, ist das Ergebnis der Quellenparameter.
Wenn die resultierende Aufrufliste leer ist, ist das Ergebnis Null.
Wenn der Quellenparameter Null ist, ist das Ergebnis Null.
-
Der
Befehl „public
final Object dynamicInvoke(Object⌷args)" kann zum Aufruf der Methode in spät-gebundener
Art verwendet werden („Delegate.dynamicInvoke"). Bei Aufruf löst das „dynamicInvoke"-Element die Ausführung der
Methode aus, die der Delegat gekapselt aufweist, wobei die Elemente der „args"-Argumente als Argumente
an die Methode übergeben
werden. Das Ergebnis von „dynamicInvoke" ist das Ergebnis
des Aufrufs der gekapselten Methode. Wenn das Ergebnis des Methodenaufrufs
eine Objektreferenz ist, wird diese Referenz zurückgegeben. Im anderen Falle,
wenn das Ergebnis ein primitiver Wert ist, dann wird der primitive
Wert in ein Objekt gehüllt
und zurückgegeben
(z.B. ein boolescher Wert wird in ein boolesches Objekt gehüllt). Im
anderen Falle ist der Rückgabetyp
der Methode „void", und ein den „null"-Wert enthaltendes
Objekt wird zurückgegeben.
-
Die „public
final static Method getMethod ()"-Methode
gibt eine Referenz auf ein eindeutiges Methodenobjekt zurück, dass
die Methode des (Ziel, Methoden)-Paares ist.
-
Die „public
final static Object getTarget ()"-Methode
gibt eine Referenz auf die Instanz des Teils des (Instanz, Methoden)-Tupels
zurück,
das zur Kapselung einer aufrufbaren Einheit notwendig ist.
-
Die „public
Delegate⌷getInvocationList ()"-Methode gibt die Aufrufliste dieses
Delegaten zurück,
in Aufrufreihenfolge. Für
Nicht-Multicast-Delegate ist das Ergebnis immer ein Feld mit einem
einzigen Element. Für
Multicast-Delegate, wie unten beschrieben, kann das resultierende
Feld mehr als ein Element aufweisen. Bei der Aufrufliste jedes der
Elemente im zurückgegebenen
Feld ist sichergestellt, dass sie genau einen Eintrag aufweist.
-
Multicast-Delegate
-
Alle
Multicast-Delegate rühren
aus der Klasse „MulticastDelegate" her, in diesem Java-Ausführungsbeispiel
bezeichnet als „com.ms.lang.MulticastDelegate". Der Effekt des
Aufrufens eines Multicast-Delegaten
könnte
sein, dass mehrere Methoden aufgerufen werden. Die Menge der Methoden,
die durch die „invoke"-Methode des Delegaten
aufgerufen werden, wird als die Aufrufliste des Delegaten bezeichnet,
und diese kann durch Benutzung der „getInvocationList"-Methode erhalten
werden.
-
Ein
Delegat, der direkt von „Delegate" abstammt, wird immer
eine Aufrufliste mit einem Element aufweisen – sich selbst. Ein Delegat,
der direkt von der „MulticastDelegate"-Klasse abstammt,
kann eine Aufrufliste mit mehr als einem Element aufweisen. Die
Methoden „Delegate.combine" und „Delegate.remove" werden zur Erzeugung
neuer Aufruflisten verwendet.
-
Die „invoke"- und „dynamicInvoke"-Methoden rufen als
Gruppe jeden der Delegaten in der Aufrufliste durch Aufruf mit der übergebenen
Argumentenliste auf. Die Delegaten werden synchron aufgerufen, in
der Reihenfolge, in welcher sie in der Aufrufliste auftreten. Wenn
einer der Delegaten eine Ausnahme zurückgibt, dann wird der Multicast
beendet und die Ausnahme zum Verwender der Aufrufmethode weitergeleitet.
Während
ein Multicast im Fortgang ist, können
Aufrufe zu „Delegate.combine" und „Delegate.remove" auftreten. Solche
Aufrufe berühren
die durch den bereits fortschreitenden Multicast verwendete Aufrufliste
nicht.
-
Im
vorliegenden Ausführungsbeispiel
der Erfindung ist die Klasse „MulticastDelegate" 40, wie
in 7 dargestellt, definiert.
-
Der
Ausdruck „protected
MulticastDelegate next" stellt
eine interne Referenz auf den nächsten Delegaten
in der Multicast-Kette dar. Damit eine korrekte Aufrufsequenz erhalten
bleibt, muss der nächste
Delegat aufgerufen werden bevor die Methode aufgerufen wird, die
dieser Delegat kapselt.
-
Der „protected
MulticastDelegate(Object target, String methodName)" Konstruktor arbeitet
wie die korrespondierenden Konstruktoren, die in der „Delegate"-Basisklasse definiert
sind.
-
Ein
alternatives Ausführungsbeispiel
des Aufrufs eines Multicast-Delegaten ist es, ein Feld von Delegaten
aufzubauen, welche durch Durchlaufen der jeweils „nächsten" Kette des Multicast-Delegaten aufzurufen
sind. Diese Liste könnte
auf dem Stapelspeicher des Programms platziert werden. Die Virtuelle-Maschinen-Interpretiersoftware 16 durchschreitet
dann dieses Feld in umgekehrter Reihenfolge und ruft die Aufrufmethode
jedes Delegaten mit den Parametern auf, die der Multicast-Delegaten-Aufruf-Methode übergeben
wurden. Der Vorteil dieser Technik ist es, dass der Programmstapelspeicher
nicht so tief sein muss, da sich maximal nur zwei Kopien der Parameter,
die an die Aufruf-Methode des Multicast-Delegaten übergeben
werden, auf dem Stapelspeicher befinden.
-
Weiteres Ausführungsbeispiel:
Einhüllen
(„wrapping") statischer Elemente
-
In
einem alternativen Ausführungsbeispiel der
Erfindung, ist die J++ Sprache zusätzlich um das Einhüllen von
statischen Elementen einer Klasse erweitert. Für diesen Zweck ist ein „Klassen"-Konstruktor, dessen
Gestalt durch die „Delegate"-Basisklasse definiert
ist, vorgesehen. Die Argumente für
diesen Konstruktor, eine Klasse und ein String-Methodenname, kombiniert
mit dem Typen des Delegaten, spezifiziert ein statisches Element
der angegebenen Klasse, die der Delegatendefinition entspricht.
Der neu konstruierte Delegat kapselt die statische Methode ein.
Dieser Konstruktor nimmt die folgende Gestalt an:
protected
Delegate(Class class, Sting methodName)
-
Dieser
Konstruktor initiiert ein neu erzeugtes Delegatenobjekt so, dass
es die durch die Argumente spezifizierte statische Methode kapselt.
Wenn die Klasse Null ist, wird eine „NullPointerException"-Ausnahme ausgelöst. Das „methodName"-Argument muss eine
Methode benennen, die passend, verfügbar und geeignet ist, wie
oben definiert. Wenn keine entsprechende Zielmethode gefunden werden
kann, wird eine Ausnahme „IllegalArgumentException" zur Laufzeit geworfen.
Wenn gar keine solche Methode gefunden wird, wird eine „IllegalArgumentException" geworfen.
-
Im
ersten Schritt zur Kompilierzeit muss für Delegate, die statische Elemente
einhüllen,
die zu durchsuchende Klasse oder das Interface bestimmt werden.
Wenn es sich um einen qualifizierten Namen der Form „TypeName.Identifier" handelt, dann ist
der Name der Methode der „Identifier" und die zu durchsuchende
Klasse ist diejenige mit dem Namen „TypeName". Wenn „TypeName" der Name eines Interfaces denn einer
Klasse ist, tritt ein Kompilierzeitfehler auf, weil diese Form nur
statische Methoden benennen kann und Interfaces keine statische
Methoden aufweisen. Es ist auch notwendig, wie im Falle des Einhüllens von
Instanzenelementen einer Klasse, zu bestimmen, ob die Methode geeignet
ist. Wenn es eine passende und verfügbare Methodendeklaration für einen
Delegateninstantiierungsausdruck gibt, wird er die Kompilierzeit-Deklaration
für den
Delegateninstantiierungsausdruck genannt.
-
Weitere Überprüfungen müssen auf
die Kompilierzeit-Deklaration vorgenommen werden. Wenn der Methodendesignator
einen MethodenNamen der Form „Identifier" aufweist, und der
Methodendesignator in einer statistischen Methode oder in einem
statischen Initialisierer oder in einem Initialisierer für eine statische
Variable auftritt, tritt ein Kompilierzeitfehler auf. Ansonsten
ist das Einhüllen
von statischen Elementen eine Erweiterung oder Modifikation zum
oben beschriebenen Einhüllen
von Instanzenelementen.
-
Gemäß diesem
alternativen Ausführungsbeispiel
ist ein entsprechender Konstruktor auch für die Multicast-Delegaten-Klasse
vorgesehen:
protected MulticastDelegate(Class class, String
methodName).
-
Dieser
Konstruktor arbeitet wie der entsprechende, in der „Delegate"-Basisklasse definierte Konstruktor.
-
Aufrufbare
Schnittstellen
-
Microsoft® Visual
J++ unterstützt
vielfache Vererbung nicht. Jedoch wird manchmal eine solche Schnittstelle
nötig sein,
um den existierenden Kode auf einen Delegatentypen zu konvertieren.
Diese Situation kann zu „java.lang.Thread" gegen „java.lang.Runnable" analogisiert werden.
Um dieses Problem anzugehen, erweitert ein Ausführungsbeispiel der Erfindung
Microsoft® Visual
J++ um die Einbeziehung einer „com.ms.lang.Callable" Schnittstelle 42,
wie sie in 8 gezeigt wird. Bevorzugterweise implementieren
Delegaten diese Schnittstelle.
-
DELEGATENBASIERTES
EREIGNISMODELL
-
Im
objekt-orientierten Programmieren ist eine Ereignis („Event") ein Konstrukt,
welches einer Ereignisquelle („event
source") ermöglicht,
einen Ereignis-Listener
(oder einer Menge von Ereignis-Listenern) anonym zu unterrichten,
dass ein Zustandsübergang
irgendeiner Art erfolgt ist. Ereignisse werden typischerweise in
einem größeren „Komponentenmodell" genannten System verwendet,
das beschreibt, wie eine Komponente ihren Funktionalitätskode,
der die Komponente verwendet, zur Verfügung stellt. Auf niedrigster
Ebene beschreiben die meisten modernen Komponentenmodelle, wie eine
Komponente ihre Eigenschaften (die den Zustand der Komponente beschreiben),
ihre Methoden (die Aktionen zur Verfügung stellen, die die Komponente
in der Lage ist auszuführen)
und Ereignisse (Benachrichtigungen der Komponente, dass etwas interessantes
passiert ist) zur Verfügung
stellt.
-
Ein
Ereignisaufruf ist wie ein rückwärtiger Methodenaufruf.
Bei einem Methodenaufruf ruft ein Komponentenkonsument eine Methode
der Komponente auf, eine Anzahl von Argumenten übergebend. Bei einem Ereignis
erfolgt der Aufruf in umgekehrter Reihenfolge – die Komponente ruft eine
vom Komponentenkonsumenten zur Verfügung gestellte Methode auf,
eine Anzahl von Argumenten übergebend. Neben
dem Unterschied in der Richtung des Aufruf gibt es noch einige andere
hervorstechende Unterschiede.
-
Bei
einem Methodenaufruf weist der Konsument typischerweise eine explizite
Abhängigkeit
von der Komponente, die aufgerufen wird, auf. Ereignisaufrufe sind
dagegen typischerweise anonym – die Komponente
weist keine explizite Abhängigkeit
vom Komponentenkonsument auf. Stattdessen können Listener sich mit einem
Ereignis oder einer Menge von Ereignissen unter Verwendung eines
Mechanismus, der von der Ereignisquelle zur Verfügung gestellt wird, verbinden.
-
Ferner
bestimmt bei einem Methodenaufruf der Angerufene die Gestalt der
Methode – die
Anzahl und Typen von Argumenten und den Rückgabetyp. Bei einem Ereignisaufruf
bestimmt die Ereignisquelle (der Aufrufende) die Gestalt.
-
Im
Ausführungsbeispiel
des delegaten-basierten Komponentenmodells gemäß der vorliegenden Erfindung
wie unten ausgeführt
werden Ereignisse typischerweise zur Kommunikation zwischen einer
Komponente (oftmals von einen kommerziellen Anbieter geschrieben)
und einem Ereignishandhabungskode („event handling code") (geschrieben von einem
Entwickler, der die Komponente gebraucht) verwendet. Das Ausführungsbeispiel
des Komponentenmodells wie unten beschrieben unterstützt sowohl
single-cast als auch multi-cast Ereignisse.
-
Klassen und
Schnittstellen
-
Im
Folgenden wird dargelegt, wie Ereignisse gemäß dem vorliegenden Ausführungsbeispiel
der Erfindung zur Verfügung
gestellt und ausgenutzt werden können.
Mehrere Klassen werden definiert und im Folgenden diskutiert:
- 1) Die Klasse „Event". Des Ereignispackets interessierender
Zustand in einem Ereignisobjekt. Alle solche Ereignisobjekte leiten
sich von der Event-Klasse her. Die Event-Klasse wird also direkt
für parameterlose
Ereignisse verwendet.
- 2) Der Delegat „EventHandler", der zusammen mit
der Klasse „Event" verwendet wird.
- 3) Die Klasse „CancelEvent", welche für stornierbare
Handlungen verwendet wird. Bevor die Handlung vorgenommen wird,
unterrichtet die Ereignisquelle interessierte Listener davon, dass
die Handlung unmittelbar bevor steht. Jeder Listener hat die Möglichkeit,
die Handlung zu „stornieren".
- 4) Der Delegat „CancelEventHandler", die zusammen mit
der Klasse „CancelEvent" verwendet wird.
-
Auslösen von
Ereignissen
-
Um
Ereignisse auszulösen
macht die Ereignisquelle eines oder mehreres des Folgenden:
- 1) Definieren einer Event-Klasse;
- 2) Definieren eines „event
handler" Delegaten;
- 3) zur Verfügung
stellen der Methoden „add" und „remove", um eine Ereignisquelle
in die Lage zu versetzen, sich mit einem Ereignis zu verbinden und
nachfolgend davon zu trennen; oder
- 4) zur Verfügung
stellen einer „Ereignis
auslösen" („event
raising") Methode,
die ein Ereignis auslöst.
-
Definieren
einer Eventklasse
-
Dies
ist eine Klasse, die sich von der Klasse „Event" ableitet und potentiell Kontextinformationen hinzufügt. Es ist
oftmals nicht notwendig eine neue Eventklasse zu definieren, da
eine existierende Eventklasse verwendet werden kann.
-
Eventklassen
werden nach Konvention verwendet. Es ist möglich Ereignisse zu definieren
und zu verwenden, die Eventklassen überhaupt nicht benutzen. Das
vorliegende Beispielkomponentenmodell empfiehlt das Verpacken von
Ereignissen als Objekte um Ereignisverdrahtungsszenarios („event
wiring scenarios")
and Versionierung („versioning") zu ermöglichen.
Diese Punkte werden im Folgenden diskutiert. Bei Mausereignissen,
die durch eine „Window"-Klasse ausgelöst werden,
möge die
Eventklasse, wie in 9 gezeigt, definiert werden.
-
Definition eines „event
handler" Delegaten
-
Jeweils
ein Delegat wird für
jede interessierende Menge von Ereignissen verwendet. Solch ein Delegat
kann von mehreren Ereignissen verwendet werden. Nach Konvention
werden „Event-Handler"-Delegate so definiert,
dass sie der folgenden Struktur entsprechen:
public multicast
delegate void <Name>Handler (Object source, <EventType>e);
Für das Mausereignis
des Beispiels 44 von 9 impliziert
dies:
public multicast delegate void MouseEventHandler(Object
source, MouseEvent e);
Das Ausführungsbeispiel des hier beschriebenen
Ereignishandhabungsmodells stellt „add" und „remove" Methoden zur Verfügung, die es einer Ereignisquelle ermöglichen,
sich mit einem Ereignis zu verknüpfen und
nachfolgend wieder davon zu lösen.
Nach Konvention sind „Event-Handler"-Delegate so definiert, dass
sie der folgenden Struktur entsprechen:
public void
addOn<EventName>Handler(<EventHandlerType> handler);
public
void
removeOn<EventName>Handler(<EventHandlerType> handler);
Für das Mausereignisbeispiel
impliziert dies:
public void addOnMouseMoveHandler(MouseEventHandler
handler);
public void removeOnMouseMoveHandler(MouseEventHandler
handler);
Bereitstellung einer "event raising" Methode, die ein Ereignis auslöst
-
Diese
Methode ermöglicht
abgeleiteten Klasse Ereignisse auszulösen, ein Ereignis zu löschen, welches
in der Basisklasse ausgelöst
wurde, und Pre- und Post-Bearbeitung zu ermöglichen, ohne Beeinträchtigung
von Komponentenkonsumenten. Der Hauptteil der Methode löst das Ereignis
aus, das „Delegate.invoke" auf das betreffende
delegatenwertige Objekt aufzurufen, die Argumente zu übergeben
und „this" als Wert des Quellenparameters
für das
Ereignis hinzuzufügen.
Für „final"-Klassen gibt es
keine Notwendigkeit solche Methode vorzusehen, da es abgeleiteten
Klassen hier nicht möglich
ist zu existieren. Nach Konvention sind „Event-Handler"-Delegate so definiert, dass sie der
folgenden Struktur entsprechen:
protected void on<EventName>(<EventType> e);
Für das Mausereignisbeispiel
ist die Methode 46, wie in 10 dargestellt,
impliziert.
-
Handhabung
von Ereignissen
-
Um
Ereignisse abzuhören,
tut ein Ereignis-Listener das Folgende:
- 1)
Lernen, welche Ereignisse einer Komponente zur Verfügung stehen,
durch Benutzung der Klasse ComponentManager, welche in der JLS beschrieben
ist.
- 2) Erzeugen einer „Event-Handler"-Funktion mit der
korrekten Signatur, wie sich durch den Delegaten definiert ist,
der für
die entsprechende Ereignissache verwendet wird, z.B. wie im Kode 48 der 11 gezeigt.
- 3) Verbinden des „Event-Handling"-Kodes mit dem Ereignis
durch Aufrufen der „add"-Methode der Ereignisquelle,
z.B. wie gezeigt im Kode 50 der 12.
- 4) Optional, Lösen
des „Event-Handling"-Kodes vom Event
durch Aufrufen der „remove" Methode, erzeugt
durch die Quelle, wie beispielsweise gezeigt im Kode 52 der 13.
In den meisten Fällen
gibt es keinen Grund die Verbindung explizit zu lösen.
-
Erstes Beispiel des Ereignismodells
im J++ Kode – Das „Box"-Beispiel
-
14A-14B illustrieren den J++ Beispielkode 54,
die Anwendung des oben beschriebenen Delegaten- und Ereignismodels
zeigend. In den 14A-14B sind
eine Mausereignisklasse und ein Mausereignishandhaber-Delegat deklariert.
Eine „Box"-Klassendeklaration
wird ausgeführt,
sowie eine Darstellung eines Komponentenkonsumenten, welcher zwei „Box"-Instanzen verwendet.
-
Ein weiteres Beispiel
eine Ereignismodells im J++ Kode – Das „Smiley"-Beispiel
-
Ein
einfaches Ereignisbeispiel, wobei ein Ereignis ohne Parameter ausgelöst wird,
ist in den 15A-15B dargestellt.
Im J++ Programm 56 der 15A-15B, löst
die „Smiley"-Aufsicht ein Ereignis
aus, wenn sich ihre „happy" Eigenschaft ändert.
-
Single-Cast
und Multi-Cast Ereignisse
-
Das
oben beschriebene Ereignismodell arbeitet gleich gut für single-cast
und multi-cast Szenarios. Nach bevorzugter Konvention dieses hier
dargelegten Ausführungsbeispiels
sind alle Ereignisse multi-cast. Die Definition eines single-cast
Ereignisses ist einfach genug vorzunehmen, durch Auslassung des
Multicast-Modifikators in der Delegatendeklaration. Ereignisse können sowohl
in Klassen- als auch in Schnittstellendefinitionen auftreten. Es
ist möglich
eine Menge von Ereignissen in einer Schnittstelle zu definieren
und mehrere Klassen die Schnittstelle zu unterstützen zu haben.
-
Der
Hauptunterschied zwischen einem Multicast-Ereignis-Modell und einem
Singlecast-Ereignis-Model liegt darin wie die Verbindung vorgenommen
wird. Bei multi-cast ist es notwendig einen Multicast-Delegaten
zu verwenden und die „addOn" und „removeOn"-Methoden zu benutzen.
Für single-cast ist
es möglich
einen nicht-mulitcast Delegaten zu verwenden und Verbindung/Trennung
mit einem Ereignis, vorgenommen durch Eigenschaften-„sets" and „gets", zu ermöglichen.
Das heißt,
eine Komponente weist eine delegatenwertige Eigenschaft für jedes der
Ereignisse auf, die sie zur Verfügung
stellt. Zum Beispiel beim MouseMove-Ereignis, würde eine Komponente ein „foo"-Ereignis zur Verfügung stellen, welches
ein „OnFoo"-Eigenschaft zur
Verfügung
stellen würde,
wie im Kode 58 in 16 dargestellt.
-
Die Klasse
Event
-
Die
Klasse Event dient mehreren Zwecken:
- 1) Sie
agiert als Basisklasse für
alle Ereignisse. Nach Konvention verwenden Ereignisse von Komponenten
einen einzigen Parameter, dessen Typ eine Klasse ist, die sich von
der Klasse Event herleitet.
- 2) Sie wird für
Ereignisse verwendet, die keinen Zustand haben. Das statische Element
EMPTY wird gewöhnlicherweise
zu diesem Zwecke genutzt und erlaubt es, ein Ereignis effizient
auszulösen,
wie beispielhaft im Kode 60, dargestellt in 17,
dargelegt.
-
Der Delegat „EventHandler"
-
Der
Delegat „EventHandler" wird für Ereignisse
verwendet, die einen einzigen Parameter des Typs Event akzeptieren,
wie im Kode 62 der 18 gezeigt.
-
Die Klasse „CancelEvent"
-
Die „CancelEvent"-Klasse wird für Ereignisse
verwendet, die sich auf eine Handlung beziehen, die storniert werden
kann. Die „CancelEvent"-Klasse weist ein
boolesches Feld mit dem Namen „cancel" auf, das aussagt,
ob die in Frage kommende Handlung storniert werden muss. Ein „true"-Wert weist darauf
hin, das die Handlung storniert wurde. Ein „false"-Wert weist darauf hin, dass die Handlung
nicht storniert wurde. Ein anfänglicher
Wert für „cancel" kann unter Verwendung
des bereitgestellten Konstruktors erzeugt werden. Typischerweise
wird „cancel" auf „false" gesetzt, so dass,
wenn das Ereignis nicht behandelt wurde oder wenn der Event-Handler das „cancel"-Feld nicht verändert hat, die Ereignisquelle
annimmt, dass die Erlaubnis zur Ausführung der Handlung gegeben
wurde. Die „CancelEvent"-Klasse ist im Kode 64 der 19 gezeigt.
-
Der Delegat „CancelEventHandler"
-
Der
Delegat „CancelEventHandler" wird für Ereignisse
verwendet, die einen einzigen Parameter des Typs „CancelEvent" akzeptieren, wie
im Kode 66 der 20 dargelegt.
-
Anderweitige
Verwendung von Delegaten
-
Wie
oben beschrieben sind Delegate im Allgemeinen nützlich, insbesondere für ereignisbezogene
Szenarios. Jedoch sind sie auch nützlich für nicht-ereignisorientierte
Szenarios, wie den Austausch des „Callback"-Mechanismus in „J/Direct" und „lazy invocation".
-
Anwendung der Erfindung
auf andere Programmiersprachen und -systeme/Alternative Ausführungsbeispiele
-
Das
hier beschriebene Ausführungsbeispiel der
Delegaten- und Ereignismodelle der vorliegenden Erfindung sind als
Erweiterung von Microsoft® Visual J++ ausgedrückt worden.
Jedoch ist es verständlich,
dass die hier beschriebene Erfindungen auch weitläufig auf
andere objekt-orientierte Programmiersprachen, wie z. B. Microsoft® Visual
Basic oder andere Versionen von J++ erhältlich von der Microsoft Corporation
oder anderen Entwicklern, anwendbar sind.
-
Des
Weiteren sind die hier dargelegten Erfindungen anwendbar auf und
nehmen die Form an von Computerprogrammen, geschrieben im erfindungsgemäßen Kode
der vorliegenden Erfindung, und Kompilierern wie auch Laufzeit-Virtuellen-Maschinen und
anderen Programmiersystemen, welche Computerprogramme in auf einem
Computer ausführbare Befehle übersetzen.
-
Die
Erfindung ermöglicht
ferner das Delegat- und Ereignismodelle in Gestalt von physikalischem Computerkode
ausgebildet sein können,
in oder auf einem Träger,
wie z.B., aber nicht darauf beschränkt, Festplatten, flexible
Disketten, Computerspeicher oder elektronischen Signalen, welche
zum Transport eines solchen Kodes von einem Computer oder einem
Speichermedium zu einem anderen verwendet werden könnten.
-
Ferner,
obgleich die hier offenbarten Ausgestaltungen in Software implementiert
sind, sind die hier ausgeführten
Erfindungen in keinster Weise ausschließlich auf Implementierung in
Software beschränkt,
und daher werden ausdrücklich
Implementierungen in Firmware und silikon-basierter oder anderer
Formen von festverdrahteter Logik oder Kombinationen von festverdrahteter
Logik, Firmware und Software oder jedem geeigneten Substitut davon
genannt.