Mit Veröffentlichung dieses Artikels liegen auf der CPR-Homepage (http://our-world.compuserve.com/home-pages/CPR_ObjectC) neue Da teien zum Download bereit:
- Object C 2.7 fĂŒr ATARI TOS
- Sozobon C mit erweitertem Linker
- Sozobon-kompatible Header-Dateien zur GEM-Programmierung
- GCC 1.5.4 fĂŒr ATARI TOS
Volker Seebode, der letzte Administrator der erweiterten Version von Sozo-bon C, hat die Quelldateien des Compilers der Firma CPR zur weiteren Verbesserung ĂŒberlassen. Das wurde auch nötig, denn mit ĂŒber 1 MB an Objektcode und 80 Quelldateien der Object C GUI Bibliothek war der Linker von Sozobon C auch ĂŒberfordert. Der erweiterte Linker kann nun ebenfalls von der Homepage geladen werden. Die GEM-Headerdateien sollen aus einer Ă€lteren GCC Distribution stammen und gut mit Sozobon C arbeiten.
UnabhĂ€ngig hiervon versucht Christian Felsch, Autor der MagiC-Anpassung von GCC 2.7.2.3, eine KompatibilitĂ€t von GCC zu MagiCMac zu erreichen. Falls es ihm gelingt, wird Object C kurzfristig auf diesen Release von GCC angepaĂt. In jedem Fall wird man versuchen, das GCC mit Object C auf TOS2WIN oder anderen Emulatoren abzugleichen, womit dann neben Sozobon C und dem alten GCC 1.5.4 auch ein aktuellerer GCC Compiler zur Auswahl steht.
AdreĂverwaltung im Eigenbau
Im letzten Artikel hatten wir begonnen, eine StdIO-basierte AdreĂverwaltung zu implementieren. Damit soll allen Lesern die Möglichkeit geboten werden, die Anwendung des erlernten Object C Syntax zu ver-H tiefen.
Wir hatten zunĂ€chst in Datei "adr.iâ eine Reihe von Klassen beschrieben, die wir hier noch um weitere Attribute erweitern. Den Namen des Attributes 'Laddresses' haben wir in âpersons' geĂ€ndert, denn unser Programm soll schlieĂlich Personen verwalten, die Adressen haben und nicht umgekehrt. Der neue Konstruktor fĂŒr Globals befindet sich bei den anderen Konstruktoren (Person, Phone, etc.).
Aus der Funktion "main()" wurde nach den nötigen Object C Initialisierungen die Haupteingabeschleife "$$Main-Loop(o_glo);" aufgerufen. Ferner haben wir eine gets()-Ă€hnliche Methode $$lnput(o_glo, str);". In der Datei "adr_file.m" haben wir eine Reihe Methoden zum Dateizugriff bereitgestellt (Open, Close, Read, Write, Delete), die allesamt auf Objekten der Klasse File arbeiten. Wir haben dazu die Basisfunktionen der Object C Laufzeitbibliothek verwendet und mĂŒssen uns daher keine Sorgen um PortabilitĂ€t machen.
Es hĂ€tte natĂŒrlich schöner ausgesehen, wenn eine Person eine Adresse erben wĂŒrde ($dat Person : Address) und jede Adresse wiederum einen TelefonanschluĂ ($dat Address : Phone). Weil jedoch jede Person mehrere Wohnungen besitzen und auch mehr als einen TelefonanschluĂ pro Wohnung haben kann, wurden Listen verwendet. Object C entfernt freigegebene Objekte dann fĂŒr uns automatisch aus allen Listen (Listing 1).
Implementieren wir also die fehlenden Do-Methoden der MainLoop. Es empfiehlt sich, mit dem DoNew (legt einen neuen AdreĂeintrag an) zu beginnen, weil wir dann gleich sehen, welche Daten wir zu verwalten haben. Zuvor schreiben wir noch eine angemessene Eingabe-Methode "Enter()": (Listing 2)
Die Methode "Enter" soll nun lediglich ausgeben, welche Art von Daten einzugeben sind und dem Anwender mit-teilen, daĂ ein Abbruch durch Eingabe von âaâ möglich ist. In diesem Fall wird op_glo->abort auf TR gesetzt, andernfalls wird âinputstr' geliefert.
Eingabe von Adressen
Wie bereits erwĂ€hnt, kann jede Person eine oder mehrere (oder auch gar keine) Adresse haben und jede Adresse wiederum beliebig viele TelefonanschlĂŒsse. Wir erzeugen also im DoNew der Klasse Global zunĂ€chst ein Objekt der Klasse Person und erlauben dann dem Anwender, den Vornamen und Nachnamen der betroffenen Person einzugeben. Falls der Anwender seine Eingabe nicht durch âaâ abgebrochen hat, wird das Objekt âo_personâ an die Liste der Personen angefĂŒgt. Dann hat der Anwender die Möglichkeit, eine Adresse anzugeben, fĂŒr die dann beliebig viele TelefonanschlĂŒsse (genauer Email-Adresse, Telefonnummer, Faxnummer und Handy-nummer) angegeben werden können. AnschlieĂend können weitere Adressen angegeben werden. (Listing 3)
In der Implementierung von DoNew zeigt sich ĂŒbrigens ein weiterer starker Vorteil von Object C gegenĂŒber C++ und Java. Methoden gleichen Namens haben nĂ€mlich oft inhaltlich starke Ăhnlichkeiten, wie die sogenannten "klassenspezifischen Blöckeâ von Person. Address und Phone zeigen. Wir können so unseren Quellcode sehr gut auf Fehler und auch auf inhaltliche Unstimmigkeiten ĂŒberprĂŒfen. Ferner kann die Notwendigkeit neuer Methoden zur besseren Modularisierung schneller erkannt werden. HĂ€tten wir in C++ programmiert, dann wĂŒrden sich diese gemeinsamen Codeblöcke wahrscheinlich nicht einmal in den gleichen Dateien befinden, weil in C++ nach Klassen und nicht nach FunktionalitĂ€t getrennt wird.
Entfernung von Adressen
Die Entfernung von Personen und den personenbezogenen Daten aus unserer AdreĂvewaltung lĂ€Ăt sich wesentlich einfacher realisieren. Hierbei wird die aktuell selektierte Person 'op_glo->ind_selected' der Personenmenge âop_glo->selected' oder auch alle selektierten Personen einfach freigegeben. Die Schwierigkeit liegt hier vor allem in der BenutzerfĂŒhrung der Selektion. Wir verlagern dieses Problem deshalb auf die Do-Query-Methode. Das bedeutet, der Anwender soll zuerst die zu löschenden EintrĂ€ge durch DoQuery spezifizieren und dann DoDel aufrufen. (Listirig 4)
Konstruktoren & Destruktoren
Wir benötigen natĂŒrlich auch die Konstruktoren der Klassen Person, Address und Phone. Ăblicherweise wĂŒrden die wichtigsten Attribute dem Konstruktor als Parameter ĂŒbergeben werden, wir mĂŒĂten dann aber lokale Eingabebuffer fĂŒr alle Attribute in unserer DoNew-Metho-de reservieren und hĂ€tten trotzdem noch unsere Sorgen mit den in DoNew vorgenommenen Append von Objekten in die Listen von Person und Address. Werden in den Attributen einer Klasse nur Nutzwerte wie zum Beispiel Namen oder Zahlen gespeichert, dann können wie im Beispiel von âPhoneâ der Destruktor und der Cloning-Block leer bleiben. Jedes Cloning funktioniert dann ganz problemlos.
Werden hingegen Listen in Attributen gespeichert, dann besteht immer die Frage, ob im Destruktor nur die Liste oder auch die Objekte der Liste freizugeben sind. Der Programmierer muĂ also entscheiden, welches Objekt letztendlich fĂŒr die zentrale Speicherung und Zerstörung bestimmter Objekte zustĂ€ndig sein soll. Das klingt kompliziert, ist aber in den meisten FĂ€llen recht einfach. In unserem Beispiel sollen Adressen zu einer Person gehören und beim Entfernen dieser Person auch aus unserem Computer verschwinden und mit diesen Adressen auch alle zugehörigen Telefonnummern. Wir mĂŒssen also zunĂ€chst alle Objekte durch "$freeobjs(list);" und dann auch die List selbst durch "$freelist(list);" zerstören. Im Gegensatz hierzu soll unsere Liste âselected' (Klasse Globals) nur auf eine Menge von Personen verweisen und diese nicht in Besitz nehmen und sogar löschen. Wir rufen deshalb auch kein "$freeobjs(..selected);n im Destruktor der Klasse Globals auf.
FĂŒr alle nicht-trivialen Clo-ning-Blöcke (trivial ist beispielsweise das Cloning von 'Phone') sollte erst einmal ein "$serr("not impl.");" eingesetzt werden und erst bei tatsĂ€chlichem Bedarf ĂŒber die Implementierung des Clonings nachgedacht werden; ungetestete Cloning-Blöcke "herumliegen" zu lassen, ist nĂ€mlich nicht ratsam, denn oft Ă€ndern sich die Anforderungen an verschiedene Attribute im Laufe der Programmentwicklung.
Object C - Listings Heft 3/98
Listing 1:
----------
Klassenbeschreibungen "ADR.I"
/* Datei: adr.i */
#include
$classes
Globals, File, Person, Address, Phone;
$
$dat Globals
List persons;
List selected;
long ind_selected;
Obj o_file; /* actual opened file */
Bool abort; /* for Do-Methods */
$
$dat File
char name[32];
Hdl hdl;
$
$dat Person
List addresses;
char first_name[ 32 ];
char lastname[32];
char birthday[32];
$
$dat Address
List phones;
char country[32];
char city[32];
char Street[32];
char postcode[16];
$
$dat Phone
char email[80];
char fax[20];
char voice[20];
char handy[20];
$
extern Obj o_glo;
extern $ptype(Globals) op_glo;
#include
Listing 2:
----------
$meth Enter (Obj obj, char* valuename, char* inputstr)
$support(Globals);
$call(obj);
$class Globals
printf("Enter '%s' or <a> to abort valuename);
$$Input(obj, inputstr);
if (*inputstr == ' aâ)
..abort = TR;
$
Listing 3:
----------
$meth DoNew (Obj obj)
$support(Globals, Person, Address, Phone);
$call (obj);
$class Globals
char inp[80];
Obj o_person;
..abort FA;
o_person - $new(Person);
$$DoNew(o_person);
if (..abort)
{
$free (o_person);
}
else
{
$app(op_glo->persons, o_person);
..o_selected = o_person;
printf("Do you want to create an Address field (y/n) ?");
$ $Input(o_glo, inp); while (*inp *= ' y')
{
Obj o_address = $new(Address);
$$DoNew(o_address)? if (..abort)
{
$free(o_address);
..abort = FA; break;
}
else
{
$ptr (Person, o_person, op_j>erson) ; $app(op_person->addresses, o_address);
printf("Do you want to create a Phone field (y/n) ?");
$$Input(o_glo, inp);
while (*inp = ' y')
{
Obj o_phone = $new(Phone);
$$DoNew(o_phone) ;
if (..abort)
{
$free(o_phone);
..abort = FA; break?
}
else
{
$ptr(Address, o_address, opaddress); $app(op_address->phones, ophone)?
}
printf("Do you want to create another Phone field (y/n)?");
$$Input(o_glo, inp);
printf("Do you want to create another Address field (y/n) ?");
$$Input(o_glo, inp);
I
)
$class Person
$$Enter(o_glo, "First Name", ..first_name);
if (!op_glo->abort)
{
$$Enter(o_glo, "Last Name", ..last_name);
if (!op_glo->abort)
{
$$Enter(o_glo, "Birthday", ..birthday);
}
$class Address
$$Enter(ojglo, "Country", ..country);
if (!op_glo->abort)
{
$$Enter(oglo, "City", ,.city);
if (!op_glo->abort)
{
$$Enter(o^glo, "Street", ..Street); if (!op_glo->abort)
(
$$Enter(o_glo, "Postcode", ..postcode);
)
I
$class Phone
$$Enter(o_glo, "Faxâ, ..fax); if (!op__glo->abort)
(
$$Enter(o_glo, "Voice", ..voice); if (!op_glo->abort)
{
$$Enter(o_glo, "Handyâ, ..handy); if (!op_glo~>abort) i
$$Enter(o_glo, "Email", ..email);
)
]
)
$
Listing 4:
----------
$meth DoDel (Obj obj)
$support(Globals);
$call(obj);
$class Globals
Long elems;
$elems(..selected, fielems);
if (elems)
{
char inp[80];
if (..ind_selected >»= OL)
{
printf("Delete [c]urrent item, delete [all] items or [a]bort ?");
$$Input(o_glo, inp);
}
else
{
printf("Delete [all] items or [a]bort ?");
$ $Input(o_glo, inp); if (EQU(inp, "c"))
CPY(inp, âa');
if (EQU(inp, "c"))
{
Obj o_person;
$elem (.. selected, .. ind_selected, &o_j>erson) ;
$free(o_person);
âelems?
if (.. ind_selected >=* elems)
.ind_selected;
[
if (EQU(inp, "all"))
{
$freeobj s(..selected);
..ind_selected = -1L;
!
)
else
printf("There are no selected items.\n");
$
Listing 5:
----------
$new Person ()
CLR(..first_name); CLR(..last^name); CLR(..birthday);
$newlist(&..addresses);
$clone
$serr("not impl.");
$free
$freeobjs(..addresses); $freelist(..addresses);
$
$new Address ()
CLR(..country); CLR(..city); CLR(..Street); CLR(..postcode);
$newlist(&..phones)?
$clone
$serr("not impl.");
$free
$freeobjs(..phones); $freelist(..phones);
$
$new Phone ()
CLR(email); CLR(fax); CLR(voice); CLR(handy);
$clone
$free
$
$new Globals ()
o_glo = obj;
op_glo * objp;
..o_file = $new(File);
$newlist(&..persons);
..ind_selected = -1L;
$newlist(&..selected);
$clone
$serr("not supported!");
$free
$free(..o_file);
$freelist(..selected);
$freeobjs(..persons)? $freelist(..persons);
$