[OO] Law of Demeter / Principle of Least Knowledge

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 27-04 11:59
Ik probeer om de perfecte applicatie te maken.. voor zover die bestaat ;), voor een java (SCJD) certificaat. (Ik zal deze taal dan ook gebruiken de voorbeelden hieronder.)

Ik heb het idee dat ik een mooie applicatiestructuur heb gemaakt, maar begin na mijn initiele opzet een beetje tegen problemen aan te lopen met betrekking tot hoeveel kennis classes van elkaar mogen hebben. Waar ik eerder tegen dit soort problemen aanliep loste ik dat wat pragmatisch op door klassen soms wel wat meer kennis van elkaar te laten hebben (law is eigenlijk een iets te zwaar woord).

Nu ik programmeer voor een certificaat, wil ik eigenlijk naar een soort ideaal-oplossing toe. Ik ben me er terdege van bewust dat dat nogal situatie-afhankelijk kan zijn en dat 'ideaal' een moeilijk te definieerbaar begrip is, dat hangt tussen verschillende belangen, maar wil toch zoveel mogelijk 'algemene' waarheden op tafel krijgen. Om die reden heb ik er ook voor gekozen om niet eerst 500 woorden te verspillen aan mijn specifieke situatie, maar een leuk algemeen probleem brengen.

compleet uit de lucht gegrepen voorbeeld
Stel, je bent als farao een piramide aan het bouwen en alles gaat prima. Op een zeker dag wil je graag een lijst maken van de voor en achternaam van alles slaven die je onder je hebt werken en je weet dat dat er een archivaris is die die gegevens in een handomdraai op een tabletje kan beitelen. De normale flow naar deze archivaris toe, zou zijn:

farao > generaal > aannemer > onderaannemer voor archieven > personeelsafdeling > archivaris > personeelsLijst.

Als al deze personen meteen ook classes, met getters zijn, breekt dat de 'law of demeter', die kan worden beschreven als 'een class mag geen kennis hebben van een class waarmee geen directe relatie is' (of anders gezegt, je mag niet meer dan 1 getter achter elkaar gebruiken. Eea staat hier prachtig uitgelegt.

oplossing(?)
De enige -een beetje- nette oplossing die ik dan kan verzinnen is door iedereen een functie te geven waarmee die gegevens opgehaald kunnen worden:

• bij de farao:
Java:
1
public List<Persoon> getPersoneelsLijst() { generaal.getPersoneelsLijst(); }

• bij de generaal:
Java:
1
public List<Persoon> getPersoneelsLijst() { aannemer.getPersoneelsLijst(); }

• bij de ..

geeft wel heeeel veel overhead. Ik probeer juist het aantal klassen wat te beperken. Als we vervolgens naast de personeelslijst ook nog een overzicht willen hebben van gewerkte uren, bij een administratie-gedeelte en daarna nog een overzicht waarin staat wat er nog nodig is om de piramide af te krijgen, dan heb je zo een handvol klassen die stampvol staan met dit soort functies. Als je nu de functie aan wilt passen en je bijvoorbeeld getPersoneelsLijst hernoemt naar getPersoneelSet, dan moet je dat op 6 plaatsen aanpassen.. Granted, het wordt makkelijker als je bv op een of ander niveau een management laag wilt toevoegen of verwijderen, maar nog steeds: Het voelt.. vies.

Een stapje verder
Ik heb in mijn applicatie alle functies die het equivalent van de generaal nodig hebben in een service-class gezet. Nou moet ik echter in mijn farao-class overal zeggen:

Java:
1
2
3
service.getGeneraal().getPersoneelsLijst();
service.getGeneraal().getGewerkteUren();
service.getGeneraal().getMateriaalLijst();


(tegen de regels van demeter, maar duidelijk en je hoeft maar op 1 plaats aanpassingen te doen)

of in de service-class

Java:
1
2
3
public void getPersoneelsLijst() { generaal.getPersoneelsLijst(); }
public void getGewerkteUren() { generaal.getGewerkteUren(); }
public void getMateriaalLijst() { generaal.getMateriaalLijst(); }


waarnbij ik dus alle requests direct doorstuur naar de generaal class. Vooral als je voor al je public classes weer documentatie moet gaan maken (javadoc), voelt dat heel onpraktisch.

Ik heb de neiging om de 'law of demeter' te blijven zien als een richtlijn en te gebruiken waar deze praktisch is (en dat ook uitgebreid documenteren), maar omdat ik ook graag een (nog! ;) ) betere programmeur wil worden, hoop ik hierin iets van jullie te leren. Hoe gebruiken jullie de law of demeter?

Localhost is where the heart is


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Waarom zou je je actoren in je systeem verwerken? Dat de farao het in werkelijkheid aan de generaal zou vragen en die weer aan een of andere accountant, zegt nog niet dat jij ook precies die situatie moet proberen te modelleren in je applicatie? In je applicatie moet je er dan rekening mee houden dat je de opdracht van de accountant verwerkt, niet die van de farao. Toch?

Het lijkt mij eerder dat je een of andere 'arbeidsregistratie'-module of service hebt. En daar kan je het dan direct aan vragen. Dus niet service.getGeneraal().getPersoneelsLijst().

Die generaal zit er gewoon helemaal niet bij. Je krijgt dan iets als arbeidsRegistratieService.getGewerkteUren(); en voor het personeel dat werkt die kan dan iets als arbeidsRegistratieService.registreerWerk(this, '5 dagen');

Acties:
  • 0 Henk 'm!

  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 27-04 11:59
Hmm. Goed punt. Het voorbeeld is een beetje contrived, in de zin dat het net zo goed een of andere machine of dataconstructie had kunnen zijn met onderdelen in plaats van een serie actoren (in mijn geval een database met meta-informatie), maar je argument gaat nog steeds op. Ik werk hier met een iets te... hierarchische mindset.

Die service zou dan niet naar 'de generaal' moeten verwijzen, maar direct de functies moeten bevatten die ik nu in 'de generaal' (of lager in de structuur) heb zitten en direct toegang moeten geven tot de gegevens die ik nodig heb.

Dat betekend wel dat de instantiatieprocedure iets lastiger wordt. Deze administratieService
moet zowel exposed zijn aan de onderaannemer als aan de farao. Zoals ik het nu heb wordt deze bij instantiatie gevuld door de onderaannemer. In deze context: naam ('Aladdin') en salarisgegevens ('een kannetje water en 3 dadels per uur').

Zonder management-framework om objecten aan elkaar te kleien (ik ben jsf gewend), zou een singleton of statisch benaderbare interface de makkelijkste methode zijn, maar dat is niet gebruikelijk voor een service. Hetzelfde geld voor een observer of eventhandler-achtige structuur.

Wie zou jij in deze context de controle over de lifecycle van de arbeidsregistratieService geven (dus de klasse instantieeren/afbreken)?

Localhost is where the heart is


Acties:
  • 0 Henk 'm!

  • Deikke
  • Registratie: Juni 2004
  • Laatst online: 13-05 11:31
Ik zou de gegevens omtrent wie gewerkt heeft overlaten aan een object die dit allemaal kan bijhouden. Dit object zou je dan via de hele trein wel op kunnen vragen indien je dat zou willen, indien de farao zelf niet weet waar dit object te vinden is. Dus de farao kan aan de generaal vragen om de complete lijst met alle administratie (personeelslijst, urenregistratie, materiaallijst).

Vraag jezelf af: Als de farao iets aan deze administratie aan wil passen, aan wie gaat hij dat doorgeven? De generaal, of wil hij dat zelf doen? In dit geval zal hij waarschijnlijk zelf deze administatie willen bekijken cq. beheren, dus de enige opdracht voor de generaal: Geef mij die administratie. Waar de generaal vervolgens verder gaat vragen, is niet de zorg van de farao. Zodra de juiste boekhouder/object die deze gegevens heeft gevonden is, heeft de generaal zijn taak gedaan, hij hoeft niet nogmaals wijzigingen door te geven, dit kan de farao zelf met de boekhouder wel bespreken

Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 07-05 20:56
SilentStorm schreef op zaterdag 19 maart 2011 @ 11:45:
Wie zou jij in deze context de controle over de lifecycle van de arbeidsregistratieService geven (dus de klasse instantieeren/afbreken)?
Een lifetime manager die binnen een inversion of control framework zit wat dependency injection usw. voor je regelt.

