Egyke ősosztály PHP-ben
Egyke (singleton) ősosztály írása PHP-ben nem is olyan egyszerű, mint elsőre gondolná az ember. Több buktatója is van az ilyen osztály létrehozásának, ezért gondoltam, hogy írok róla pár sort.
Elég sűrűn használom az egyke mintát a most leírt örökléssel. Jobb szeretem így, mint gyár (factory) mintával kombinálva, persze ízlések és pofonok. Az örökléses megoldásnak annyi a hátulütője, hogy nem lehet szabadon megválasztani az ősosztályt, mindig az egyke (vagy annak leszármazottja) kell, hogy legyen.
Az egyik buktató amibe beleütközik az ember az osztály létrehozásakor, hogy a ReflectionClass
úgy működik, mintha mi írtuk volna PHP-ben. Ha például reflectionnel akarunk elérni osztályon belülről egy privát metódust, akkor hibát dob. Ez nem túl szerencsés, mert a call_user_func()
és call_user_func_array()
leváltására teljesen jó lenne a reflection, viszont ilyen jogkörökkel használhatatlan.
Konkrétan az új példány paraméterezésénél lenne a reflectionre szükség. A problémát sajnos nem lehet megoldani, a konstruktort meg nem lehet publicra tenni, mert akkor az instance
osztályfüggvényen kívülről is lehetne példányosítani.
class A
{
static public function instance()
{
$reflection=new ReflectionClass(__CLASS__);
$reflection->newInstanceArgs(array());
}
protected function __construct(){}
}
A::instance(); //Fatal Error
A probléma sajnos megoldhatatlan, a PHP fejlesztői nem tartják hibának, így marad a paraméterek nélküli példányosítás.
A másik buktató a statikus tulajdonságok és függvények öröklésével kapcsolatos. Ha egy statikus függvényt a szülőből örököl egy osztály, akkor abban a statikus változók a szülő statikus változóira mutatnak. A PHP fejlesztői szerint ez teljesen rendben van, szerintem nem.
In fact static method calls are resolved at compile time. When using an explicit class name the method is already identified completely and no inheritance rules apply.
Itt sem érdemes meddő vitát folytatni, ez van, ezt kell szeretni.
class A
{
static public $i=0;
static public function i()
{
self::$i=1;
}
}
class B extends A{}
A::i();
echo 'A::$i='.A::$i; //A::$i=1
echo '<br>';
echo 'B::$i='.B::$i; //B::$i=1 (0 helyett)
Ez a probléma hál' Istennek áthidalható úgy, hogy csinálunk egy mapet azokkal az osztályokkal és példányokkal, amiket vissza akarunk hívni az instance
osztályfüggvény hívásakor. A leszármazottban az instance
hívásakor a mapet az ősosztályból fogja lekérni az értelmező.
abstract class Singleton
{
static protected $map=array();
final static public function instance()
{
if (func_num_args())
{
throw new Exception('Singleton contructors cannot have arguments.');
}
$class=get_called_class();
if (!isset(self::$map[$class]))
{
self::$map[$class]=new $class();
}
return self::$map[$class];
}
protected function __construct()
{}
final protected function __clone()
{}
}
class A extends Singleton
{}
class B extends Singleton
{}
class C extends A
{}
var_dump(A::instance());
var_dump(B::instance());
var_dump(C::instance());
Akkor sincs nagy gond, ha a PHP fejlesztői megoldják a statikus öröklődést. Annyi történik majd, hogy minden leszármazott osztály kap egy saját tömböt, amiben csak a saját példánya lesz benne.
■
na most vagy keso van vagy
egyreszt gany, masreszt lassu, miert nem jo a new self? vagy ha leszarmaztatast is akarsz, akkor http://hu.php.net/manual/en/function.get-called-class.php persze ehhez php 5.3 kell a late static binding miatt.
ez megoldas a masodik bekezdesedben felhozott problemakra is, ha a leszarmaztatott osztaly statikus fuggvenyebol el akarod erni a statikus valtozoit, akkor a self helyett a static-ot kell hasznalni (static::$foo) es maris futasidoben lesz feloldva a hivatkozas.
ez a late static binding.
de most latom hogy az uccso kodreszletedben hasznalod is a get_called_class-t.
akkor csak tisztaban vagy vele, hogy mi is ez.
na mind1, mar faradt vagyok agyalni, ird le hogy mit szeretnel/mi a problemad, es akkor vagy tudok segiteni, vagy megvilagosodom.
Tyrael
meg lefekves elott
Viszont en amondo vagyok, hogy egyke ne akarjon parametereket kapni, ha erre van szukseg akkor inkabb Factory.
Egyket amugy sem divat manapsag eroltetni, es nem veletlenul.
Tyrael
Ja, látom, ügyes a
Bizonyos esetekben jól jön az egyke, én leginkább globális változók elkerülésére használom. pl Autoload::instance(), Server::instance() meg hasonlók, ezeken belül ha egy példány kell dolgokból, akkor dekorálok a Factory-n, hogy tárolja le a példányokat egy Map-ben, vagy csak simán setter-ben adom meg, attól függ mennyire általános dologról van szó. Pl a Controller példányoknál Factory kell (mert a kéréstől függ, hogy melyik controller-re van szükség éppen, és azok egymást is hívhatják bizonyos esetekben), de Session-nél meg setter.
Bizonyos esetekben jól jön az
gondoltal mar ra, hogy a singleton is egy globalis dolog? semmi kulonbseg nincs. ha csak erre kell, akkor felejtsd el, hasznalj nyugodtan globalis valtozot.
ha csak erre kell, akkor
Sokkal több vele a macera, mint a Singleton-nal, meg jobb szeretem az osztályokhoz kötött dolgokat. Létrehozok néhány alap Singleton-t, az összes többi objektumot meg ezeken keresztül érem el.
Szia! 5.3.0-val próbáltam,
5.3.0-val próbáltam, abban már elvileg van late static binding (legalábbis ezt olvastam php.net-en), viszont nekem a static kulcsszóval ugyanúgy nem volt jó, mint self-el.
A reflection csak a paraméter lista miatt kellett volna, nem feltétlen szükséges, én általában setter-es dependency injectiont használok nem konstruktorosat.
mire kell oly sűrűn?
ezt írod:
"Elég sűrűn használom az egyke mintát a most leírt örökléssel."
Majd megöl a kíváncsiság amiatt, hogy megtudjam mire használod sűrűn a singletonokat? Én csak nagyon ritkán látom értelmét és létjogosultságát.
Relatív, most éppen egy
Más megközelítés
Singleton (and multiton) with a different approach
Mellesleg nagyon hasznos design pattern (főleg a multiton változat) pl erőforrások kezelésére.
kb 15-20 evvel ezelott
vannak mintak amelyek ma mar erossen ellenjavaltak, pl a singleton. probalj meg tesztet irni ahhoz a kodhoz, amelyik singletont hasznal. good luck.
Igen?
Nap mint nap írok teszteket Singleton osztályokhoz, erősen TDD orientált munkahelyen dolgozom. Semmi komoly problémám nem akadt ezzel.
Ki tudnád fejteni, kérlek, hogy miért gondolod, hogy ellenjavallt a Singleton használata és mi okoz nehézséget a tesztelésében?
http://sebastian-bergmann.de/
http://framework.zend.com/wiki/display/ZFDEV2/Zend+Framework+2.0+Roadmap
http://www.symfony-project.org/gentle-introduction/1_4/en/06-Inside-the-Controller-Layer
http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container
A symfony2 el is ment a DI iranyba a korabbi minden singleton felallasbol.
http://sebastian-bergmann.de/archives/882-Testing-Code-That-Uses-Singletons.html
Lets have a look at the default implementation of the Singleton design pattern in PHP:
Tyrael
DI
Mellesleg a dependency injection (ha tényleg így akarunk hívni néhány jól bevált best practice-t) abszolúte nem összeegyeztethetetlen a tágabb értemeben vett singletonnal (ld. a cikk, amit az első bejegyzésemben írtam, 5 perc alatt átalkítható "containerré" vagy inkább lookup osztállyá).
furcsa nekem a
A patternek tobbnyire best-practice-ek, amik egy egy problemara kielegitonel jobb megoldast kinalnak.
Es ezeknek hol jobb hol roszabb neveket adnak/adunk amiatt, hogy lehessen rola hoszabb magyarazkodas nelkul beszelni.
A DI az egy ugyanolyan pattern, mint a tobbi, talan egy picit jobb.
http://martinfowler.com/articles/injection.html
nincs benne semmi egetrengeto, nem is ujdonsag.
igazabol talan ugy fogalmaznek, hogy bizonyos korokben most kezdik ujrafelfedezni ezt az eszkozt.
Az hogy a tagabb ertelemben vett singletonnal mennyire osszeegyeztethetetlen, az attol fugg, hogy mit nevezel tagabb ertelemnek, es osszeegyeztethetetlennek. :)
A cikket elolvastam, itt a velemenyem rola:
Multiton = Registry + Singletons
A srac megoldasa:
Factory+Multiton
ezekkel onmagukban nincs baj, de semmivel nem lett csokkentve a statikus kulso dependencia azzal, hogy a singletonjaidat sokkal okosabb Multiton-okra cserelted.
Ha a peldanyaidat injectalod a fugvenyeidbe ahelyett, hogy ok nyulkalnanak ki es szednek ossze a fuggosegeidet, akkor hasznalhatsz nyugodtan Singletont.
Szoval nem a Singletonnal van a problema, hanem azzal, ahogyan felallitod, es biztositod a fuggosegeket a fuggvenyeid/osztalyaid kozott.
Tyrael
Igaz
Nekem mindössze azzal van problémám, hoyg a DI nem egy újdonság, hanem inkább egy programozási gyakorlat. Szerintem semmi köze nincs a design patternekhez, amik azért ennél jóval konkrétabb dolgokat írnak le. Ezt az is bizonyítja, hogy a DI-t számos különböző - ha úgy tetszik - design patternnel megvalósíthatod.
Szűk értemeben vett singleton alatt a "klasszikus" (getInstance) singletont értem, ami eleve egy kicsit sántít, hiszen egy oszálynak függetlennek kell lennie attól, hogy hogyan és mennyi objektumot példányosítok belőle. Tágabb értelemben véve a Singleton az, amikor egy osztályból egy objektumpéldányt hozunk létre és kizárólag ezt használjuk (már-már statikusan).
Összeegyeztethetelennek azt nevezem, ha ezt írod:
Miközben ez nem két ellentétes irány.
szvsz csoppet felreertik itt
valaminek menedzselni kell az egyetlen peldanyt, ami biztos, hogy nem maga az osztaly kene, hogy legyen. ez lenne az a reteg, amit korabban irtam. emellett el kellene felejteni a new operator hasznalatat ezen a retegen kivul (newable objektumok kivetelevel). kis segitseg a megerteshez.
ez az egyik resze. a masik resze pedig maga a peldakodokbol lathato. az a baj a singleton mintaval, mint olyannal, hogy ezt az alabbi modon hasznaljak (azzal a cellal hozzak letre, hogy igy hasznaljak), es ez meglehetosen nehezen tesztelheto:
UI: ez a singleton minta, lehet rajta csavargatni, atnevezgetni, es arrol szol, hogy sajat maga menedzseli az egyetlen peldanyat (privat konstruktor, clone, statikus metoduson keresztul peldanyosithato).
ZF "menekül"
ez nem kéne, hogy érv legyen,
”The secret in testing is in writing testable code” Miško Hevery
Tyrael
Tyraelnel a pont :) annyit
annyit tennek meg hozza, hogy alapvetoen hibas az az elkepzeles, hogy egy osztaly feladata "csinalni" valamit, es meg onmaga felugyeli azt, hogy egy peldany legyen belole. ez erosen SRP violation.
a singleton egy letrehozasi minta. egy idealis vilagban (ugye arra tartunk :) ) van egy reteged az alkalmazasodban, amely felelos az objektum graf letrehozasaert. egy ilyen reteg felelossege lehet az, hogy egy adott objektum csak egy peldanyban letezhet. az o felelossege, hogy az egyetlen peldanyt injektalja mindenkinek, akinek szuksege van ra. igy a unit tesztek utjaban sem all senki.
mas oldalrol, hogyan tesztelsz egy ilyen kodot, mikor nem tudod mockolni a Db peldanyt?
es meg nehany link:
- Singletons are Pathological Liars
- Flaw: Brittle Global State & Singletons
Lehet, hogy rosszul látom, de
$db
, amit a hívó környezetbenDb::getInstance()
-szal adsz meg.A lenyeg elsikkadt. Nem a
Nem a singleton az egyetlen olyan pattern/eszkoz amivel meg tudod neheziteni a tesztelest, de most errol volt szo.
A legtisztabb es legjobban tesztelheto eset az az, hogy a kod egyetlen dependenciaja input szempontbol a kapott argumentumok, es idealis esetben, modositast csak a kapott parametereken vegez.
Ezaltal jol mockolhatova valik a mukodese.
Persze a valo vilag nem mindig ilyen idealis, ilyenkor jonnek az egyeb trukkok, de ez mar nem olyan kenyelmes tesztelesi szempontbol, mint az idealis eset.
A DI-os felvetesed szinten ebbe a kategoriaba esik. Ha valamely fuggvenyedbe ilyen mesterseges fuggosegeket teszel, mint peldaul direkt fajlmuvelet wrapper fuggveny/osztaly nelkul, direkt db muvelet, globalis/statikus valtozok/fuggvenyek/osztalyok piszkalasa, akkor ott szinten nehezebb a megfelelo teszteles.
Persze minel tobbet hasznalod, a DI-t, annal kevesebb es valoszinuleg magasabb szinten definialod ezeket az eroforrasokat, ezaltal kevesebb helyen kell, es sokkal konyebb lesz mockolni oket.
Tyrael
Megint Tyraelnel a pont.a
a problema, hogy global state cuccost hasznalsz a kodban (az osztaly phpban globalis dolog), ami valojaban a kod fuggosege. injektald be a fuggosegeket. errol szol az egesz. nem tunik neheznek az egesz. kerdes miert csinaljak megis rosszul az emberek? szemleletvaltasrol is szol a dolog.
Ezt érdemes elolvasni a témában
http://misko.hevery.com/code-reviewers-guide/
Csatlakozom
egyéb
A másik dolog: miért kell Singleton ősosztályt gyártani? Egy meglehetősen egyszerű design patternről van szó, amit egyszerű implementálni bármely osztályban. Singletonokat ritkán örököltetünk (mivel ritkán is használjuk őket ;) így a rengeteg kód, amit az örököltetés megkerülésére írtál általában felesleges. És mivel nincs többszörös öröklődésünk, ezért elég pazarlásnak érzem azt, hogy az ősosztály a design patternt határozza meg, nem pedig a szemantikai őst.
Hmm, egyszer használtam már
Vélemény
Én a kedves PHP fejlesztőkkel értek egyet, ugyanis egy statikus változó célja, hogy az adott változó mindig ugyan arra a címre mutasson. Mivel egy statikus változó mindig egy helyre mutat, ezáltal a függvény ami őt felhasználja is csak ugyan azzal a címmel babrálhat. Öröklődésnél ez meg pláne értelmet kell hogy nyerjen, mivelhogy egy származtatott osztály lényegében egy ős osztály módosítva és/vagy kibővítve. Így tehát, ha egy származtatott osztály megörökli egy statikus változó "használati jogát", akkor ő is ugyan azt a címet kell hogy elérje, mint a szülő.
Emlékezzünk vissza OO tanulmányaink kezdetére. A statikus változó célja, hogy osztályonként csupán egy legyen belőle. Egy származtatott osztály nem egy új osztály, hanem egy módosított ős osztály, eképpen értelmet nyer az "osztályonként csupán egy" kifejezés. Ha Minden származtatott osztályban más és más címre mutatnának ugyanazon nevű statikus változók, akkor nem öröklődésről beszélnénk, mivel nem "örökölnék" a címet :)
Szóval ezzel nem kell vitába szállni, és nem kell lenyelni hogy "ez van ezt kell szeretni" :) Ez logikailag így helyes és a kedves fejlesztők nagyon is értik a dolgukat :)
Én nem tudom, hogy ez e a
ettol fuggetlenul ahogy irtam
ugyanez persze mukodik metodusokra is.
Tyrael
Ha ezt vesszük, akkor
Én elfogadom, ha úgy gondolják, hogy az öröklött változó az ős-höz tartozik, de akkor tényleg tartozzon oda. Szerintem van egy kis zavar a fejlesztők fejében, hogy mit is akarnak.
szerintem kovetkezetes,
late static binding nelkul egy csomo dolgot eddig csak nagyon korulmenyesen lehetett megcsinalni.
igazabol csak annyi problemam van vele, hogy ez johetett volna mar az 5-os php-val.
Tyrael
Értem, de ha az a jobb, akkor
valószínűleg ugyan azért,
parseolas kozbensokkal
ertsd aki nem szarmaztat osztalyokat, vagy szarmaztat, de a szarmasztatott osztalybol nem akar statikus methodust hivni, ott is atkerulne a self feloldasa forditasi idobol futasi idobe.
Tyrael