IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

[POO] Héritages de variables statiques


Sujet :

Langage PHP

  1. #1
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2008
    Messages
    32
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2008
    Messages : 32
    Points : 80
    Points
    80
    Par défaut [POO] Héritages de variables statiques
    Bonjour,

    le problème que je vais vous exposer a déjà été posé sur le chat, mais je pense que tout le monde a dû s'endormir avant la fin. J'ai déjà une solution, donc ce post n'est pas critique, et j'ai déjà farfouillé pas mal de doc sur le sujet, sans succès.

    Voilà, j'ai repris le développement d'un jeu de stratégie "persistant" en PHP, et décidé de le passer en MVC (parce qu'il y en a marre des fonctions ultra-optimisées, en vitesse ET en effets de bord).

    La base du jeu, c'est l'unité. La plupart des unités n'ont pas le droit d'attaquer. Du coup j'ai une classe mère :

    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    abstract class Unit {
     public static $canAttack=false;
     
    public function canAttack(){
        return self::$canAttack;
    }

    Mais j'ai une classe d'unités qui peut attaquer, évidemment (c'est un jeu de guerre, tout de même).

    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    abstract class AttackingUnit extends Unit{
    public static $canAttack=true;

    Admettons qu'un fantassin soit une unité qui attaque :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    class FootSoldier extends AttackingUnit{

    J'ai une trentaine d'unités comme ça. Admettons que je charge dynamiquement l'unité :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $unit = "unNomDeClasseQuiVarie";
    $unit_object = new $unit(); // et ça marche !

    Je veux savoir si l'unité peut attaquer. Et bien la méthode qui consiste à faire
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    $unit->canAttack();

    Me renvoie, pour un fantassin : false.


    Je pourrais réimplanter un getter pour chaque paramètre dans chaque classe, l'ennui c'est que des paramètres comme ça, j'en ai environ 25... (et puis je voulais profiter de l'objet...)

    Connaissez vous une syntaxe qui me permet de passer au dela de ça ? Ou un changer de conception éventuellement ? Qui soit à la fois clair et concis, car j'ai environ 35 classes rien que pour décrire mes unités, qui sont environ 10% du modèle...

    Merci d'avance à ceux qui oseront se casser la tête sur le sujet ^_^

  2. #2
    Membre confirmé Avatar de goodpz
    Profil pro
    Inscrit en
    Février 2007
    Messages
    475
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 475
    Points : 514
    Points
    514
    Par défaut
    Le problème est dû au fait que php résout les appels statiques lors de la "compilation" du script et non lors de son l'exécution. Quand tu call $unit->canAttack(), c'est Unit::$canAttack qui va être renvoyé parce que la fonction fait référence à la class Unit via le mot clé self:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public function canAttack(){
      return self::$canAttack;
    }
    Avec php 5.3, tu pourrais remplacer "self" par "static", ce qui a pour effet de différer la résolution de lien à l'exécution (c'est le fameux late static binding) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public function canAttack(){
      return static::$canAttack;
    }
    Comme je suppose que tu n'as pas accès à php 5.3 pour l'instant, il vaut mieux que tu changes ton design. $canAttack pourrait être une variable membre normale (et protected).

  3. #3
    Expert éminent
    Avatar de GrandFather
    Inscrit en
    Mai 2004
    Messages
    4 587
    Détails du profil
    Informations personnelles :
    Âge : 54

    Informations forums :
    Inscription : Mai 2004
    Messages : 4 587
    Points : 7 103
    Points
    7 103
    Par défaut
    Bonjour,

    le problème est que le self de return self::$canAttack est lié statiquement (au sens POO) à la classe dans laquelle il est défini, et puisque qu'en l'occurrence c'est dans la classe Unit, c'est la valeur de $canAttack de cette classe qui est renvoyée, indépendamment de l'instance utilisée.

    Il n'y a pas de moyen simple de contourner ceci en conservant ton modèle de classe actuelle. Si ça t'intéresse, des solutions sont envisagées qui consisteraient à utiliser l'indicateur de portée static plutôt que self pour ce cas particulier.

    Personnellement, je considère que redéfinir dans des classes filles les méthodes (ou les membres) statiques de la classe mère est rechercher les ennuis, et n'est pas forcément utile. Tu peux facilement te passer de $canAttack :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    abstract class Unit {
        public function canAttack()
        {
          return false;
        }
    }
     
    abstract class AttackingUnit extends Unit {
        public function canAttack()
        {
          return true;
        }
    }
    Edit : grillé au poteau

  4. #4
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2008
    Messages
    32
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2008
    Messages : 32
    Points : 80
    Points
    80
    Par défaut
    Merci à tous les deux pour vos réponses. Je suis toujours autant impressionné par la vitesse et la qualité des réponses de ce forum...

    Je ne connaissais pas l'existence du mot clé static, qui en effet serait une bonne solution, sauf que le serveur de prod dont je dispose n'est en effet pas en php 5.3.


    Je refuse la solution de la variable non statique, car elle implique un surcroit de 25 variables par unité, qui multiplié par environ 300 unités dans les affichages les plus critiques (et chaque joueur ayant tendance à rafraichir toutes les 3 secondes quand il est en phase de jeu critique...) donne un résultat non abordable.
    J'aime l'absence de variable et la présence d'une méthode, et je l'avais refusé parce qu'elle coupe la clarté (quand on chargeait une classe, on avait immédiatement la liste des paramètres devant les yeux). C'est clairement la solution la plus logique, cependant.

    Je me permets de partager ma vision des choses, qui tire partie du fait que le système n'appelle pas à répétition, et que je peux donc m'offrir un surcroit de temps de calcul sur cette partie. Le tweak vient directement du net, et j'y ai ajouté une liste de variables statiques pour résoudre mes problèmes d'autocomplétion (merci Eclipse PDT) :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    /**
    	 * Returns the parameter corresponding to the property asked.
    	 * 
    	 * The list of property can be found in constants.php.
    	 * @param $propertyName
    	 * @return mixed
    	 */
    	public function getProperty($propertyName){
    		$class = get_class($this);
    		eval("\$param=$class::\$$propertyName;");
    		return $param;
    	}

    et du coup, j'ai une liste de defines :
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    // ----------------
    // All those constants defines variables that can be accessed 
    // through de unit.getProperty method.
    // ----------------
    define('UNIT_CAN_ATTACK', "actionAttack");
    define('UNIT_CAN_BOMBARD', "actionBombard");
    // et plein d'autres constantes pour mes autres propriétés statiques

    Je reste à l'affût de toute autre méthode sioux. Suggestion, ou commentaire sur mon idée.

  5. #5
    Membre confirmé Avatar de goodpz
    Profil pro
    Inscrit en
    Février 2007
    Messages
    475
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 475
    Points : 514
    Points
    514
    Par défaut
    Ca fonctionne, mais en ce qui me concerne, je trouve ta solution hideuse
    eval() est réputée pour être très lente. define() aussi (pour php < 5.3).

    Pourquoi ne pas utiliser de simples variables membre ? Les accesseurs "getProperty()" seraient définis une seule fois par branche dans la hiérarchie.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    abstract class Unit {
      // variables communes
      protected $canAttack = false;
      protected $canBlah   = true;
     
      // accesseurs communs
      public function canAttack() {
        return $this->canAttack;
      }
      // ..
    }
     
    abstract class AttackingUnit extends Unit {
      // redefinition
      protected $canAttack = true;
     
      // pas de redefinition des accesseurs communs
    }
     
    class FootSoldier extends AttackingUnit {
    }
     
    $units = 'FootSoldier';
    $unit = new $units();
    $unit->canAttack();
    On pourrait aussi surcharger __get() ou __call() pour unifier la définition des accesseurs, mais ça ne changerait pas grand chose.
    OK, avec des variables membres non statiques, il y aura une "occurrence" pour chaque variable par objet instancié. Tant qu'il n'y a pas de LSB (late static binding (voir mon premier post)), ça me parait plus judicieux, d'autant plus qu'il se peut que finalement tu ais besoins de redefinir des variables par instance et non pas par classe. Par exemple, se peut-il qu'un FootSoldier ne soit plus en mesure d'attaquer parcequ'il est blessé (ou une connerie du genre) ?

  6. #6
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2008
    Messages
    32
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2008
    Messages : 32
    Points : 80
    Points
    80
    Par défaut
    Citation Envoyé par goodpz Voir le message
    Ca fonctionne, mais en ce qui me concerne, je trouve ta solution hideuse
    Ahhh, j'aime ce genre d'entames

    eval() est réputée pour être très lente. define() aussi (pour php < 5.3).
    En effet. La clarté externe contre la vitesse... j'avoue que c'est un choix qui se défend.

    Pourquoi ne pas utiliser de simples variables membre ? Les accesseurs "getProperty()" seraient définis une seule fois par branche dans la hiérarchie.

    OK, avec des variables membres non statiques, il y aura une "occurrence" pour chaque variable par objet instancié. Tant qu'il n'y a pas de LSB (late static binding (voir mon premier post)), ça me parait plus judicieux,
    * 25 par unité. * 300 unités. Si tu prends en compte en plus les temps d'affectation à l'initialisation... je ne suis pas sûr que tu sois plus rapide que mon eval, qui ne me sert qu'une fois ou deux par affichage. (Je ne jette pas la pierre cela dit, tu n'as pas toutes les données du problème).
    J'ai un autre problème qui est que ma classe Unit fait déjà 800 lignes, mais je peux remettre une couche d'abstraction au besoin, ça c'est pas grave...

    d'autant plus qu'il se peut que finalement tu ais besoins de redefinir des variables par instance et non pas par classe. Par exemple, se peut-il qu'un FootSoldier ne soit plus en mesure d'attaquer parcequ'il est blessé (ou une connerie du genre) ?
    J'aime le concept, et je le proposerai à mes admins. Mais non, ce n'est pas le cas. On a choisi une autre façon de rendre l'attaque impossible quand l'unité est blessée (sa précision dépend de sa vie, pour être précis).


    Les deux solutions sont aussi claires l'une que l'autre en ce qui concerne le rangement des données. Maintenant je fais appel aux grands manitous de l'optimisation pour me dire combien coûtent 25 variables de plus par unité contre 25 defines une fois pour toutes et une eval mettons, maximum 2 fois par unité ^_^.
    J'en parlerai à mon co-développeur, qui planchait sur une autre solution...

    Merci beaucoup pour le temps passé sur le problème en tous les cas

  7. #7
    Expert éminent
    Avatar de GrandFather
    Inscrit en
    Mai 2004
    Messages
    4 587
    Détails du profil
    Informations personnelles :
    Âge : 54

    Informations forums :
    Inscription : Mai 2004
    Messages : 4 587
    Points : 7 103
    Points
    7 103
    Par défaut
    Citation Envoyé par LogistiX Voir le message
    J'aime l'absence de variable et la présence d'une méthode, et je l'avais refusé parce qu'elle coupe la clarté (quand on chargeait une classe, on avait immédiatement la liste des paramètres devant les yeux). C'est clairement la solution la plus logique, cependant.
    On ne peut pas dire qu'une solution à base de eval() et de get_class() soit particulièrement exemplaire du point de vue clarté...

    Je commence à cerner un peu mieux les données du problème, et je soupçonne un vice de conception à la base ; pose-toi cette question : quel est l'intérêt de créer une hiérarchie de classes, et même d'utiliser les classes, si tout ce qui fait l'identité et la logique interne d'une unité provient de données statiques ?

    N'y aurait-il pas plutôt intérêt à utiliser un simple tableau associatif pour conserver les données fondamentales des unités, et à déplacer les 800 (!) lignes de code de la classe Unit dans une ou plusieurs autres classes qui constitueraient alors le moteur de jeu ?

  8. #8
    Membre régulier

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2008
    Messages
    32
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2008
    Messages : 32
    Points : 80
    Points
    80
    Par défaut
    Citation Envoyé par GrandFather Voir le message
    On ne peut pas dire qu'une solution à base de eval() et de get_class() soit particulièrement exemplaire du point de vue clarté...
    A l'extérieur de la fonction, c'est limpide. A l'intérieur, effectivement c'est un autre problème.

    Je commence à cerner un peu mieux les données du problème, et je soupçonne un vice de conception à la base ; pose-toi cette question : quel est l'intérêt de créer une hiérarchie de classes, et même d'utiliser les classes, si tout ce qui fait l'identité et la logique interne d'une unité provient de données statiques ?
    Oui, sauf qu'il te manque encore des données. En l'occurence, chaque unité a des points de vie, une vitesse, une vision, une position, un possesseur (etc...) bien à elle, chargées depuis la base de données. Et les 800 lignes qui trainent (dont 33% de commentaires), elles concernent ces accès / modifications là. Et ça, j'ai intérêt à le gérer comme je le fais, j'en suis convaincu. Peut-être éventuellement en remettant une surcouche abstraite pour casser en deux fois 400 lignes, mais dans les faits...

    N'y aurait-il pas plutôt intérêt à utiliser un simple tableau associatif pour conserver les données fondamentales des unités, et à déplacer les 800 (!) lignes de code de la classe Unit dans une ou plusieurs autres classes qui constitueraient alors le moteur de jeu ?
    Là par contre tu as mis le doigt où ça fait mal. Très mal même, car tu viens de m'énoncer l'ancien fonctionnement, que j'ai remis en question...
    Ce qui me gêne dans cet ancien système c'est que je n'ai pas de moyen standardisé d'accéder aux données (qui est un array associatif de profondeur 3 : type de l'unité, modèle de l'unité, caractéristique), et que j'ai une énorme redondance. J'ai mis longtemps avant de me décider à changer...

    En Java, j'aurais fait une hashmap, oui. Mais une hashmap où les capacités seraient à choisir parmi une énum (d'où mes defines). Et je me demande après réflexion, si un tableau à double entrée avec mes 25 defines, ne serait pas une solution pour résoudre le problème...
    Je vais explorer cette piste, il y a peut être beaucoup à y gagner en effet. Merci pour cette remarque ^_^

Discussions similaires

  1. Héritage et variable statique
    Par darkrojo dans le forum Débuter
    Réponses: 9
    Dernier message: 10/08/2011, 13h52
  2. [POO] Tableau en variable statique
    Par daajack dans le forum Langage
    Réponses: 4
    Dernier message: 11/03/2008, 14h50
  3. [POO] Héritage et variable static
    Par Al3x dans le forum Langage
    Réponses: 3
    Dernier message: 23/12/2007, 17h27
  4. [POO] Héritage : Surcharge d'un membre statique parent
    Par Nullos Oracle dans le forum Langage
    Réponses: 7
    Dernier message: 11/09/2007, 18h39
  5. Réponses: 5
    Dernier message: 23/11/2006, 13h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo