getInstance() sur une classe fille

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : getInstance() sur une classe fille

par Hywan » 14 févr. 2008, 17:43

Hehe, les Singleton sont parfois obligatoires pour éviter des comportements étranges. À toi de choisir ;-).

par Genova » 14 févr. 2008, 15:30

Hmm effectivement tu peux faire comme ca, mais je voulais un truc intuitif, pas forcement tomber dans un truc trop complexe ^^

Pas grave, les singletons sont pas non plus obligatoires :)

par Hywan » 13 févr. 2008, 19:29

Un premier jet :
<?php

class SingleFactory {

    private static $_instances = null;

    public static function get ( $className = '' ) {

        if(empty($className))
            throw new Exception('Classname should not be empty.');

        if(self::$_instances === null)
            self::$_instances = new ArrayObject(array(),
                                                ARRAY_AS_PROPS, 'ArrayIterator');

        // ici, le factory fait son affaire, il trouve la classe,
        // et la charge (require_once par exemple)
        // sinon erreur.

        $object = new $className();
        // on peut aussi utilisé la réflexion ou des callbacks,
        // et tout le barda, mais on va faire simple.

        // on vérifie que l'objet n'existe pas déjà
        if(self::$_instances->offsetExists($className))
            return self::$_instances->offsetGet($className);

        // sinon on l'ajoute !
        self::$_instances->offsetSet(
            $className, // ou get_class($object), ça dépend de tes cas
            $object
        );

        return $object;
    }
}
C'est un premier jet, il y a sûrement des choses à améliorer. J'aurais plus de temps, je le ferais un peu mieux, mais je dois partir, désolé.

par Genova » 13 févr. 2008, 19:16

A la fabrique de singleton ? Mélange de factory et de singleton ?

par Hywan » 13 févr. 2008, 19:07

Oui, c'est sûr il manque quelque chose.
As-tu pensé à la fabrique de Singleton ?

Ou sinon, il faut revoir l'architecture de ta portion d'application :).

par Genova » 13 févr. 2008, 19:04

Le but est justement de ne pas déclarer le singleton dans toutes les filles. Il manque apparament un outil à PHP pour détecter lorsqu'il se trouve fille avec un appel static.

par Hywan » 13 févr. 2008, 18:58

En effet, c'est intéressant.
J'ai tenté plusieurs choses rapidement, une des plus concluante est :
<?php

header('Content-Type: text/plain');

abstract class Mere {

    protected static $_instance = null;

    private function __construct ( ) {

        echo 'Bonjour bonjour' . "\n";
    }
}

class Fille extends Mere {

    public static function getInstance ( ) {

        if(parent::$_instance === null)
            parent::$_instance = new self();

        return parent::$_instance;
    }

    public function test ( ) {

        echo 'Ceci est un test' . "\n";
    }
}

$f = Fille::getInstance();
$f->test();
L'inconvénient est qu'on doit exporter la classe getInstance dans la classe Fille. Impossible de déclarer :
abstract public static function getInstance ( );
dans la classe Mere (erreur stricte).

Un coup d'oeil dans le manuel en anglais serait de bonne augure je pense. Apparemment, ça pourrait nous aider, je n'ai lu qu'en diagonal. Une solution doit bien exister.

Sinon comme le proposait Calimero (et c'est pas si bête si on y réfléchit un peu ;-)), serait de faire une classe Factory mais qui fonctionne sur des instances uniques (repère la clé avec get_class()). Ça poserait encore quelques soucis si on veut plusieurs classes Fille, il ne risque d'enregistrer que la classe mère et donc impossible d'avoir plusieurs filles (si on me répond : « bah, faut faire des garçons », je tape à coup de clavier ^^). Tout dépend du comportement que tu veux obtenir au final.

Il reste encore une autre solution, c'est de passer à travers une interface :
<?php

header('Content-Type: text/plain');

interface IFille {

    public static function getInstance ( );
}

abstract class Mere {

    protected static $_instance = null;

    private function __construct ( ) {

        echo 'Bonjour bonjour' . "\n";
    }
}

class Fille extends Mere implements IFille {

    public static function getInstance ( ) {

        if(parent::$_instance === null)
            parent::$_instance = new self();

        return parent::$_instance;
    }

    public function test ( ) {

        echo 'Ceci est un test' . "\n";
    }
}

$f = Fille::getInstance();
$f->test();
Plus de soucis de cette façon, tout fonctionne parfaitement. Le seul problème, c'est qu'il faut étendre Mere, et implémenter IFille, c'est pas super super. Et surtout, on laisse le soin au fille de sa déclarer elle-même, pas super non plus.

Ce que je ne comprends pas, c'est pourquoi on peut déclarer une méthode statique dans une interface et pas dans une classe abstraite. Le manuel ne dit rien à ce sujet ?

Àmha, la meilleure solution reste la fabrique à Singleton ;-).


