4. Vererbung
Wenn eine Oberklasse ihre Attribute unter private deklariert
hat, dann sind sie eben auch nicht für die abgeleiteten Unterklassen
sichtbar. Da sie aber natürlich dort gebraucht werden, sieht DELPHI dafür
die Anweisung protected vor.
Wenn ein Exemplar einer Unterklasse erzeugt wird, muss man dafür
sorgen, dass das Exemplar, das aus der Oberklasse geerbt wird, ebenfalls
mit erzeugt wird. (Sonst gibt es einen Absturz.) Für ererbte Teile gibt es
die Anweisung inherited.
TPatient = class (TPerson)
private
Krankenkasse: string[25];
...
public
constructor Create; override;
// weil der geerbte überschrieben wird
...
end;
....
constructor TPatient.Create;
(*
-------------------------------------------------------------------- *)
begin
inherited Create;
// die Person erzeugen, die man erbt
Init;
end;
|
|
6. Assoziation
Für die Assoziation gilt:
Während in der OOA vorzugsweise zunächst
nur ungerichtete Assoziationen gezeichnet werden sollten, |
|
kann im Entwurf
eine gerichtete Assoziation bereits Hinweise auf die Richtung des
Zugriffs und die Art der
Implementation geben. Beispiel: Dem Kunden wird ein Kundenbetreuer zugeordnet. |
|
a) gerichtete Assoziation
Dazu ist es erforderlich, dass in der Klientenklasse
-
ein Referenzattribut auf das Serverobjekt vereinbart wird
-
Methoden eingeführt werden, die die Beziehungen aufbauen und
wieder entfernen können.
TKunde = class (TObject)
private
Nummer :
integer;
Name :
string[15];
Betreuer:
TBetreuer;
// Referenzattribut
public
constructor
Create;
procedure
SetLink (betr : TBetreuer);
// Verbindung herstellen
function
GetLink : TBetreuer;
// Ref. holen
procedure
RemoveLink;
// Ref. löschen
procedure
SetNummer (n: integer);
...
end; |
Die Set-/GetMethoden werden wie üblich implementiert; RemoveLink so:
procedure RemoveLink;
(* -------------------------------------------------------------------- *)
begin
Betreuer:= NIL;
end; |
Bis hierher sind das alles nur die technischen Vorbereitungen
(Deklaration). Die Verknüpfung selbst wird in einer operativen Einheit (z.
B. GUI) bei Bedarf hergestellt
TFensterFrm = class(TForm)
SteuerPnl : TPanel;
...
private
Kunde : TKunde; //
Referenzattribute auf die Objekte
Betreuer: TBetreuer;
procedure Init;
..
end;
IMPLEMENTATION
...
procedure TFensterFrm.ErfassenBtnClick(Sender: TObject);
(* --------------------------------------------------------------- *)
begin
Kunde := TKunde.Create;
//
Clientobjekt erzeugen
Betreuer := TBetreuer.Create;
// Serverobjekt erzeugen
PersDatenAktualisieren;
// Daten aus
dem Fenster übernehmen
BetreuerDatenAktualisieren; // ""
Kunde.SetLink(Betreuer);
// Assoziation
herstellen
Kundenliste.Append (Kunde); //
komplett in die Liste übernehmen
end; |
Das Lesen und Anzeigen des dazugehörigen Betreuers läuft wieder über
die Referenz in Kunde:
procedure TFensterFrm.ErsterBtnClick(Sender: TObject);
(* ------------------------------------------------------------------- *)
begin
Kundenliste.First;
Kunde := Kundenliste.GetElement;
KundenMaskeAktualisieren;
Betreuer:= Kunde.GetLink;
// Referenz auf das Serverobjekt holen
BetreuerMaskeAktualisieren;
// und jetzt kann es angezeigt werden
end; |
b) bidirektionale Assoziation
Dieser Abschnitt
ist bisher aus guten Gründen nicht veröffentlicht worden, soll aber wegen
mehrfacher Nachfragen angefügt werden. In leicht lesbaren Einführungen
heißt es oft, in der OOP treten die Objekte mit einander in Beziehung und
tauschen Nachrichten aus. Das suggeriert, daß die Objekte scheinbar
beliebig in Kontakt treten können und sich gegenseitig kennen. Wäre dem
so, dann könnte man in einem Programm unterschiedliche Objekte einfach
aufeinander loslassen und die erledigen dann irgendwas. Eine derartige
Programmstruktur könnte man (temporär) netzartig nennen. Leider ist das
nicht so einfach. Wenn zwei Objekte sich gegenseitig ansprechen können,
nennt man das eine bidirektionale Assoziation. In Programmstrukturen, die
hierarchisch aufgebaut sind und daher relativ sicher und übersichtlich,
vermeidet man nach Möglichkeit bidirektionale Assoziationen. Wenn in einem
Entwurf derartige Situationen auftreten, deutet es oft darauf hin, daß der
Entwurf in der OOA nicht sauber aufgelöst ist. Sollte aus sehr guten Gründen dann
doch einmal eine derartige Beziehung implementiert werden, bietet Delphi
nur eine eingeschränkte Möglichkeit an. Den Import der
gegenseitigen Units im Interface-Teil wie üblich quittiert Delphi mit der
Fehlermeldung 'Überkreuzender Bezug zweier Units ..' und bietet dafür den
Import im Implementation-Teil an.
Beispiel:
Eine Firma stellt den Mitarbeitern einen Firmenwagen. Beim
Mitarbeiter soll der KFZ-Typ gespeichert werden und beim KFZ der
Mitarbeiter. Man soll also fragen können: Welches Auto hat Müller? Wer
fährt den BMW? |
|
Dieses Beispiel soll nur zur Demonstration einer möglichen
Implementierung dienen, keinesfalls aber als Vorlage für einen
Entwurf! |
In uMitarbeiter ist die Vorgehensweise gezeigt. Um den Linkaufbau variabel zu halten,
soll der Parameter vom Typ TObject
sein. Der Operator IS führt
eine dynamische Typprüfung durch und AS die erforderliche Typumwandlung
zur Laufzeit.
procedure TMitarbeiter.SetLink (obj : TObject);
(*
-------------------------------------------------------------------- *)
begin
if obj IS TAuto // Typprüfung
then ...
end; |
Die Klasse TAuto wird analog implementiert.
Die GUI-KLasse bietet keine besonderen Probleme. Es können ein
oder auch mehrere Objekte assoziiert werden.
UNIT uMitarbeiter;
(* ******************************************************************** *)
(* K L A S S E : TMitarbeiter *)
(* -------------------------------------------------------------------- *)
(* Version : 0.9 *)
(* Autor : S. Spolwig, OSZ-Handel I, 10997 Berlin *)
(* Aufgabe : bildet das Objekt Mitarbeiter ab, *)
(* speichert aktuellen Firmenwagen *)
(* Compiler : DELPHI 6.0 *)
(* Aenderung : V. 1.1 30-DEZ-06 *)
(* ******************************************************************** *)
INTERFACE
(* ========================== Export ================================== *)
type
TMitarbeiter = class (TObject)
private
Name,
Vorname : string[15];
AutoID : string[10]; // Firmenwagen
public
constructor Create;
procedure Init;
procedure SetName (n: string);
procedure SetVorName (vn: string);
function GetName : string;
function GetVorName : string;
function GetAutoID : string;
procedure SetLink (obj : TObject);
function GetLink : TObject;
procedure RemoveLink;
end; (* TMitarbeiter *)
(* -------------------- B e s c h r e i b u n g -------------------------
Oberklasse : -
Bezugsklassen : TAuto
Methoden
--------
Create
Auftrag: Mitarbeiter erzeugen und Initialisieren
vorher : -
nachher: Mitarbeiter ist erzeugt
Init
Auftrag: Mitarbeiter Initialisieren
vorher : ist erzeugt
nachher: Name ist 'Müller'; andere Attribute sind leer.
Set...
Auftrag: Attribut schreiben
vorher : Mitarbeiter ist vorhanden.
nachher: Attribut ist gesetzt
Get...
Anfrage: Attribut aus dem Objekt lesen
vorher : Mitarbeiter ist vorhanden.
nachher: -
SetLink (obj : TObject);
Auftrag: Link mit externem Objekt herstellen
vorher : obj ist vorhanden.
nachher: Linkobjekt erzeugt; ev. Zugriffe
GetLink : TObject;
Anfrage: nach Linkobjekt
vorher : Linkobjekt ist vorhanden.
nachher: -
RemoveLink
Auftrag: Link mit externem Objekt unterbrechen
vorher : obj ist vorhanden.
nachher: Linkobjekt ist NIL
----------------------------------------------------------------------- *)
IMPLEMENTATION
(* ==================================================================== *)
uses uAuto; // importiert Bezugsklasse
var Link : TAuto; // Linkobjekt; Mitarbeiter kennt Auto
constructor TMitarbeiter.Create;
(* -------------------------------------------------------------------- *)
begin
inherited Create;
Init;
end;
procedure TMitarbeiter.Init;
(* -------------------------------------------------------------------- *)
begin
Name := 'Müller';
Vorname := '';
AutoID := '';
end;
procedure TMitarbeiter.SetName (n: string);
(* -------------------------------------------------------------------- *)
begin
Name := n;
end;
procedure TMitarbeiter.SetVorName (vn: string);
(* -------------------------------------------------------------------- *)
begin
VorName := vn;
end;
function TMitarbeiter.GetName : string;
(* -------------------------------------------------------------------- *)
begin
result := Name;
end;
function TMitarbeiter.GetVorName : string;
(* -------------------------------------------------------------------- *)
begin
result := Vorname;
end;
function TMitarbeiter.GetAutoID: string;
(* -------------------------------------------------------------------- *)
begin
result := AutoID;
end;
(* -------------------------------------------------------------------- *)
(* --------------------- Der Link auf Auto -------------------------- *)
(* -------------------------------------------------------------------- *)
procedure TMitarbeiter.SetLink (obj : TObject);
(* -------------------------------------------------------------------- *)
begin
if obj is TAuto // Typprüfung
then
begin
Link := obj AS TAuto; // Typwandlung
AutoID := Link.GetID; // Zuweisung aus der Assoziation
end;
end;
function TMitarbeiter.GetLink : TObject;
(* -------------------------------------------------------------------- *)
begin
Result := Link;
end;
procedure TMitarbeiter.RemoveLink;
(* -------------------------------------------------------------------- *)
begin
Link := NIL;
end;
END.
|
unit uFenster;
// ***********************************************************************
// K L A S S E : TFensterFrm
// -----------------------------------------------------------------------
// Version : 0.9
// Autor : S. Spolwig, OSZ-Handel I, 10997 Berlin
// Aufgabe : GUI fuer Demo mit bidirektionaler Assoziation
// verschiedener Objekte: Person - Firmenwagen
//
// Welches Auto hat Müller ?
// Wer fährt den BMW ?
// Compiler : DELPHI 6
// Aenderung : V. 0.9 - 30-DEZ-06
// ***********************************************************************
interface
// =======================================================================
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls,
uAuto,
uMitarbeiter;
type
TForm1 = class(TForm)
AutoBtn : TButton;
PersonBtn : TButton;
PersonPnl : TPanel;
AutoPnl : TPanel;
procedure FormCreate(Sender: TObject);
procedure PersonBtnClick(Sender: TObject);
procedure AutoBtnClick(Sender: TObject);
end;
var
Form1 : TForm1;
Mitarbeiter : TMitarbeiter;
Auto,
pkw : TAuto;
implementation
// =======================================================================
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
(* -------------------------------------------------------------------- *)
begin
Mitarbeiter := TMitarbeiter.Create;
Auto := TAuto.Create;
// pkw := TAuto.Create; // geht auch mit mehreren
end;
procedure TForm1.AutoBtnClick(Sender: TObject);
(* -------------------------------------------------------------------- *)
begin
Auto.SetLink(Mitarbeiter); // Auto ----> Mitarbeiter
PersonPnl.Caption := Auto.GetBesitzer; // Auto hat jetzt den Namen
end;
procedure TForm1.PersonBtnClick(Sender: TObject);
(* -------------------------------------------------------------------- *)
begin
Mitarbeiter.SetLink(Auto); // Mitarbeiter ---> Auto
AutoPnl.Caption := Mitarbeiter.GetAutoID;// Mitarbeiter hat jetzt AutoID
end;
END.
|
|
7.
Die einfache Nutzung von Serverobjekten
Die Beziehungen zwischen Klassen sollen im Normalfall auch
innerhalb der Klasse implementiert sein. DELPHI und auch andere Sprachen
verfügen auf Grund ihres Konzepts über einen Datenraum der die gesamte
Unit umfasst, also weitergeht als das darin befindliche Klassenkonstrukt.
Insbesondere bei Hauptprogrammen und Objekten mit
Steuerungsfunktion (GUI-Fenster) bei denen ein anderes unabhängiges Objekt
der Fachklasse nicht als Komponentenobjekt (Attribut) eingebunden werden
soll, bietet sich eine einfache Nutzung des Serverobjekts als Vereinbarung
an, hier z. B. wenn das Fenster geschlossen wird, der Kunde jedoch
weiterleben soll, um in einem neuen anderen Fenster dargestellt zu werden.
UNIT
uSteuerung;
INTERFACE
Uses uKunde; // import: TKunde
type TSteuerFrm = class(TForm)
SteuerPnl : TPanel;
NeuBtn : TButton;
...
end;
var
AktuellerKunde : TKunde;
// Referenz mit
Objektnamen vereinbaren
IMPLEMENTATION
procedure TSteuerFrm.OkBtnClick(Sender: TObject);
(* ------------------------------------------------------------------- *)
begin
DatenAktualisieren(AktuellerKunde);
// Referenzieren und
end;
// Daten aus der
Eingabemaske uebernehmen
|
Hier ist die Vereinbarung der
Referenzierung im Deklarations-/Interface-Teil vorgenommen. Ebenso könnte
die Referenzierung bzw. die Instanzierung im Implementation-Teil gemacht
werden, wenn es angebracht scheint. Damit läge dann die Nutzung versteckt
im privaten Teil und ist nach außen nicht sichtbar.
In besonderen Situationen könnte das
Serverobjekt auch direkt in seiner eigenen Klasse vereinbart sein, erzeugt
und exportiert werden. Dann lässt es sich durch einfachen Import nutzen.
Jedoch stellt diese Variante den Export einer globalen Variablen dar mit
allen damit verbundenen Nachteilen. |