OOP vraagjes
Gisteren hier op het forum weer een goed gesprek gehad over mijn framework. En dat heeft me weer even aan het denken gezet. Begin dit jaar was ik begonnnen aan een framework. Dat heeft even stilgelegen, en nu ben ik aan de slag gegaan met versie 2 :) En dan wil ik ook namespaces en exceptions gaan gebruiken.
Nu mijn vragen:
1) Stel ik ga een class maken waarmee ik directories kan aanmaken en verwijderen. Dan heb je dus 2 functies/methods, een "make" functie en een "remove" functie. Mag je deze functies in 1 class zetten, of zijn dit eigenlijk 2 aparte classes. Wat is goed OOP gebruik? Als je 1 class gebruikt, krijg je zoiets:
Directory::make($dir) en Directory::remove($dir)
Zou je 2 classes gebruiken dan krijg je zoiets:
DirectoryMaker::make($dir) en DirectoryRemover::remove($dir)
2) Stel ik heb een class met namespace Ozzie\Core en in die class moet ik ergens een directory verwijderen. De directory class heeft namespace Ozzie\Directory. Nu heb ik verschillende opties om de directory class aan te roepen, en ik ben benieuwd hoe jullie dat doen:
a) \Ozzie\Directory\Directory::make($dir);
b) boven de class plaats je "use \Ozzie\Directory\Directory;" en zodra je de directory class nodig hebt zeg je "Directory::make($dir);"
c) anders
3) In versie 1 van mijn framework stopte ik alle classes in de service-container. Echter, ik neem aan dat een Directory class niet echt een service is (of wel?). Het lijkt me dat je in een class niet dit gaat doen: $services->get('directory')->make($dir). Hetzelfde geldt bijvoorbeeld voor het inladen/verwijderen van bestanden, of het throwen van een Exception. Ik neem aan dat dit soort zaken eigenlijk niet in een service-container thuishoort.
Nu is mijn vraag, wanneer hoort iets WEL in de service-container thuis? Stel we hebben een e-mail service die automatisch geconfigureerd wordt, ja... dan hebben we het over een service. Maar neem nu bijvoorbeeld een User. Is een User ook een service? Kortom, de vraag is "Wat maakt een class tot een service? Wanneer hoort een class thuis in een service-container?"
Ik hoop dat iemand me weer een eindje vooruit kan helpen. Alvast dank!
Gewijzigd op 09/10/2013 13:28:29 door Ozzie PHP
CRUD (create-read-update-delete) en het active record pattern. Ik zou voor een directory dus één klasse gebruiken met twee methode: Directory::create() en Directory::delete().
Voor simpele objecten kun je vaak uitgaan van Meer reacties zijn welkom! (vooral ook op vraag 2 en 3)
Ozzie PHP op 09/10/2013 13:58:21:
Als een active record pattern een databasekoppeling heeft, wordt create() vaak insert(). In lijn met SQL heb je dan insert-update-delete. Maar je ziet wel vaker synoniemen, bijvoorbeeld remove() in plaats van delete() of find() in plaats van search() als je CRUD uitbreidt tot SCRUD.Kies je dan ook daadwerkelijk voor de benaming "create" (en niet voor "make")?
Vergis je je vaak bij het programmeren, dan kun je altijd nog een alias invoeren:
P.S. weet jij een antwoord op vraag 2 en 3?
Niet helemaal consistent, maar voor mij wel duidelijk: als de search() in SCRUD niet voldoende is, dan krijgt de klasse een find_by_id(), find_by_username(), enzovoort.
Bij 2 zou ik Directory::create($dir) gebruiken als een statische methode, maar dáárover verschillen de meningen.
Een servicecontainer gebruik ik zelden of nooit.
find_by_id vind ik wel grappig. Jij laat de variabele dus terugkomen in de functienaam? Doe je dat altijd? Waarom niet gewoon find($id)? Of gaat het hier om 1 class met meerdere "find" varianten?
Quote:
Bij 2 zou ik Directory::create($dir) gebruiken als een statische methode..."
Ja, dat zou ik ook doen. Maar de vraag ging er eigenlijk om hoe je de juiste namespace gebruikt. Roep je de class met z'n volledige naam aan, dus "\Ozzie\Directory\Directory::make($dir);" of maak je gebruik van "use \Ozzie\Directory\Directory"?
Quote:
Een servicecontainer gebruik ik zelden of nooit.
Ah oké. Hopelijk kan iemand anders dat dan toelichten. Het gaat er mij vooral om wat nu precies een service is. Wanneer is een class een service?
Ozzie PHP op 09/10/2013 14:48:38:
Thanks Ward. Met list bedoel je dan dat je een overzicht opvraagt of iets dergelijks? Dus $products->list(); geeft alle producten?
Bijvoorbeeld een Cart::list() inderdaad voor een lijst met de inhoud van een winkelwagentje.
Ozzie PHP op 09/10/2013 14:48:38:
find_by_id vind ik wel grappig. Jij laat de variabele dus terugkomen in de functienaam? Doe je dat altijd? Waarom niet gewoon find($id)? Of gaat het hier om 1 class met meerdere "find" varianten?
Dat doe ik uiteraard alleen als er naast een find_by_id() bijvoorbeeld een find_by_username() is. Het geldt zelfs algemener: ik gebruik $id, tenzij dat in een context onduidelijk is omdat er naast een $user_id nog een $order_id is.
Gewijzigd op 09/10/2013 15:04:52 door Ward van der Put
Iemand die nog mijn vraag over de namespace en services kan beantwoorden?
Vraag 2: b
Vraag c: Als je iets vaker gebruikt in je framework zoals config, database zet je in je service container. Hoe vaak verstuur je een e-mail? Niet vaak dus die zou ik bijvoorbeeld niet in de service container zetten maar gewoon pas aanmaken wanneer je hem daadwerkelijk gebruikt bv op login pagina etc.
En wat betreft de benamingen van functies, ik hou altijd alles zoveel mogelijk het zelfde in alle classes in mijn framework. Dus niet de ene class write, en de andere weer set. Gewoon alles set, wel zo makkelijk om te gebruiken vind ik.
1) Waarom noem je het eigenlijk FileSystem en niet gewoon File?
2) oké! :)
3) Dus eigenlijk zeg je dat jouw criterium of iets wel of niet een service is, wordt bepaald door hoe vaak je die class gebruikt? Klopt dat? En als we dan een User hebben, die heb je vaak nodig! Is dat dan een service?
Vraag 2: Hangt er van af, als je maar een klein deel van de website afgeschermt is voor gebruikers heb je niet persee de User class nodig (misschien is Service wel een class die eigenlijk noodzakelijk is voor de website om te kunnen werken) en roep je hem niet eens aan. Als je persee voor de website een ingelogde user nodig hebt (bijvoorbeeld CMS) dan zou ik User class wel in de service container zetten.
p.s. Zou je dit niet kunnen oplossen met een namespace? Dat je de class wel gewoon File noemt maar de namespace System geeft? Dan krijg je dus \System\File::remove();
2) Hmm, oké. Het is me nog steeds niet helemaal duidelijk. Ik dacht dat een service iets is wat je even "makkelijk uit de kast kunt pakken als je het nodig hebt". Bijvoorbeeld, ik moet ergens een pdf maken. Ah, mooi... ik pak even de PDF service erbij. Maar als je dan kijkt HOE VAAK je een PDF maakt? Tja, dat is dus bijna nooit. Voor m'n gevoel is een pdf-maker wel een service, maar het strookt niet met het criterium dat "een service een class is doe vaak wordt gebruikt". Snap je wat ik bedoel?
Een andere reden die ik hier iemand op het forum heb horen zeggen, is dat je een service container ook gebruikt om makkelijk je classes te kunnen beheren en te wijzigen. Stel je hebt een PDF class, en na een jaar vind je een veel beter PDF class, namelijk PDFultimate. Het nadeel is dat je nu overal in je project waar je $pdf = new PDF(); hebt gezet, je dit moet vervangen door $pdf = new PDFultimate(); Je moet dus een hoop bestanden aanpassen. Als je echter pdf als service had ingesteld, hoefde je alleen de class-naam in het configuratiebestand te vervangen. Oké, dit is natuurlijk handig... maar heeft eigenlijk weinig te maken met het feit of een class wel of niet als een service moet worden gezien. En op die vraag, wanneer een class een service is, blijft het antwoord toch erg wazig.
Gewijzigd op 09/10/2013 15:59:06 door Ozzie PHP
Als je een betere PDF class vind, kun je toch de Class zelf aan te passen ipv de Class naam? Want als je een nieuwe PDF class hebt heb je misschien ook andere methods waardoor je als nog heel veel andere bestanden moet aanpassen. Als je de nieuwe code van de PDF class gewoon in de huidige PDF class zet is dat toch veel makkelijker (overbodige methods bv exception gooien dat het een oude overbodige method is)
Of zie ik dit verkeerd?
Elke class die een andere class nodig hebt en elke class die die andere class nodig zou kunnen hebben zijn services. Een Mailer kan gebruikt worden door een class, dus die stoppen we in de container. Een NewsLetterManager heeft die mailer nodig, dus die staat ook in de container.
Een exception is een class dat alleen waardes vasthoudt, waarbij die waarden telkens anders zijn. Dat komt dus niet in een container.
Een directory class komt er ook niet in, maar ik zou er een FileSystem class van maken, die dus kan omgaan met directories en files, dan zou ik hem er wel in zetten en afstappen van die static methods.
Of je een service nou vaak of niet vaak gebruikt maakt niks uit. Het mooie van een service container is juist dat het lazy-loaded is, zodra geen een class de service opvraagt zal hij ook nooit aangemaakt worden en heb je dus geen snelheid verspilt.
En het mooie van een service container is dat je een klasse opvraagt, maar geen idee heeft wat dat nou voor class is. Ik vraag iets op wat een pdf kan maken en waarvan ik, doormiddel van interfaces, zeker van ben dat hij bepaalde methods heeft. Of dat nou Pietje_PDF(), Ozzie\Core\PDF\Creator(), Zend_Pdf() of Whaha_Ik_Doe_Niks_Pdf() class is maakt je niks uit, als hij maar zijn taak doet. Dat maakt het heel makkelijk om dus van PDF class te wisselen.
Een ander mooi voordeel van een service container is dat alle objecten standaard niet opnieuw worden geinitialiseerd. Dat gebeurd 1 keer en daarna niet meer, behalve als je expliciet zegt dat hij elke keer een nieuwe instance moet terug geven. Maar in dat geval is de class 'stateful', en dat is iets wat je moet zien te vermijden.
Gewijzigd op 09/10/2013 17:03:02 door Wouter J
Quote:
Ja maar als je het zo bekijkt kun je alles als een Service zien want elke class kan veranderen na verloop van tijd.
Precies! Dat is dus ook waarom ik twijfel wat nu wel en niet een service is.
Quote:
...
Of zie ik dit verkeerd?
Of zie ik dit verkeerd?
Dat is ook een gedachtengang die ik had, maar ik denk dat het niet klopt. Stel dat het bijv. om een class uit een library gaat, met eigen naamgeving e.d. dan kun je (denk ik) niet zomaar de ene class naar de andere ombouwen. Het mooie van zo'n container is dan dat je alleen de class-naam wijzigt. Maar wellicht kan Wouter hier even z'n licht over laten schijnen?
Quote:
Elke class die een andere class nodig hebt en elke class die die andere class nodig zou kunnen hebben zijn services.
Ah, dit is nieuw. Maar dit geldt dan toch vrijwel voor iedere class, want iedere class zou een andere class nodig kunnen hebben. Jij zegt dat een Exception class niet in de container hoort, maar... om maar eens iets te noemen... een databaseconnection class moet een Exception kunnen gooien en heeft dus een Exception class nodig. Het is een class die een andere class nodig heeft. Dan zou het toch ook in de container moeten?
Met het overige ben ik het trouwens helemaal eens! Ik wil alleen nog even heel duidelijk voor mezelf krijgen wanneer iets een service is. Een soort van definitie als het ware. Een service is een class die ... en ... en... Zoiets zeg maar. Zodat ik aan de hand van die definitie makkelijk en snel kan bepalen of het om een service gaat. Kun jij zo'n definitie bedenken?
Gewijzigd op 09/10/2013 17:42:54 door Ozzie PHP
Quote:
Kun jij zo'n definitie bedenken?
Een service is een class die (1) een andere class nodig hebt en (2) die (a) die andere class nodig zou kunnen hebben en (b) die vaste waarde in zijn constructor heeft niet niet veranderd hoeven te worden.
Dat vat het denk ik wel mooi samen. Een container 'bevriest'/gaat op slot, dus dan kun je de waardes die je aan een constructor meegeeft telkens veranderen.
Quote:
Maar wellicht kan Wouter hier even z'n licht over laten schijnen?
Ik snap niet precies wat jullie willen. Maar wellicht dat een, zoals Symfony dat noemt, "bridge" de uitkomst bied. Stel we hebben een interface, Ozzie\Bridge\PdfBridge\CreatorInterface. Deze bepaalt welke methods een PDF creator moet hebben. Zodra we een class hebben, zoals Pietje_Pdf() en die willen gebruiken als Pdf creator in onze container maken we een bridge class, die zorgt dat je alsnog de methods in de CreatorInterface kunt gebruiken met deze klasse:
Code (php)
Nu registreer je Ozzie\Bridge\PdfBridge\PietjePdfCreatorBridge en je kunt de service altijd gebruiken!
Gewijzigd op 09/10/2013 17:57:58 door Wouter J
Quote:
Ik snap niet precies wat jullie willen.
Het ging er niet om dat we iets wilden. De stelling van Hertog Jan was dat je geen service nodig hebt, omdat je gewoon de class zelf kunt aanpassen. Dus stel je hebt overal in je code $pdf = new PDF(); staan, en je wil dan die PDF class een keer veranderen, dan zou Hertog Jan die PDF class zelf aanpassen. Hij zou dus de code in die class vervangen. (In tegenstelling tot een service waarbij je alleen de class-naam in het config-bestand hoeft aan te passen.) De vraag was of jij het eens bent met die stelling.
Quote:
Een service is een class die (1) een andere class nodig hebt en (2) die (a) die andere class nodig zou kunnen hebben en (b) die vaste waarde in zijn constructor heeft niet niet veranderd hoeven te worden.
Aha! Daar ga ik even wat dieper op in...
Een service is een class die:
1 ) een andere class nodig heeft EN
2a) die die andere class nodig zou kunnen hebben EN
2b) die vaste waarde in zijn constructor heeft niet niet veranderd hoeven te worden.
Wouter, ik probeer dit even voor mezelf begrijpelijk te gaan maken.
Punt 1 en punt 2a, kunnen we die samenvoegen tot:
Een service is een class die een andere class nodig heeft of nodig zou kunnen hebben?
Punt 2b. Hier bedoel je waarschijnlijk mee dat je overal in je project dezelfde (geinitialiseerde) service aanroept. Ik neem aan dat dit betrekking heeft op deze eerdere opmerking van jou: "Een ander mooi voordeel van een service container is dat alle objecten standaard niet opnieuw worden geinitialiseerd." Is dat wat je hier bedoelt? En meteen vraag ik me dan af, als je de service zo instelt dat hij wel telkens opnieuw wordt geinitialisseerd, is het dan eigenlijk geen service?
Laat ik van dit laatste een voorbeeld geven. Stel ik heb een of andere mailer:
$mailer = $this->services->get('mailer');
$mailer->setTo('Wouter', '[email protected]');
$mailer->setSubject('hoe gaat het?');
$mailer->setMessage('Hallo, hoe gaat het ermee?');
$mailer->send();
Stel nu, heel ergens anders in de code wil ik weer een mail versturen, maar naar totaal iemand anders, met een ander onderwerp en ander bericht. Dan kan ik me voorstellen dat ik aan mijn services een "nieuwe" mailer opvraag, waar nog geen gegevens in staan. Toch heb ik het idee dat die mailer (ondanks dat ie telkens een nieuw exemplaar teruggeeft) wel een service is. Of klopt dat dan niet?
Quote:
Een container 'bevriest'/gaat op slot, dus dan kun je de waardes die je aan een constructor meegeeft telkens veranderen.
Hier kan ik je even niet volgen?
Quote:
De vraag was of jij het eens bent met die stelling.
Nee, totaal niet. OO gaat er juist om dat je alle aanpassingen kan doen zonder de class te hoeven aanpassen.
Quote:
Wouter, ik probeer dit even voor mezelf begrijpelijk te gaan maken.
Goed, ik deelde het express op in 1 en 2. Dat zijn de 2 voorwaarden, voorwaarde 2 heeft 2 voorwaardes die in elkaar horen. Dus als een class een deze class nodig heeft, maar zijn constructor parameters zijn telkens anders is het nog steeds geen service.
Laat ik eerst even kijken wat een container doet. Deze maakt een object aan wanneer nodig. De constructor heeft bepaalde argumenten nodig, sommige zijn services andere static waardes (parameters genaamd).
Nadat alle services en parameters zijn geladen wordt de klasse op slot gezet, je kan geen parameters meer veranderen en de waardes die je aan een constructor meegeeft zijn dus altijd hetzelfde. Het maakt niet uit of je die nou 1x of 100x opnieuw laat aanmaken, je maakt hem altijd aan met dezelfde argumenten. Je krijgt alleen telkens een nieuwe instance.
En dat bedoelde ik dus met 2b. Een exception class is geen service, omdat je telkens bij het aanroepen andere message, code en previous exceptions moet meegeven aan de constructor.
Een Mailer service is wel een service, aangezien de waarde van de constructor (host, port, username, etc.) wel telkens hetzelfde blijven in 1 applicatie.
Een PDF generator is ook een service, aangezien deze helemaal geen argumenten nodig heeft.
Quote:
Nee, totaal niet. OO gaat er juist om dat je alle aanpassingen kan doen zonder de class te hoeven aanpassen."
Oké, dat idee had ik ook.
Quote:
Dus als een class een deze class nodig heeft, maar zijn constructor parameters zijn telkens anders is het nog steeds geen service.
Dus wat jij wilt zeggen is dat wanneer je de class telkens aanroept met andere variabelen in de constructor, dan is het geen service. Correct? Dit betekent dus wel dat ik die betreffende class telkens "hardcoded" moet gebruiken. Dus $class = new Class(); Als ik dan een nieuwe versie van die class wil gebruiken, dan moet ik óf overal de class-naam aanpassen, of ik moet de class zelf aanpassen. Maar volgens mij hadden we zojuist besloten dat dat niet handig is, toch?
Quote:
Het maakt niet uit of je die nou 1x of 100x opnieuw laat aanmaken, je maakt hem altijd aan met dezelfde argumenten. Je krijgt alleen telkens een nieuwe instance.
Euh, wacht even... je krijgt toch niet telkens een nieuwe instance? Je krijgt toch juist telkens dezelfde instance terug?
Quote:
Een PDF generator is ook een service, aangezien deze helemaal geen argumenten nodig heeft.
Dit klopt dan weer niet met jouw eerdere uitspraak:
Quote:
Een service is een class die (1) een andere class nodig hebt ...
Maar als ik er nu zo over nadenk, dan komt het er eigenlijk op neer dat een service een class is die gedurende de request niet verandert. Je maakt 'm eenmaal aan (ongeacht of ie wel of geen contructor parameters nodig heeft) en vervolgens wordt dat betreffende object gebruikt. Services zijn dus eigenlijk onveranderende objecten???
>> Maar volgens mij hadden we zojuist besloten dat dat niet handig is, toch?
Noem mij eens klassen anders dan exception en entities (die toch al geen services zijn), die nog meer telkens andere constructor variabelen hebben?
>> Dit klopt dan weer niet met jouw eerdere uitspraak:
Klopt perfect. PDF creator is een service omdat hij aan voorwaarde 2 voldoet: classes kunnen hem nodig hebben en hij heeft veen veranderende argumenten in de constructor.