singleton of ... ?
Ik ben onlangs begonnen met het OO programeren.
Nu heb ik verzonnen dat ik een class 'core' heb die dingen afhandeld als berichtgeving naar de gebruiker, of debug info etc.
om nu gebruik te kunnen maken van de functions in 'core' laat ik iedere andere class 'core' extenden, maar nu komt het: hoe zorg ik er voor dat alle extendende classes dezelfde instantie van 'core' gebruiken zodat alle berichten bij elkaar uitkomen?
ik zat te denken aan singleton, maar ik heb gelezen dat dit meestal niet nodig is en anders toch wel geen idee is ivm beperkingen die dan optreden.
of is er nog een andere (betere) optie?
bij voorbaat alvast denk voor het lezen van m'n vraag
Ivar
PS als je iets niet snapt aan mijn vraag vraag het dan, dan kan ik het uitleggen!
Zodat alle berichten bij elkaar uit komen?
Quote:
Dat is technisch onmogelijk (in gelijk welke taal), een pointer kan maar een instantie van 1 class bevatten.hoe zorg ik er voor dat alle extendende classes dezelfde instantie van 'core' gebruiken
Tenzij jij zo iets bedoeld:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
$core = new Core();
$core1 = new SubCore();
$core1->core = $core;
$core2 = new OtherSubCore();
$core2->core = $core;
?>
$core = new Core();
$core1 = new SubCore();
$core1->core = $core;
$core2 = new OtherSubCore();
$core2->core = $core;
?>
Nu zit in $core1 en in $core2 een referentie naar de instantie van de 'hoofdcore'. En wel in $this->core
De registry is dan een singleton object, en is abstract.
Je kan dus altijd je referenties terug vinden door bijvoorbeeld $oCore = Registry::get('Core') op te vragen.
Combineer deze aanpak met $_SESSION en serialize() en je hebt een vergaarbak van referenties, let wel op dat je niet te veel in je registry gaat zetten natuurlijk ;)
Als je behoefte hebt aan een simpele uitwerking van mij registry roep je maar.
Voorbeeldje:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
class CCore {
private $_oUser;
function __construct()
{
$this->_oUser = new CUser;
self::instance($this);
}
public static function instance( CCore $_oInstanceNew = null )
{
static $oInstance = null;
if( $_oInstanceNew )
{
$oInstance = $_oInstanceNew;
return;
}
return $oInstance;
}
}
class CUser {
private $_oCore;
function __construct()
{
$this->_oCore = CCore::instance();
}
}
?>
class CCore {
private $_oUser;
function __construct()
{
$this->_oUser = new CUser;
self::instance($this);
}
public static function instance( CCore $_oInstanceNew = null )
{
static $oInstance = null;
if( $_oInstanceNew )
{
$oInstance = $_oInstanceNew;
return;
}
return $oInstance;
}
}
class CUser {
private $_oCore;
function __construct()
{
$this->_oCore = CCore::instance();
}
}
?>
Werkt prima.
core kan bij user en user bij core. Er is steeds maar 1 instantie van core.
Zo kun je ook een winkelmand en een product klasse maken (net als user). Klasse winkelmand doet iets met producten. Winkelmand kan in deze constructie gebruikt maken van klasse product en andersom (die alles via core).
Gewijzigd op 01/01/1970 01:00:00 door Martijn B
@midas: idd dat bedoel ik.
@hipska: de oplossing die je aandraagt zou wel eens het makkelijkste/best kunnen zijn.
@satsume: ik weet niet ofdat ik wel een registry wil gebruiken, is dit niet een beetje een 'vieze' methode voor dit soort dingen?
@Martijn!: ik bedoelde in eerste instantie idd singleton, maar ik ga nu als eerste eerst de optie van hipska bekijken en eventueel daarna die van jou.
Er is wel een verschil, in mijn oplossing kun je overal gebruik maken van de core.
ivar schreef op 05.01.2009 19:57:
bedankt voor al deze oplossingen
@satsume: ik weet niet ofdat ik wel een registry wil gebruiken, is dit niet een beetje een 'vieze' methode voor dit soort dingen?
@satsume: ik weet niet ofdat ik wel een registry wil gebruiken, is dit niet een beetje een 'vieze' methode voor dit soort dingen?
Absoluut niet, als je al je instanties en aanroepingen daarvan aan de registry overlaat heb je praktisch het zelfde als een singleton, alleen kijkt de registry of er al een instantie van een object bestaat en niet het object zelf. Sterker nog ben ik van mening dat niet alle (als je er meer hebt waar ik donder op zeg/ga zeggen) objecten de functies moeten bevatten om zich als singleton te gedragen, dat is naar mijn mening niet hun plaats.
Een registry vangt dit op door zelf te kijken of er al een instantie van een object bestaat en die eventueel te retourneren, waar jij denkt dat je een nieuwe maakt, overigens als je wel een nieuwe instantie nodig hebt van een oibject kan je natuurlijk forcen of een andere naam mee geven voor dit object.
zou je ook nog andere voorbeelden kunnen geven waarvoor ik dat registry dan zou kunnen gebruiken?
Vaak wordt een registery als een singleton klasse gebouwd, zodat er maar één centraal register is.
Heel heel heel simpel:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
class Registery {
static public function sharedInstance() {
static $instance;
return $instance ? $instance : $instance = new self();
}
protected $_items = array();
protected function __construct() {
// protected puur om singleton af te dwingen
}
public function set($key, $value) {
$this->_items[$key] = $value;
}
public function get($key) {
if(!isset($this->_items[$key])) throw new InvalidArgumentException("Het register bevat niets met de naam $key");
return $this->_items[$key];
}
}
class DB {
}
$registery = Registery::sharedInstance();
$registery->set('db', new DB());
// willekeurig stukje code
$db = Registery::sharedInstance()->get('db');
?>
class Registery {
static public function sharedInstance() {
static $instance;
return $instance ? $instance : $instance = new self();
}
protected $_items = array();
protected function __construct() {
// protected puur om singleton af te dwingen
}
public function set($key, $value) {
$this->_items[$key] = $value;
}
public function get($key) {
if(!isset($this->_items[$key])) throw new InvalidArgumentException("Het register bevat niets met de naam $key");
return $this->_items[$key];
}
}
class DB {
}
$registery = Registery::sharedInstance();
$registery->set('db', new DB());
// willekeurig stukje code
$db = Registery::sharedInstance()->get('db');
?>
Je kan het nadeel ook al een beetje zien, wanneer je deze code in je klassen gebruikt, hebben die klassen altijd een register nodig, en moet dat register altijd die variabele bevatten. Op deze manier kan je dus niet voor klasse a, die het registery gebruikt, database a pakken en voor klasse b, die datzelfde registery gebruikt database b. Tenzij je je registery gaat aanpassen tussen de calls door.
Nog een nadeel is dat de klassen die je registery gebruiken op deze manier altijd afhankelijk zijn van het beschikbaar zijn van de klasse Registery. In theorie maakt dit je klassen minder herbruikbaar, maar in praktijk valt dit in mijn ervaring wel mee.
Wat ikzelf dan wel weer een nadeel vindt is dat je niet aan de aanroep naar de klasse, of aan de buitenkant, alle methods en hun argumenten bij elkaar, kan zien wat de klasse nu werkelijk precies nodig heeft. De API maakt niet duidelijk dat hij ook nog informatie haalt uit een register.
Het eerste en het laatste is deels op te lossen door het Singleton deel weg te laten, en één registery-instance mee te geven aan de __construct. Dan heb je er al meer controle over, maar dan is ook een groot deel van het nut weg, en of het overgebleven nut dan nog opweegt tegen het niet kunnen zien wat de klasse nodig heeft aan de API (de API beschrijft niet wat de inhoud moet zijn van je registery) is dan nog maar de vraag.
Moet je nu geen singleton of registery gebruiken? Dat is helemaal aan jezelf. Ik zelf probeer ze altijd te vermeiden, en je kan heel goed zonder leven heb ik gemerkt, al zal je altijd een soort context-object nodig hebben, een object dat toegang geeft tot bijvoorbeeld de data uit je model wil je MVC deels implementeren. Maar singleton gebruiken is veel minder denkwerk. Je zou kunnen beargumenteren dat dit tot minder mooie code leidt, maar volgens mij valt dat wel mee. Vooral in het begin is het erg handig, in de zin van een gemakkelijke oplossing voor veel problemen die je tegenkomt bij het programmeren a la OOP. En of de nadelen op jouw en je applicatie van toepassing zijn, ligt helemaal aan je eigen eisen :)
bedankt voor dit antwoord, ik zal er binnenkort naar kijken omdat ik daar nu even weinig tijd voor heb (druk op school :P ).
het werkt, maar is het ook 'netjes'?
Zoiets zou ik dan bijna eerder opslaan in een $GLOBALS.
(je moet ff wat dubbelposten verwijderen ;-))
Door keuzes te maken met behulp van de design patterns zoals singleton en registry kun je een nette opzet maken voor je applicatie.
ik wil hier dan ook gelijk aan toevoegen dat ik dit een zeer vervelende bug vind.
@han dit is hier idd beter geschikt voor.
edit: bedankt voor het verwijderen van de posts
edit 2: ik gebruik nu dit in de 'core' class:
en dan kan ik overal deze class aanroepen dmv:
dit werkt, maar als iemand nog een tip heeft om deze methode te verbeteren hoor ik het graag! ik zou eventueel ook de berichten array in een $GLOBALS kunnen zetten, maar ik denk dat dit handiger is :P
Gewijzigd op 01/01/1970 01:00:00 door ivar
Probleem 1: Je verbindt $GLOBALS['Core'] onlosmakelijk met die ene klasse. Bij een normale singleton implementatie heb je tenminste nog de vrijheid te kiezen welke klasse je aanroept, en bij een registery heb je nog de vrijheid welk object je onder een bepaalde naam in het registery stopt. Dit is in één applicatie niet zo'n probleem, maar zodra je de code wilt hergebruiken is er een redelijke kans dat dit je in de weg gaat zitten.
Probleem 2: Wanneer je nu een nieuwe instantie van Core aanmaakt, overschrijft hij de oude in $GLOBALS['Core']. Alle berichten die in de oude zaten zijn dan foetsie, tenzij je nog een andere referentie had naar die ene oude instantie. Maar dat zou betekenen dat je meerdere instanties van hetzelfde object hebt, wat weer het voordeel van singleton, of een globale message-passer wegneemt.
Probleem 3: $GLOBALS is vrij te veranderen. Wanneer jij eerst de instantie van Core start, en daarna toevallig in een lusje of ergens anders ogenschijnlijk onschuldig een variabele genaamd $core binnen de global scope gebruikt (en die is groot in PHP) is je Core instantie foetsie. Dat is soms best lastig te debuggen, en je moet je best doen goeie variabele-namen te verzinnen die elkaar niet in de weg zitten. Daarom sla je bij Singleton de instantie op in de klasse zelf, zodat het onmogelijk om vanuit de rest van je code bij deze instantie te komen. De klasse heeft volledige controle. (En dat is goed :) )
Ik kan je aanraden je klassen altijd zo autonoom mogelijk te maken. Van zo min mogelijk andere klassen afhankelijk, het liefst van geen enkele gobale variabele afhankelijk, en als je hem toch afhankelijk wilt maken van andere klassen (wel zo handig in veel gevallen, scheelt dingen dubbel implementeren en dubbel fixen) doe dat dan weer zo veel mogelijk via methods, zodat je niet tijdens het bedenken van de klasse beslist welke andere klassen zijn benodigdheden gaan geven (zoals databases) maar maak een method zodat je de database aan de instantie kan koppelen. Op die manier hoef je pas bij het gebruik van de klasse te beslissen hoe je de database gaat aanleveren.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
class Teletubbie {
protected $_database;
public function __construct() {
/* Nu kan je niet om het gebruik van Database heen */
$this->_database = new Database();
}
}
class Teletubbie {
protected $_database;
public function __construct($database) {
/* Het enige waar je je nu nog zorgen over hoeft te maken is of je
* $database wel kan wat jij hoopt hij kan. Maar daar kan je
* Interfaces (op PHPfreakz staat een goed artikel daarover)
* voor gebruiken. */
$this->_database = $database;
}
}
$database = new SuperDuperTurboDatabase(); // véél beter dan de normale Database ;)
$teletubbie = new Teletubbie($database);
?>
class Teletubbie {
protected $_database;
public function __construct() {
/* Nu kan je niet om het gebruik van Database heen */
$this->_database = new Database();
}
}
class Teletubbie {
protected $_database;
public function __construct($database) {
/* Het enige waar je je nu nog zorgen over hoeft te maken is of je
* $database wel kan wat jij hoopt hij kan. Maar daar kan je
* Interfaces (op PHPfreakz staat een goed artikel daarover)
* voor gebruiken. */
$this->_database = $database;
}
}
$database = new SuperDuperTurboDatabase(); // véél beter dan de normale Database ;)
$teletubbie = new Teletubbie($database);
?>
Bedankt allen!