Dat framework neemt een bestaande instantie die een 'arbeidsregistratie' interface implementeert of maakt een nieuwe instantie aan, al naar gelang de configuratie v/d lifetime manager. Deze instantie wordt geinjecteerd in de constructor van je 'farao' die er dan mee kan werken.

Of die instantie dan concreet een 'generaal' , 'aannemer', 'onderaannemer' , whatever... is zal iedereen worst wezen. Dat stel je op één plek in tijdens het opbouwen van je IoC container en dan ben je er klaar mee.

Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 19:36
R4gnax schreef op zaterdag 19 maart 2011 @ 16:06:
[...]

Een lifetime manager die binnen een inversion of control framework zit wat dependency injection usw. voor je regelt.

Dat framework neemt een bestaande instantie die een 'arbeidsregistratie' interface implementeert of maakt een nieuwe instantie aan, al naar gelang de configuratie v/d lifetime manager. Deze instantie wordt geinjecteerd in de constructor van je 'farao' die er dan mee kan werken.

..
Klinkt als Autofac :P. Alleen die heb je niet voor Java. Heeft Spring dit trouwens ook ?

[ Voor 5% gewijzigd door D-Raven op 20-03-2011 13:54 ]


Acties:
  • 0 Henk 'm!

  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 27-04 11:59
Ik zou de gegevens omtrent wie gewerkt heeft overlaten aan een object die dit allemaal kan bijhouden. Dit object zou je dan via de hele trein wel op kunnen vragen indien je dat zou willen, indien de farao zelf niet weet waar dit object te vinden is. Dus de farao kan aan de generaal vragen om de complete lijst met alle administratie (personeelslijst, urenregistratie, materiaallijst).
Bedoel je daarmee zoiets als de arbeidsregistratiemodule, waar ACM ook al over sprak? Lijkt me een goed idee!
Vraag jezelf af: Als de farao iets aan deze administratie aan wil passen, aan wie gaat hij dat doorgeven? De generaal, of wil hij dat zelf doen? In dit geval zal hij waarschijnlijk zelf deze administatie willen bekijken cq. beheren, dus de enige opdracht voor de generaal: Geef mij die administratie. Waar de generaal vervolgens verder gaat vragen, is niet de zorg van de farao. Zodra de juiste boekhouder/object die deze gegevens heeft gevonden is, heeft de generaal zijn taak gedaan, hij hoeft niet nogmaals wijzigingen door te geven, dit kan de farao zelf met de boekhouder wel bespreken
- Het gaat inderdaad om een ideaal situatie. De farao wil het zelf kunnen, maar moet via de generaal, omdat de generaal degene is die dat soort gegevens kent. Je bent nu eigenlijk mijn probleem aan het parafraseren.
- Ik zou liever ook niet de 'innerlijke werking' van de aannemers-organisatie aan de generaal willen blootstellen.
- Deze alinea lijkt tegen te spreken wat je in de vorige zei.
Een lifetime manager die binnen een inversion of control framework zit wat dependency injection usw. voor je regelt.
Ik ben het juist gewend om dit soort dingen met een IoC framework op te lossen. Voor mijn dagelijkse bezigheden werk ik erg veel met JSF en ben enigszins bekend met spring. Bij deze opdracht heb ik echter wat een andere mindset aan moeten wenden. Ik mag hierbij enkel gebruik maken van de JDK en mijn eigen code. Zonder framework wordt het.. iets lastiger om een dergelijke service aan te bieden. Vandaar mijn vraag. Ik zal 'm nog eens wat duidelijker stellen:


