In dit artikel een praktisch voorbeeld van de toepassing van objectgeoriënteerde syntax van Visual Objects (VO). We gaan in op het integreren van eigen classes in de gegenereerde code. Een voorbeeld hiervan is dat we, gebruik makend van traditionele DBF tabellen en RDD (Replaceble Database Driver) technologie uit Clipper, toch gemakkelijk en generiek referentiële integriteit kunnen waarborgen. Alle voorbeelden zijn toe te passen in zowel VO 1.0 als 2.0. Dit artikel is vooral bedoeld voor Clipper programmeurs die de overstap naar VO overwegen of gaan maken en VO programmeurs die verder willen gaan dan het benutten van de standaard geboden functionaliteit.
Tot het moment dat objectgeoriënteerde databases in de praktijk meer toegepast worden, zullen we nog vele applicaties ontwikkelen die via objectgeoriënteerde talen relationele databases benaderen. Met deze benadering zijn ook nu al duidelijke voordelen te behalen. Dan bedoel ik voordelen bovenop de voordelen die objectoriëntatie normaal gesproken al biedt, zoals robuustheid, herbruikbaarheid en goede onderhoudbaarheid.
Visual Objects Architectuur
We hoeven in VO niet van de grond af aan te beginnen. De hele benadering van tabellen via werkgebieden die we kennen uit de xBase wereld, is reeds ingebouwd in de meegeleverde class library. Daarnaast is ervoor gezorgd dat de benadering van SQL databases op vrijwel identieke wijze kan plaatsvinden. Zowel de class voor benadering van SQL tabellen (SqlTable) als die voor benadering van DBF files (Dbserver) erven namelijk van dezelfde base class (DataServer) en hebben zeer veel overeenkomstige methoden en eigenschappen. Bovendien gebruikt de class waarop de data entry schermen gebaseerd zijn, een DataServer-instance voor de benadering van de data. Op deze manier is het dus kinderspel geworden van databron te wisselen. Sterker nog: omdat de SQL-classes eigenlijk ODBC gebruiken kunnen zelfs andere ODBC-bronnen gebruikt worden zoals TXT files of Excel sheets. Uiteraard biedt ook het product VO2Ado aantrekkelijke mogelijkheden op dit vlak.
Naast dit soort overduidelijke voordelen is het op deze manier voor de traditionele Clipper programmeur een ideaal groeipad naar een SQL omgeving. Om daar nog meer in tegemoet te komen zorgt CA (Computer Associates) niet alleen voor veel ODBC-drivers bij VO, maar wordt bij VO ook een single-user SQL-Server geleverd. Bij 1.0 was dat de Watcom-engine, bij 2.0 wordt de ‘eigen’ Open Ingres Desktop database ingesloten.
Op zich klinkt dit goed, maar er is natuurlijk nog wel wat werk te verzetten. Zo is (CA) niet zover gegaan als mogelijk is met bijvoorbeeld de Dbserver class. Zij gaan ervan uit dat derde partijen dit gat zullen invullen, en in zekere zin is dat reeds het geval.
CA heeft ervoor gezorgd dat de SqlTable-class zich gedraagt als een record georiënteerd mechanisme, en met een gebufferde Dbserver wordt het mogelijk commit en rollback te implementeren op DBF files. Dit laatste is een van de zaken die CA nog niet voor ons geregeld heeft, maar die wel te vinden is in diverse voorbeelden die op conferenties zijn gepresenteerd. Veel zijn te downloaden via www.cavo.com.
SQL versus DBF
Bij gebruik van een SQL omgeving kunnen regels als ‘alle offerte-regels moeten worden verwijderd bij verwijdering van de offerte’ of ‘een klant-record mag niet worden verwijderd als er nog opstaande posten voorkomen voor die klant’, worden opgenomen als integriteitregel op de database. Zo worden deze regels geforceerd, hoe je de database ook benadert. In een omgeving die gebruik maakt van DBF files moet je dit soort regels in elke applicatie zelf coderen. Met behulp van de OO benadering in VO is dit niet langer noodzakelijk. De enige afspraak die nodig is, is dat de tabellen altijd via de geschreven Dbserver class, of een afstammeling daarvan, worden benaderd. Deze classes kunnen worden opgenomen in een library, zodat meerdere applicaties daar gezamenlijk gebruik van kunnen maken. Neveneffect hiervan is dat de regels, indien ze worden aangepast, automatisch beschikbaar zijn voor alle applicaties die er gebruik van maken.
De praktijk
Na deze theoretische inleiding nu de praktische invulling ervan. Als eerste gaan we ervoor zorgen dat er geen weeskinderen meer kunnen ontstaan na verwijderen van een ouder-record. We gebruiken de eerder genoemde Offerte/Offerteregels combinatie als voorbeeld.
Als via de Dbserver-editor een tabel-class wordt gegenereerd, erft de gegenereerde class van de VO Dbserver-class. Deze class heeft een delete method waarmee het actuele record kan worden verwijderd. Dit is dus de eerste kans om in te grijpen. Het is vrij eenvoudig om een delete method te schrijven die ook alle offerteregels verwijdert. In het codevoorbeeld ga ik ervan uit, dat de offerte-class dbsOff heet en de offerteregel-class dbsOffRgl (dbs staat voor Dbserver, net als frm als prefix voor een Form oftewel window kan dienen en mnu als prefix voor een gegenereerd menu).
De oplettende lezer zal opmerken, dat in deze oplossing steeds het offerteregel bestand opnieuw wordt geopend. In de praktijk zult u wellicht in een subclass van dbsOff een dbsOffRgl-object opnemen en openen in de init-method (niet vergeten dit object dan ook weer te sluiten in de close-method van dbsOff!).
Voor de volledigheid heb ik hierna de source van deze oplossing opgenomen. Ik ben ervan uitgegaan dat we de gegenereerde code voorzien van een underscore als eerste karakter van de naam. Hierdoor is er goed onderscheid te maken tussen door VO gegenereerde en handgeschreven code.
CLASS dbsOff INHERIT _dbsOff
PROTECT oOffRgl AS dbsOffRgl
METHOD Init(cDBF, lShare, lRO, xRdd) CLASS dbsOff
Super:Init(cDBF, lShare, lRO, xRdd)
Self:oOffRgl := dbsOffRgl{}
Self:SetSelectiveRelation(Self:oOffRgl, #OffNr )
RETURN Self
METHOD Close() CLASS dbsOff
Self:oOffRgl:Close()
RETURN Super:Close()
Hoewel dit al een werkbare oplossing levert, kunnen we nog een stapje verder gaan door dit op een generieke wijze op te lossen. We missen dan eigenlijk een method AddChild bij de Dbserver-class, en de afhandeling in de Dbserver delete method. VO biedt echter wel een mogelijkheid deze functionaliteit zelf aan te brengen. Een gegenereerde Dbserver-class hoeft namelijk niet van Dbserver te erven; een property in de Dbserver-editor kan worden gebruikt om te erven van een eigen class die dan op zijn beurt natuurlijk weer erft van Dbserver, zodat de wel aanwezige functionaliteit gewoon kan worden benut. Daar was het namelijk allemaal om te doen in objectoriëntatie. De bedoelde property heet ‘Inherit From Class’ en is beschikbaar in vrijwel iedere visuele editor in VO, voor vrijwel alle objecten.
De generieke code voor de DbChildServer class zal er ongeveer zo uit kunnen zien:
CLASS DbChildServer INHERIT DbServer
PROTECT aChildren := {} AS ARRAY
ACCESS NrOfChildren CLASS DbChildServer
RETURN ALEN(Self:aChildren)
METHOD AddChild( oChild, sKey ) CLASS DbChildServer
AADD( aChildren, oChild )
Self:SetSelectiveRelation( oChild, sKey )
RETURN Self
METHOD Delete() CLASS DbChildServer
LOCAL nI AS WORD
// Return-waarde
LOCAL lRetval := True AS LOGIC
FOR nI := 1 TO Self:NrOfChildren
IF .not. Self:aChildren[nI]:DeleteAll()
lRetVal := False
EXIT
ENDIF
NEXT
IF lRetVal // Alle kinderen gelukt
lRetVal := SUPER:Delete()
ENDIF
RETURN lRetVal
METHOD Close() CLASS DbChildServer
ASEND( Self:aChildren, #Close )
RETURN Super:Close()
Hieronder volgt de code om deze functionaliteit in een applicatie toe te passen, b.v. in samenwerking met een Datawindow:
METHOD OpenOfferteWindow CLASS StandardShellWindow
LOCAL oWin AS OfferteWin
LOCAL oOfferteServer AS dbsOff
oOfferteServer := dbsOff{}
oOfferteServer:Addchild(dbsOffRgl{}, #OFFNR )
oWin := OfferteWin{Self,,oOfferteServer}
oWin:Show()
RETURN Self
Mocht het nodig zijn om b.v. in een subform de offerteregels te tonen, dan kan dit gemakkelijk doordat de OffRgl-server op deze wijze ook direct beschikbaar is. Maak het subform zoals normaal, maar haal daarna de inhoud van de server-property weg. Dan wordt namelijk geen Use-aanroep gegenereerd. Dat kunt u dan later handmatig doen. Hieronder een voorbeeld van benodigde code gebaseerd op dit principe:
ACCESS oSfOffRglForm CLASS OfferteWin
// Nodig omdat oSf… een protected-var is.
RETURN Self:oSfOfferteWin_Detail
ACCESS oOffRgl CLASS dbsOfferte
RETURN Self:aChildren[1]
METHOD OpenOfferteWindow CLASS StandardShellWindow
LOCAL oWin AS OfferteWin
LOCAL oOfferteServer AS dbsOff
oOfferteServer := dbsOff{}
oOfferteServer:Addchild( dbsOffRgl{}, #OFFNR )
oWin := OfferteWin{ Self,,oOfferteServer }
oWin:oSfOffRlgForm:Use( oOfferteServer:oOffRgl )
oWin:Show()
RETURN Self
Het is helaas niet mogelijk een volledig functionele class te beschrijven die al dit soort principes in zich heeft; daar zou zo ongeveer een heel boek voor nodig zijn. Zo zou de hiervoor beschreven DbChildServer eenvoudig kunnen worden aangepast voor relaties die andersom liggen. Op die manier kan dan gecontroleerd worden of een record wel mag worden verwijderd, hetgeen b.v. het geval zou kunnen zijn als er geen referenties meer naar zijn; vooral handig bij referentie tabellen.
Ook is in de gepresenteerde voorbeelden de foutafhandeling nauwelijks aanwezig, hetgeen deze code niet bruikbaar maakt binnen een omgeving met meerdere programmeurs. Dit laatste druist juist in tegen de principes van objectoriëntatie. Op dit vlak is er dus duidelijk nog wat werk aan de winkel. De bedoeling was echter wat ideeën te geven waarop u zelf uw dataserver-class kunt baseren, en wel onder het motto: ‘Steel het idee, niet de code’.
Ik wens u daarmee veel succes en ben gaarne bereid via de nieuwsgroepen uw vragen te beantwoorden.
Ed Richard