Moin!
Aber mal zur Sache.
:)
Ein einziges Singleton reicht schon, damit das Leben nicht mehr so schön ist.
Gutes Argument! Mich verwirrt ein wenig dass du strikt dagegen bist später aber:
Ich bin strikt dagegen. Singletons sind nicht vernünftig testbar. Sie erzeugen fix codierte Abhängigkeiten und Global State.
Ich bin sehr dafür, Konfigurationsdaten und Programm strikt zu trennen. Ob nun als Singleton oder sonstwie gelöst: Diese Klasse liest einmal die Konfiguration ein und liefert die darin definierten Werte aus. Fertig.
Das ich hier Singletons erwähne, liegt lediglich daran, dass andere sie so gern verwenden und sich auch angesprochen fühlen sollen.
Am Ende hab ich 100 Zeilen die einfach funktionieren. Was sagst du, wenn ich noch eine Importdatei bräuchte müsste ich alles nochmal machen. Recht hast du.
Ich hab absolut nichts dagegen, dass man Wegwerfcode quick und dirty schreibt, benutzt, und dann wegwirft.
Da gibts keine Notwendigkeit für automatisierte Tests (das macht man interaktiv für nur den konkreten Fall manuell), es gibt keine Wartungsprobleme bei langfristiger Nutzung, keine Migration und Anpassung an sich verändernde Anforderungen.
Das funktioniert aber nur, wenn man den Code wirklich wegwirft. ;)
Ein Defaultwert ist für mich essenziell wichtig. Der Idealfall ist, dass ein Programm immer weiß was der Anwender machen möchte.
Wenn du vom Anwender sprichst, dann meinst du den Programmierer? Denn das ist der Anwender deiner Klasse mit dem Defaultwert.
Das dies nicht realisierbar ist, liegt auf der Hand, es sollte jedoch ein Anstreben sein. Star Trek Fans werden das Wort "Notenergie" wahrscheinlich zugenüge gehört haben. Ein Defaultwert ist der Zustand (Notenergie) in den etwas fällt (z.B. ein Objekt), wenn alle Stricke reißen.
Wenn ich Programmierer und also Anwender deiner Klasse wäre, dann würde ich von der Klasse im Prinzip nur dein Interface sehen. Und das Interface sagt mir dann, dass ich einen Wert angeben _kann_ für die Cookie-Lebensdauer.
Nun kennt man es ja vom Setzen von Cookies, dass man die Cookies persistent macht, wenn man eine Lebensdauer angibt, und dass es nur Session-Cookies sind, wenn man nichts angibt. Folglich würde ich beim Anblick des Interfaces vermuten: Die Lebensdauer triggert die Persistenz des Cookies, lasse ich die Angabe weg, gibts Session-Cookies.
Und deine Klasse enttäuscht mich, indem sie den für mich willkürlichen Wert von 14 Tagen einsetzt. Warum 14 Tage? Kann man das irgendwo nachlesen? In der Klasse steht es jedenfalls nicht, denn dort ist, wie du sagst, nur der Aufruf des Singletons zur Besorgung des Default-Wertes. Also etwas potentiell konfigurierbares.
Nun ja, ich bin also in die 14-Tage-Falle getappt, und korrigiere meinen Fehler. Ich will explizit 21 Tage, also setze ich die 21 Tage explizit in den Aufruf.
Andere Entwickler kommen und gehen, und irgendwann entscheidet jemand, den Default-Wert auch auf 21 Tage zu setzen. Das berührt natürlich erstmal die Frage: Woher weiß man, welche Stellen das alles berührt? Aber darum solls eigentlich nicht gehen.
Denn es vergeht noch etwas mehr Zeit, und wieder einmal wird die Cookie-Lebensdauer von 21 Tagen als falsch empfunden und soll überall reduziert werden auf 7 Tage... was natürlich dort scheitert, wo ich explizit 21 Tage konfiguriert hatte. Hmpf... ;)
Ich gebe gern zu: Dieses Beispiel ist eigentlich eher eine Anwendung von Konfigurationsparametermanagement. Die Frage, die ich deshalb eigentlich stellen sollte: Wieso erlaubt mir deine Klasse das Wohlgefühl des Rundum-Glücklich-Pakets, obwohl im Sinne der Applikation und der Transparentmachung der nötigen Konfigurationen eigentlich zu fordern wäre, dass ich meine 21 oder 14 oder sonstwie besonderen Tage Cookielebensdauer viel besser ganz bis in die Konfigurationsdatei der Applikation getragen hätte. Die Konfiguration hätte dann festlegen können, dass tatsächlich meine individuellen 21 Tage gelten sollen, oder dass man in der Konfiguration umstellt auf den globalen Lebensdauerwert von 14 Tagen, den man dann ändert auf 21 Tage, und später auf 7.
Die Klarheit in der Anwendung der Klasse ist für mich größer, wenn mich die Klasse zwingt, explizit einen Wert anzugeben, und wenn ich diesen Wert auf sehr einfache Weise über die Konfiguration dorthin bekomme.
Das bedeutet für mich:
- Es gibt keinen Default-Wert, der auf magische Weise schon mal vorbelegt wird.
- Die Klasse fordert explizit alle ihre Abhängigkeiten (und die Konfiguration der gewünschten Cookie-Lebensdauer betrachte ich jetzt mal als so eine - du darfst dir aber gerne auch andere Parameter vorstellen, wie URLs, Dateinamen, etc.) vom Programmierer ein, und dieser muss liefern.
- Die Lieferung ist auf einfache Weise möglich, indem man das Konfigurationsobjekt auf einfache Weise an der Stelle verfügbar hat, wo man es benötigt, und in dem es auf einfache Weise möglich ist, den gewünschten Wert entweder individuell festzulegen, oder den Default-Wert zu übernehmen.
- Die Übernahme des Default-Wertes anstelle eines individuellen ist Aufgabe des Konfigurationsobjekts.
Vorteil: Weniger Testaufwand, weil es nur eine gültige Art des Aufrufs gibt, nämlich mit explizit angegebenem Parameter. Und den kann ich im Test variieren.
Nachteil der Singleton-basierten Injektion des Default-Wertes: Wie soll ich zum Test dort unterschiedliche Werte hineinbekommen?
Vorteil:
- etwas funktioniert mit möglichst geringer Konfiguration
Würde ich nicht so sehen. Im Gegenteil muss sich die Klasse nicht nur mit sich selbst beschäftigen, sondern auch noch mit der Beschaffung des Default-Wertes.
- weniger Exception Behandlungen
Exceptions sind bislang nirgends im Spiel gewesen. Im Vergleich zwischen magischen Default-Werten und expliziter Werteübergabe sehe ich auch nicht, warum da irgendwo eine Exception vorkommen muss.
- wahrscheinlich einfachere Aufrufe (in deinem Fall müssen ja immer alle Parameter bekannt sein)
Die Parameter stehen sowieso alle in der Funktionssignatur. Und das Konfigurationsobjekt herzubekommen, um die konfigurierte Lebensdauer oder sonst einen Wert hineinzutun, ist auch nicht schwer.
Nachteil:
- Objekte laufen mit Defaultwerten, auch wenn man das nicht möchte...
Objekte haben zwei Gesichter, obwohl sie nur eines haben sollten: Mit explizitem Wert, oder mit Default.
Außerdem: Objekte sind hart verdrahtet abhängig von dem Konfigurations-Singleton, obwohl man eigentlich nur einen Integer übergeben müsste.
Die Cookie Klasse würde in Auszügen bei mir so aussehen
class cCookie
{
$intCookieLifetimeDays = cSystem::init()->getCookieLifetime();
public function getCookieLifetime()
{
return $this->intCookieLifetimeDays;
}public function setCookieLifetime($intCookieLifetimeDays)
{
$this->intCookieLifetimeDays = $intCookieLifetimeDays
}
}
>
> Der Defaultwert wird anfangs von meinem Gott-Singleton in das Objekt gesetzt. Das wird im Konstruktor erledigt. Danach hat das Objekt einen Defaultwert. Der kann jederzeit durch die Setter Methode angepasst werden.
Ich würde mir sowohl den Singleton-Aufruf im Konstruktor, wie auch die Getter und Setter für diesen Wert komplett sparen, und stattdessen im Konstruktor einfach nur den gewünschten Wert übergeben.
Das spart mir zwei Methoden in der Klasse (Objekte haben sowieso meist zuviele davon), ich habe keine hartcodierte Abhängigkeit vom Singleton, und die Funktionsweise der Klasse wird in ihrem Interface deutlicher: "Wenn du mich benutzen willst, brauche ich die gewünschte Lebensdauer der Cookies in Tagen."
An der Stelle, an der man die Klasse dann wirklich braucht:
`$cookieSetter = new cCookie(cSystem::init()->getCookieLifetime());`{:.language-php}
Ja, da ist dein Singleton noch drin. Das kann ich nicht so einfach rauswerfen, weil dein gesamtes System darauf basiert. Ich findes es, jetzt wo ich es genauer betrachte, übrigens interessant: Du setzt da, jedenfalls laut dem Funktionsnamen, eben gerade KEINEN Default-Wert für die Cookie-Lebensdauer, denn sonst hieße die Funktion sowas wie "getDefaultCookieLifetime".
Weil sie aber nun mal nicht so heißt, ist das eigentlich schon der beste Beweis dafür, dass es in deinem System sowas wie Default-Konfigurationswerte nicht gibt. Es gibt nur Konfigurationswerte.
Jeder Default-Konfigurationswert, der verwendet werden soll, erfordert eine Methode im Singleton (und mutmaßlich irgendwie einen Eintrag in einer Konfigurationsdatei). Wann immer eine Klasse einen neuen Default-Wert braucht, muss das Singleton angepasst werden, damit die Verwendung der Klasse dann ohne das Singleton auskommen kann - oder nochmal einen Konfigurationswert erfordert, der ebenfalls aus dem Singleton kommt.
Sieht für mich irgendwie nicht wirklich nach Arbeitserleichterung aus. :) Mal abgesehen von der beständigen Verwendung des Singletons.
> Wenn du jetzt irgendwann den Defaultwert ändern möchtest, darfst du das an x Stellen machen. Außer natürlich du kapselst den Aufruf in irgendeiner Form...also Depency Injection. Das hast du ja als erstes Vorgeschlagen.
`new cCookie(14);`{:.language-php} ist Dependency Injection. Die externe Abhängigkeit ist der zu benutzende Wert für die Cookie-Gültigkeit, ohne den die Klasse nicht arbeiten kann.
> Man könnte natürlich das ganze auch ohne D.I. erledigen:
> ~~~php
> new cCookie(getCookieLifetime());
>
Das ist AUCH Dependency Injection. Die Dependency der Klasse ist die Lebensdauer des Cookies.
Dann hat man jedoch wieder das Problem des Gott-Singletons bzw. verstreuen sich dann die Konfigurationselemente vielleicht im gesamten Sourcecode.
Ich bin ja gegen Singletons, erst recht die von Karel Gott. ;)
OOP hin, Martin Fowler her, du kannst es nennen wie du willst, aber ich stehe auf mein Gott-Singleton. Und wenn mein Chef irgendwann mal möchte, dass es anders heißt, dann werde ich deinen Chef dazu anstiften dein Depency Injektion Verwaltungsobjekt anders zu benennen.
Man gut, dass mein Chef darauf keinen Einfluss hat. ;)
- Sven Rautenberg