Hoe regel je de toegang tot- en lifecycle van een service class, die vanuit meerdere plaatsen in de applicatie moet kunnen worden bereikt, zonder een extern library te gebruiken?

Localhost is where the heart is


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
SilentStorm schreef op maandag 21 maart 2011 @ 16:35:
Hoe regel je de toegang tot- en lifecycle van een service class, die vanuit meerdere plaatsen in de applicatie moet kunnen worden bereikt, zonder een extern library te gebruiken?
Als eerste denk ik aan singleton, 'enum-singleton', eigen implementatie van een DI, een RegistryService (waarmee je eigenlijk alleen je probleem verplaatst). Er zijn vast nog wel complexere oplossingen mogelijk.

Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 07-05 20:56
SilentStorm schreef op maandag 21 maart 2011 @ 16:35:

Hoe regel je de toegang tot- en lifecycle van een service class, die vanuit meerdere plaatsen in de applicatie moet kunnen worden bereikt, zonder een extern library te gebruiken?
Door zelf een simpel IoC framework te bouwen. Moet het toch mogelijk zijn om iets simpels te realiseren wat je eisen dekt. Je kunt vast wel een paar zaken opsteken van het bekijken van Spring. Unity is weliswaar geschreven voor C#, maar kan ook nog wel leerzaam zijn.

Heb je reflection tot je beschikking? Dan kun je tenminste al runtime een geregistreerde koppeling tussen interface en concreet type ophalen en constructor dependency injection plegen. Enige wat je dan mist is lifetime management, en daar is vast wel een 80-20 oplossing op te vinden die afdoende is voor je certificering.

[rant]
Knap waardeloos trouwens dat je voor industriële certificeringen als deze bewust ondermijnd wordt als je breedgedragen industriestandaarden in wilt zetten. Het non-excuus is dat je libraries niet mag gebruiken, omdat ze pas bij andere, zwaardere certificeringsprogramma's ter sprake komen. Ze zijn dus "te moeilijk" of zo. Het lijkt wel de middelbare school! |:(
[/rant]

Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 18:27
Ga voor een SCJD opdracht absoluut niet zelf een lightweight DI framework in de lucht hijsen.
KISS is hier vele malen belangrijker dan proberen "perfecte" code te schrijven. Doe niet meer dan strict noodzakelijk is om de functionele eisen van de opdracht te dekken. Maak de code vooral niet complexer dan nodig door te zoeken naar de meest "elegante" oplossing. Testbaarheid is wel erg belangrijk. En blijf bewuste keuzes maken. Die zal je uiteindelijk ook als deel van de opdracht moeten opleveren. Perfectie nastreven gaat je hier zeker niet helpen.

Kijk eventueel ook eens op het SCJD/OCMJD forum van Coderanch.

Acties:
  • 0 Henk 'm!

  • ameesters
  • Registratie: Juni 2008
  • Laatst online: 05-01-2022
wat jij volgens mij zoekt is een Observer Pattern:
Wikipedia: Observer pattern

Of begrijp ik je verkeerd?

Acties:
  • 0 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 23-04 16:41

drm

f0pc0dert

Volgens mij is je uitgangspunt niet helemaal goed. Je moet beginnen bij Separation of Concern en Design by Contract, daarna volgen de patterns, en dan blijkt vanzelf dat de Law of Demeter gaat gelden. Als je het goed doet tenminste.

Met andere woorden, de Law of Demeter vind ik niet zo'n goede raadgever, Separation of Concern wel, en dan is Design by Contract een logisch gevolg. Want dat dwingt je om na te denken over verantwoordelijkheden en niet over knowledge. Knowledge zegt niet zo veel, het zegt namelijk niks over wat de verantwoordelijkheden van een bepaald component zijn, en waarom het dus voor die componenten van belang is om juist wel "knowledge" te hebben van andere onderdelen, en waarom ze dus dependent worden op die onderdelen. Je kunt namelijk niet zeggen dat iets per definitie maar van een x aantal andere dingen knowledge mag hebben (dat gebeurt in de LoD dus ook niet), maar je kunt wel zeggen dat een component maar 1 verantwoordelijkheid mag hebben.

