Ik zat even in de code te kijken van een Yaml parser (Spyc) en daarin zie ik een paar static methods die vervolgens een nieuw object van diezelfde class aanmaken:
<?php
public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) {
$spyc = new Spyc;
return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes);
}
?>
Kan iemand uitleggen waarom ie een nieuw object aanmaakt? Het nut van een static method is toch juist dat je niet iedere keer een nieuw object hoeft aan te maken?
Dit type self-creation pattern is een goedkoop/zuinig alternatief voor een externe factory of builder. Als je alleen maar een Foo nodig hebt en die Foo al volledig beschreven wordt door de class Foo zelf, dan kun je de Foo-klasse zelf een Foo-object laten maken, zonder tussenkomst van een FooFactory of een FooBuilder.
Logischerwijs moet die methode in class Foo zelf dan static zijn, want je wilt daarmee een new Foo kunnen maken als er nog geen Foo-object is.
En onderwater wordt dan een nieuw Spyc aangemaakt. Dat slaat toch nergens op?
Dat is hetzelfde als je dit zou doen:
$spyc = new Spyc();
$spyc->YamlDump();
Wat is in dit geval dan het nut of de toegevoegde waarde om een statische functie te gebruiken als onderwater toch stiekem een nieuw object wordt aangemaakt?
Het hangt ervan af. Het voegt inderdaad niets toe als je een standaard pattern gebruikt:
<?php
$foo = new Foo();
?>
Je kunt dit een prototype pattern noemen als de class Foo() op eigen kracht een volwaardige Foo oplevert.
De zaak verandert als je een ander creational pattern gebruikt, bijvoorbeeld een factory pattern:
<?php
$foo = FooFactory::create();
?>
Stel nu dat we hiervoor deze opzet hebben gebruikt:
<?php
class Foo
{
}
class FooFactory
{
public static function create()
{
return new Foo();
}
}
?>
Dan valt op dat de factory betrekkelijk weinig verantwoordelijkheden heeft: het factory pattern geeft nog steeds een object terug volgens een prototype pattern.
In dat geval kun je daarop bezuinigen (bijvoorbeeld in een extra round-trip naar het file system via een autoloader) door beide in elkaar te schuiven:
<?php
class Foo
{
public static function create()
{
return new Foo();
}
}
?>
Nu hebben we dus een mengvorm van het prototype pattern en het factory pattern. Dat is wat jij in die code aantreft.
Dit kán een anti-pattern worden, met name wanneer de factory wél aanvullende verantwoordelijkheden heeft. Bijvoorbeeld in het volgende geval kunnen we het geheel niet meer zomaar "samenvouwen":
<?php
class User {}
class SuperUser extends User {}
class UserFactory
{
public static function create($type = null)
{
if ($type == 'admin' || $type == 'root') {
return new SuperUser();
} else {
return new User();
}
}
}
?>
Oké... maar mijn vraag is vooral, waarom zou je zoiets als dit willen doen?
<?php
class Foo
{
public static function create()
{
return new Foo();
}
}
?>
Ik heb dus een Foo nodig. Waarom zou ik dan dit willen doen:
<?php
$foo = Foo::create();
?>
In plaats van
<?php
$foo = new Foo();
?>
Daarbij komt dat in de method uit het 1e berichtj er ook geen object wordt teruggegeven. Ik voer dus een statische functie uit. Handig zou je zeggen, want dan hoeft er geen object te worden aangemaakt (gunstig voor het geheugen). Maar onderwater wordt er gewoon wel een object aangemaakt. Stel ik moet bijv. 10 bestanden inladen, dan wordt er 10x een object aangemaakt. Misschien zie ik iets over het hoofd, maar ik begrijp die hele constructie niet eigenlijk... :-s
Je zou dat kunnen doen om dezelfde reden als waarom je een factory of builder gebruikt: je kunt de methode create() andere/meer dingen laten doen. In dat geval zou ik dus wél een aparte klasse toevoegen.
Bovendien kun je in een constructor niet toereikend exceptions afhandelen, waardoor een directe aanroep van een complexe new Foo() zich minder makkelijk laat aanpassen en testen dan een omweg langs Foo::create().
Verder kun je in het wild nog een luie en onelegante reden aantreffen: één grote klasse die op eigen kracht al alles kan (en waarschijnlijk te veel doet), waardoor de maker het nut van een aparte factory of builder niet inzag.
Tot slot zou het nog zoiets kunnen zijn als je bij aliassen en updates vaak ziet: hetzelfde op twee verschillende manieren doen, omdat de één "zus" en de ander "zo" handiger vindt of omdat we iets vroeger "linksom" deden en dat nu "rechtsom" doen.
Ik ken de parser niet (linkje?), maar het zou ook nog een "legacy support"-erfenisje kunnen zijn van functioneel PHP dat naar OOP is herschreven. Daarin worden relatief vaak statische methoden gebruikt, omdat die zich als functies laten aanroepen.
Het begint met drie keer if (!function_exists(...)) en de public static function wordt daarna ook niet geschuwd, dus dat riekt inderdaad naar een herschreven function library.
Dat zegt — overigens — verder niets over de kwaliteit van de oplossing, maar aangezien je YAML wilt gebruiken voor de configuratie van de kernel en de applicaties in een OOP-framework, lijkt bijvoorbeeld Symfony\Component\Yaml me een veel eleganter voorbeeld.
Ja, daar ben ik nog niet helemaal over uit. Er staan best goede berichten namelijk over Spyc. En het is maar 1 bestand wat op zich ook wel fijn is.
Ik heb die yaml reader straks niet bij ieder request nodig, dus ik wil 'm pas inladen op het moment dat ik 'm nodig heb. Bij spyc hoef ik alleen een bestand te includen. Bij de symfony variant moet ik een autloader variant inschakelen. Ik weet niet eens of het een psr-0 of psr-4 autoloader gebruikt. En als je het efficiënt wil doen, dan zou je telkens voordat je de yaml reader gebruikt de autoloader moeten registeren, en als je klaar bent weer unregisteren. bij spyc is dat niet van toepassing. Dat vind ik wel een voordeel.