Ah j'oubliais, toutes les solutions affichent :

Code : Tout sélectionner

Bonjour bonjour Ceci est un test
évidemment.

par Calimero » 13 févr. 2008, 18:33

Attention Calimero, il me semble que tu confonds 2 design patterns : Singleton et Factory. Singleton permet une seule instance de l'objet, quant à Factory, il distribue des instances à partir d'une chaîne de caractères, d'un tableau etc. représentant des noms de classes — par exemple.
Cool, y'en a un qui suit ;-) Effectivement, à peu de choses près, ça fait un factory. Je raisonnais par rapport à un test que j'avais sous les yeux, où je transmettais le nom de la classe sans jamais l'écrire explicitement, ce qui gardait la méthode getInstance() raisonnablement courte pour ne pas polluer les classes filles.

par Hywan » 13 févr. 2008, 18:19

Attention Calimero, il me semble que tu confonds 2 design patterns : Singleton et Factory. Singleton permet une seule instance de l'objet, quant à Factory, il distribue des instances à partir d'une chaîne de caractères, d'un tableau etc. représentant des noms de classes — par exemple.

par Calimero » 13 févr. 2008, 16:37

Après plusieurs essais infructueux, j'en arrive hélas à la même conclusion : Pas possible en l'état des choses avant PHP 5.3.

Mais il y a apparemment des façons de faire plus légères que de redéfinir complètement getInstance(). (par exemple en lui passant le nom de la classe à instancier, ce qu'il n'arrive pas à trouver tout seul), je crois d'ailleurs que c'est la technique couramment utilisée dans plusieurs projets implémentant ce pattern.

par Genova » 13 févr. 2008, 16:11

Salut,
ça ne marche pas malheureusement, il persiste à m'appeler la classe "Papa". J'ai vérifié le debug backtrace, il ne passe jamais dans la classe fille :(

Ca doit probablement être impossible dans l'état actuel, dommage.

par Calimero » 13 févr. 2008, 15:50

Vu sur le manuel (j'ai testé, ça marche bien) :
abstract Class Papa{
    private static $_instance;
    
    abstract public function hello();

    public static function getInstance()
    {
        if (is_null(self::$_instance))
        {
            $class=get_class_static();
            self::$_instance = new $class();
        }
        return (self::$_instance);
    } 

    function get_class_static() {
        $bt = debug_backtrace();
   
        if (isset($bt[1]['object']))
            return get_class($bt[1]['object']);
        else
            return $bt[1]['class'];
    }
}

par titerm » 13 févr. 2008, 15:26

Je ne pense pas qu'il y ait de solution a ton pb avec les versions actuelle de php.
Il me semble que la 5.3 devrait faire ton bonheur grace au "late static binding".

getInstance() sur une classe fille

par Genova » 13 févr. 2008, 14:36

Bonjour,
je voudrais implémenter le design pattern singleton dans une classe, avec un basique getInstance() comme ceci :
	public static function getInstance()
	{
		if (is_null(self::$_instance))
		{
			self::$_instance = new self();
		}
		return (self::$_instance);
	}
Le problème qui se pose c'est que ma classe est une classe abstraite, et que le self() pointe vers cette classe au lieu de pointer vers les enfants. Je vais faire plus simple :
abstract class Papa
{
	private static $_instance;
	
	abstract public function hello();

	public static function getInstance()
	{
		if (is_null(self::$_instance))
		{
			self::$_instance = new self();
		}
		return (self::$_instance);
	}
}

class Fille extends Papa
{
	public function hello()
	{
		echo 'Hello';
	}
}

Fille::getInstance()->hello();
ben en gros ça marche pas, self() représente Papa().

il y a un moyen de gérer ce problème, autrement qu'en mettant le getInstance() en série dans chaque classe fille ?

Merci :)