Het grote verschil met de "echte" wereld is dat een persoon altijd meerdere gezichten heeft in verschillende situaties en in al die verschillende situaties allerlei verschillende verantwoordelijkheden. Een persoon in de echte wereld implementeert dus allerlei verschillende interfaces:
Java:
1
2
class Pharao implements RulerDictator, Fornicator, Father, Husband, PyramidOwner, CommanderInChief, TacticalGenius, .... {
}

Feitelijk is Farao in dit geval een rol, waarvan je uit moet zoeken wat in dit geval nou precies de verantwoordelijkheid (of de vraag cq. query) van de farao is. Is de Farao een client die aan een bediende vraagt wat hij wil weten? Weet de bediende waar hij moet zijn en wie hij daarvoor moet hebben? Wat is dan de rol van die bediende t.o.v. de Farao?
Java:
1
2
Pharao implements Client { ... }
Bediende implements HumanResourcesService { ... }
enzovoorts. Patterns komen daar vanzelf uit voortrollen, en als je ff niet weet waar je dat moet zoeken, trek dan gewoon GoF uit de kast, of schaf die aan als je hem nog niet hebt.

Verder ben ik het eens met FallenAngel666.

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • 0 Henk 'm!

Anoniem: 381712

SilentStorm schreef op vrijdag 18 maart 2011 @ 19:28:
...
geeft wel heeeel veel overhead. Ik probeer juist het aantal klassen wat te beperken. Als we vervolgens naast de personeelslijst ook nog een overzicht willen hebben van gewerkte uren, bij een administratie-gedeelte en daarna nog een overzicht waarin staat wat er nog nodig is om de piramide af te krijgen, dan heb je zo een handvol klassen die stampvol staan met dit soort functies.
Shja, dat is precies de reden waarom slecht georganiseerde verticale organisatie zo traag zijn. Drm slaat de spijker op de kop, een goed ontwerp begint bij het vaststellen van de verantwoordelijkheden en dat is vaak best lastig. Vooral omdat de beste oplossing vaak de simpelste is (had einstein daar niet mooie uitspraak over?).
..
Als je nu de functie aan wilt passen en je bijvoorbeeld getPersoneelsLijst hernoemt naar getPersoneelSet, dan moet je dat op 6 plaatsen aanpassen..
...
Nope, en dat is precies waar LoD om draait. Als de farao zo nodig een PersoneelSet van zijn generaal wil hebben is het aan de generaal om te zien hoe hij daar aan komt. Indien zijn aannemer enkel met een PersoneelLijst op de proppen kan komen zal de generaal, dan wel zelf wat extra werk moeten gaan verzetten in een private method, dan wel -god behoed ons- een handig mannetje opzoeken die dat klusje voor hem kan opknappen. Over het algemeen begin je met de eerste optie en refactor je naar de tweede zodra de generaal het te druk krijgt met belangrijkere zaken. Leiding geven is nl. delegeren, net als in de 'echte' wereld :P.

Een mooi klassiek voorbeeld voor LoD is de krantenjongen die betaald moet worden.

[ Voor 21% gewijzigd door Anoniem: 381712 op 13-04-2011 03:53 ]


Acties:
  • 0 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 23-04 16:41

drm

f0pc0dert

joostmoesker:
Een mooi klassiek voorbeeld voor LoD is de krantenjongen die betaald moet worden.
Leuk stuk :)

[ Voor 4% gewijzigd door drm op 13-04-2011 20:23 . Reden: cut & paste fail ]

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • 0 Henk 'm!

  • sonix666
  • Registratie: Maart 2000
  • Laatst online: 01-05 13:35
Voor zover ik kan beoordelen slaat de law of demeter vooral op het structureren van klassen wat betreft het uitvoeren van behavior/logica/state changes. Het is bijna niet toe te passen op het tonen van gegevens. Dus als het gaat om puur tonen van gegevens dan is er weinig verkeerd aan gewoon getters en setters te gebruiken.
Pagina: 1