Petite question de design pattern

Bonsoir :slight_smile:

Voici le contexte de mon problème :

J’ai une classe qui intègre un constructeur qui prend des paramètres.
Lors de la création de l’instance, les données de ces paramètres sont vérifiées, et si elles sont incorrectes une exception est remontée.

Je cherche à développer une fonction qui va créer des tableaux d’instances de cette classe, en grand nombre.
Dans cette fonction, je suis certain que les paramètres que j’utilise pour créer les instances sont valides.
La vérification des paramètres par le constructeur de la classe est donc inutile, et elle prend beaucoup de temps pour rien.

Le problème que je cherche à résoudre :

Dans tous les cas, je ne veux pas que ma classe comporte un constructeur ou des accesseurs qui ne vérifient pas la validité des données, car je ne veux pas que cette vérification puisse être outrepassée.

Je souhaite cependant que la fonction qui me crée des tableaux d’instances déroge à cette règle, et puisse passer outre cette restriction.
Ainsi, elle pourrait me créer des tableaux d’instances en utilisant les paramètres déjà validés qu’elle a à sa disposition.

Quelqu’un connaît-il une méthode ou un design pattern pour résoudre ce genre de problème ? Je pense qu’un design pattern doit exister, mais je n’ai pas réussi à trouver mon bonheur

[ul]
[li]quelque soit la solution, la fonction qui crée des tableaux d’instances peut être placée n’importe où, ça n’a aucune importance pour moi[/li] [li]les données membres de la classe, générées par le constructeur à l’aide des paramètres reçus, sont protected[/li] [li]l’application est en PHP, mais ça n’a pas une grande importance, je recherche une solution générique (c’est pour ça que j’ai pas posté dans web dev), à moins qu’il y ait une solution particulière à PHP[/li][/ul]

Je vous remercie par avance !
Bonne soirée.

Factory ? Je dis ca au pif hein, mais ca a l’air de coller.
Sinon, a priori, les exceptions dans un ctor, c’est le mal.
Et sinon, je trouve la maniere de raisonner etrange. Ya pas de pattern pour tout, et en rechercher un a caler a tout bout de champ sans raison peut etre nocif. Tu as ta classe, tu met un ctor privé, un generateur static, un ctor publique et zou, on en parle plus. (Mais je sais pas si c’est possible avec php).
(Du coup, la question est incoherente, tu veux outrepasser la verification des données sans pouvoir jamais l’outrepasser. Rly ?)

Factory ne correspond pas à mon problème.
En fait si je parle de design pattern c’est qu’il me semble avoir déjà vu un pattern pour ce genre de problème. Mais je ne tiens pas particulièrement à utiliser un design pattern.

Je cherche en effet à outrepasser la protection, mais dans un contexte safe. Sans que ce soit possible depuis l’extérieur de la classe. Je ne veux pas permettre l’instanciation sans vérification des paramètres, sauf lorsque l’on veut récupérer un groupe de plusieurs instances, le système qui va générer le groupe devrait idéalement pouvoir outrepasser la restriction.

Ce que je veux faire n’est peut être pas possible :wink:

