Voorwoord: onderstaande code gaat ervan uit dat er maar één persoon tegelijk ingelogd kan zijn. Denk maar dat het dient voor een programma dat ergens op een computer draait. Wanneer de gebruiker wil beginnen werken, moet hij eerst z'n username en password ingeven. Deze worden dan vergeleken met deze in een bestandje waar tevens de rollen die aan de gebruiker zijn toegekend vermeld staan en aldus worden aan de gebruiker z'n rollen toegekend bij het inloggen.
Een makkelijke manier om het op te lossen. Laten we aannemen dat er 2 rollen zijn: Administrator en Guest. Administrator en Guest mogen alletwee inloggen en uitloggen. Administrator mag hiernaast ook printA() en printB() oproepen, Guest mag alleen maar PrintB() oproepen.
Begin met het definiëren van een klassa IUser. Dis is de interface die alle acties bevat die uitgevoerd kunnen worden. We hebben hierboven 4 acties gedefiniëerd, dus we krijgen het volgende. Let op dat elke functie een boolean terug geeft. Waarom we dit doen zal later duidelijk worden.
Code:
interface IUser
{
public boolean printA();
public boolean printB();
public boolean login(String username, String password);
public boolean logout();
}
Hierna definiëren we een klasse CurrentUser. Dit is onze ingelogde gebruiker. Onze ingelogde gebruiker heeft een stel rollen toegekend gekregen, dus we voorzien een Vector $currentRoles om deze op te slaan. Hiernaast implementeert onze CurrentUser tevens de IUser interface en verwijst de verantwoordelijkheid voor het uitvoeren van de acties door naar z'n rollen. De rollen zullen we later programmeren.
Code:
class CurrentUser implements IUser
{
private Vector $currentRoles = new Vector();
public boolean printA()
{
boolean done = false;
for(int i = 0; i < $currentRoles.size() && done == false; i++)
done = ((IUser)$currentRoles.elementAt(i)).printA();
if(done == false) return false;
return true;
}
public boolean printB()
{
boolean done = false;
for(int i = 0; i < $currentRoles.size() && done == false; i++)
done = ((IUser)$currentRoles.elementAt(i)).printB();
if(done == false) return false;
return true;
}
public boolean login(String username, String password)
{
//hier komt code om een bestandje uit te lezen, om username en password
//na te kijken en om de rollen in te lezen in de Vector $currentRoles
//als dit allemaal perfect verloopt return je true, als er een fout in
//sluipt dan return false
}
public boolean logout()
{
$currentRoles.clear();
return true;
//we gaan ervan uit dat zo'n simpele operatie niet mis
//kan lopen, dus doen we niet de moeite om te checken of
//er iets fout gelopen is. We geven altijd true terug, omdat
//we geen fout verwachten.
}
}
Zie hoe we printA() en printB() niet door CurrentUser laten uitvoeren... we laten de rollen dit oplossen. Return true geeft natuurlijk aan dat de operatie succesvol verlopen is, return false geeft een fout aan. Deze false kan om 2 redenen worden terug gegeven.
Enerzijds kan een gebruiker die ingelogd is als Guest de operatie printA() hebben proberen uit te voeren. De for loop zal op zoek gaan naar een rol die de bevoegheid heeft om deze operatie uit te voeren. De rol Administrator is echter niet toegekend aan de ingelogde gebruiker. 'done' blijft dus false.
Anderzijds kan het zijn dat een ingelogde gebruiker een operatie heeft proberen uit te voeren waarvoor hij gerechtigd is, maar dat deze operatie zelf een fout heeft ondervonden en daarom geen 'true' rapporteert om het succesvol uitvoeren van de operatie te bevestigen.
Werken met True en False is hier verre van perfect. Zelf een paar excepties schrijven, is hier een veel betere keuze. Alle excepties van heel het systeem zullen op een bepaald moment langs het CurrentUser object passeren (als ze niet vroeger opgevangen zijn). Deze excepties opvangen laat je toe om nuttige(!) error boodschappen voor de gebruiker te genereren.
Soit, dit terzijde.
Nu gaan we ons bezighouden met de rollen. We beginnen met het schrijven van een abstracte klasse UserRole die voor alle methodes die niet door alle gebruikers uitgevoerd kunnen worden 'false' terug geeft. De methodes die wel door alle gebruikers uitgevoerd kunnen worden laten we 'true' terug geven. De eigenlijke rollen zullen deze klasse implementeren.
Code:
abstract class UserRole implements IUser
{
public boolean printA() {return false;}
public boolean printB() {return false;}
public boolean login(String username, String password) {return true;}
public boolean logout() {return true;}
}
Nu definiëren we de Guest rol. Let op dat we de acties die de Guest niet mag uitvoeren (printA() dus) niet vermelden. Deze wordt immers overgeërfd van UserRole.
Code:
class Guest extends UserRole
{
public boolean printB()
{
//nu hebben we 2 opties. Of we zetten hier effectief code (met als nadeel
//dat we deze code moeten dupliceren bij de rol Administrator) of we
//encapsuleren alle code van alle acties ergens in een klasse die we zullen
//aanspreken telkens we een actie willen uitvoeren.
//Beide keuzes zijn natuurlijk slecht, maar dit is maar een voorbeeld.
//In de praktijk is het het simpelste dat je de Facade van de module
//aanspreekt die de uit te voeren actie omvat. Deze Facade moet of een
//Singleton zijn of moet zich ergens bij het opstarten in ons UserManagement
//systeem geregistreerd hebben.
//hier gaan we nu ook false of true moeten teruggeven naargelang de
//module rapporteert of de actie al dan niet succesvol is uitgevoerd.
}
//login() en logout() zijn nergens gedefiniëerd. Dit is niet nodig daar deze
//al in CurrentUser staan.
}
En voor de Administrator hebben we:
Code:
class Administrator extends UserRole
{
public boolean printA()
{
//zelfde commentaar
}
public boolean printB()
{
//zelfde commentaar
}
//login() en logout() zijn nergens gedefiniëerd. Zie commentaar Guest.
}
Wat een hoop werk zeg (en verdomd veel typewerk).
Hoe werken we nu met dezen hoop brol?
Code:
CurrentUser $cu = new CurrentUser();
$cu.login(uw_naam, uw_paswoord);
$cu.printA();
$cu.printB();
Nemen we aan dat we inloggen als Guest.
- $cu.login() zal aan $cu de rol Guest toekennen.
- $cu.printA() zal de actie printA() van de rol Guest oproepen. Deze hebben we niet geherdefiniëerd toen we de klasse Guest schreven, en zal dus 'false' terug geven (net zoals we in de klasse UseRole vermeld hebben). We kunnen deze 'false' negeren en dan gebeurt er niets of we kunnen deze gebruiken om een error te genereren (als ge dit van plan bent, dan zijn excepties beter - toch nog maar es herhalen).
- $cu.printB() zal de actie printB() van de rol Guest oproepen. Deze hebben we in de klasse Guest wel geherdefiniëerd en dus zal de gewenste code uitgevoerd worden.
Voila.
Opmerking 1
De *ahem* kracht van deze manier zit hem in 3 punten:
- Je kan dit als een aparte laag implementeren tussen GUI en onderliggende logica. Deze laag is verantwoordelijk voor het nagaan welke acties een gebruiker mag uitvoeren en deze dan verder te delegeren naar de onderliggende logica. Dit systeem is dus helemaal losgekoppeld van de eigenlijke logica.
- Een gebruiker kan meerdere rollen toegekend krijgen. Het is perfect mogelijk iemand zowel Administrator als Guest te maken. Deze gebruiker kan dan natuurlijk zowel de acties van de Administrator als deze van de Guest uitvoeren.
- Het toevoegen van nieuwe rollen is enorm simpel. Dit behoeft geen verdere commentaar.
Opmerking 2
De *kuch* aandachtige lezer zal zich nu wel hebben afgevraagd waarom die IUser er eigenlijk bijstaat. Op het eerste zicht doet die immers niet veel. Wel die IUser zorgt ervoor dat wanneer we ergens een nieuwe actie toevoegen (bv. printC()), maar deze ergens in deze wirwar van klassen vergeten te definiëren (we voegen printC() bv. toe bij CurrentUser, maar niet bij UserRole), dat de andere klassen gaan klagen en je het project niet aan de praat zal krijgen. IUser is dus eigenlijk de stok achter de deur die zulke stomme fouten moet voorkomen.