Bah, de ce que je connais, (C#), je ferais comme ca perso.
Un constructeur privé qui ne check rien, qui donc peut etre uniquement appellé en interne depuis ladite classe, via par exemple une methode statique qui te renvoie un tableau tout beau.
Ce qui n’empeche pas a coté un constructeur publique, propre, qui lui va verifier les données que tu lui passes. Ca, c’est en C# donc, peut etre pas possible en PHP.
(A priori, c’est possible : http://www.php.net/manual/en/language.oop5.visibility.php#100379 , mais j’ai pas cherché plus loin hein.)

Ou meme internal…

Pareil qu’Anaelle. C’est simple et donc de bon goût.

un builder pattern?

[code]public class Tartempion
{

private Truc champObligatoire;
private Bidule champOptionel;

public static class Builder
{
    private Truc champObligatoire = null;
    private Bidule champOptionel = VALEUR_PAR_DEFAUT;

    public Builder(Truc chpOligatoire)
    {
        champObligatoire = chpObligatoire;
    }

    public Builder champOptionel(Bidule param)
    {
        champOptionel = param;
        return this;
    }
}

public Tartempion(Builder bld)
{
    champObligatoire = bld.champObligatoire;
    champOptionel = bld.champOptionel;
}

}[/code]

Merci pour vos réponses, je regarde ça ce soir et je vous dis ce qu’il en est :slight_smile:

En PHP? :wink:

Merci à tous, j’ai pu trouver comment résoudre mon problème :slight_smile:
Je me suis inspiré de ce qu’a dit AnA-l en adaptant aux spécificités de PHP, à savoir l’absence de surcharge.

Voici, grosso modo, la solution que j’ai mise en place.
C’est pas très joli dans cet exemple, mais il faut avoir à l’esprit que la $var que j’ai symbolisée ici représente en réalité un très grand nombre de variables qui ne sont jamais toutes passées lors de l’instanciation, sauf pour le cas particulier de la création d’un tableau d’instances.

On a donc :
[ul]
[li]notre constructeur public, qui procède à une initialisation « par défaut »[/li][li]une méthode static getInstance qui permet de passer des paramètres qui sont validés avant l’instanciation[/li][li]une méthode multipleInstancesGenerator qui permet de récupérer un tableau d’instances pré-initialisées, sans aucune vérification de données[/li][/ul]

Dans mon projet réel, l’équivalent de multipleInstancesGenerator va en fait chercher les données en BDD, voilà pourquoi une vérification est inutile : les données déjà présentes en BDD sont considérées comme valides, car elles sont déjà passées à travers cette classe.

[code]<?php

class Test
{
private $var;

    public function __construct()
    {
            echo 'Constructeur', "<br />";
            $this->var = 0;
    }

    public static function getInstance($param)
    {
            if($param < 0)
                    throw new Exception('$param est < 0, valeur='.$param);

            $c = __CLASS__;
            $instance = new $c;
            $instance->var = $param;
            return $instance;
    }

    public static function multipleInstancesGenerator()
    {
            $c = __CLASS__;
            for($i=0; $i<10; $i++)
            {
                    $instances[$i] = new $c;
                    $instances[$i]->var = $i*10;
            }
            return $instances;
    }

    public function getValue()
    {
            return $this->var;
    }

}

try
{
$instance1 = Test::getInstance(3);
$instance2 = Test::getInstance(18);
$instance2 = Test::getInstance(-5); // param incorrect => echec
}
catch (Exception $e)
{
echo 'Exception : ', $e->getMessage(), « 
 »;
}

$instance_tab = Test::multipleInstancesGenerator();

echo $instance1->getValue(), « 
 »;
echo $instance2->getValue(), « 
 »;
for($i=0; $i<10; $i++)
{
echo $instance_tab[$i]->getValue(), « 
 »;
}

?>[/code]

Qu’en pensez-vous? Des remarques?
Merci de votre aide en tout cas.

Bah, c’est l’idée, mais a priori, tu peux mettre le constructeur en private du coup (et d’apres la doc php.net, tu peux aussi surcharger le constructeur). Parce que la, tu peux appeller ton constructeur de l’exterieur de la classe, sans qu’aucune verification ne soit faite (et ca va a l’encontre de ce que tu voulait).

En effet, je peux appeler le constructeur d’en dehors, mais il ne prend pas de paramètre et se contente d’initialiser « par défaut ». Si on veut passer des paramètres, on doit passer par la getInstance, et ils seront vérifiés.

Je n’ai rien trouvé dans la doc de PHP pour surcharger des fonctions, à part ça :
A part peut être avec ça, mais j’aime pas :wink: PHP: Surcharge magique - Manual
mais ce n’est pas vraiment ce que je recherchais.

Merci :slight_smile:

Ca : http://fr.php.net/manual/fr/language.oop5.decon.php#99903

Et passe ce ****** de constructeur private. Ca fout en l’air tout ce que tu demandais dans ton post originel. Et tu fait un constructeur publique qui remplace cette horreur de getInstance…

En effet, c’est une méthode intéressante pour les constructeurs multiples, thx :slight_smile:

Voici un exemple qui correspond un peu plus à la réalité de mon problème.

Dans mon projet, j’ai 3 cas :
[ul]
[li]un constructeur qui permet de créer une instance vierge, les données sont ensuite insérées par des modifieurs qui s’occupent de vérifier leur validité[/li][li]fonction Load() : un constructeur qui prend en paramètre l’ID d’une entrée BDD et qui tente de la charger. En cas d’échec, il remonte une exception et n’instancie pas. Comme tu l’as soulevé plus haut, il n’est pas propre de remonter une exception dans un constructeur, d’où l’intérêt de cette fonction Load()[/li][li]fonction LoadMultiple() : crée un tableau d’instances à partir de données BDD, cette fonction se doit de passer outre les vérifications des modifieurs puisque les données provenant de la BDD sont valides[/li][li]la fonction commit() : réalise un INSERT ou un UPDATE sur la BDD en utilisant les données de la classe[/li][/ul]

De cette façon :
[ul]
[li]il est impossible d’instancier la classe en y insérant des données erronées, le seul accès extérieur sont les modifieurs[/li][li]il est possible de récupérer un tableau d’instances, dont les données sont ne sont pas vérifiées car elles sont considérées comme valides : elles proviennent directement de la BDD[/li][/ul]

Si je remplace Load par un constructeur, je ne pourrais plus remonter d’exception en cas d’ID invalide.

Cet exemple est peut être un peu plus parlant que le précédent, et il correspond bien à mon problème. Vos avis sont les bienvenus je suis ouvert aux suggestions :slight_smile:

[code]<?php
/**

  • Exception remontée en cas d’ID de donnée invalide
    */
    class ExceptionInvalidId extends Exception { }

/**

  • Classe permettant de gérer des données de la BDD
    /
    class Exemple
    {
    /
    données privées, qui DOIVENT toujours être valides */
    private $datas;

    /**

    • Création d’une instance vierge
      /
      public function __construct()
      {
      /
      initialisation des données avec des valeurs par défaut */
      }

    /**

    • Création d’une instance depuis une entrée de la BDD

    • @param int $id l’ID de l’entrée BDD

    • @return object
      */
      public static function Load($id)
      {
      if($id < 0)
      throw new ExceptionInvalidId();

      /* tentative de chargement de l’entrée ID depuis la BDD, et exception en cas d’échec */

      /* données chargées, on peut instancier */
      $c = CLASS;
      $instance = new $c;
      $instance->data = $data;
      return $instance;
      }

    /**

    • Chargement de plusieurs entrées depuis la base de données

    • @return array[int]object
      /
      public static function LoadMultiple()
      {
      /
      chargement des entrées de la BDD */

      /* création et retour du tableau */
      $c = CLASS;
      foreach($datas as $data)
      {
      $instance = new $c;
      $instance->data = $data;
      $instances = $instance;
      }

      return $instances;
      }

    /**

    • Exemple de modifieur
    • @return 0 en cas de succès, code d’erreur sinon
      /
      public function set_value($valeur)
      {
      /
      effectue des vérifications sur la valeur avant modifier les données de la classe */
      }

    /**

    • Exemple d’accesseur
      /
      public function get_value()
      {
      /
      récupérer une valeur à partir des données de la classe */
      }

    /**

    • Propager vers la base de données
    • @return 0 en cas de succès, code d’erreur en cas d’échec
      /
      public function commit()
      {
      /
      insertion ou mise à jour d’une donnée */
      }
      }

?>[/code]

Je trouve que c’est parlant comme implémentation, et dév-connerie-proof.
On veut créer une nouvelle (new) donnée à insérer en BDD ?

<?php $exemple = new Exemple(); ?>
On veut charger une donnée existante ?

<?php $exemple = Exemple::Load($id); ?>
On veut récupérer plusieurs instances ?

<?php $exemple = Exemple::LoadMultiple(); ?>

Et inverse le problème?

Faire une classe private maclasseQuiVerifieRien { } … et public maclasseSafe extends maclasseQuiVerifieRien { } et faire les vérifs dans le constructeur de celle-ci?