Poster une réponse 
 
Note de cette discussion :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
[POO] Utilisation du singleton
Auteur Message
uriak Hors ligne
Membre

Messages : 376
Inscription : Aug 2007
Réputation : 10
Message : #1
[POO] Utilisation du singleton
Ce tutorial est destinée à montrer une application plus poussée de la POO, et de certains design patterns.

Prérequis : Je recommande avant lecture d'avoir parcouru les tutoriaux POO et Singleton pour comprendre la PPO et l'utilisation d'élément statiques pour une classe.

Objectif : Dans un jeu où les différents éléments sont gérés par des classes, on souhaite pouvoir accéder aux éléments propres au joueur facilement, tout en évitant des requêtes inutiles et de devoir faire des classes différentes pour ce qui appartient au joueur et les autres.

Contexte : Un joueur se trouve dans une zone située dans une région. Ces éléments ne sont pas susceptibles de changer en dehors d'une action du joueur.

Présentation de la classe CharacterManager

Code PHP :
class CharacterManager
{
    
//inutilisés pour le moment
    
protected $output;
    protected 
$log;

    
//variable principale où sont stokées les valeurs définissant un joueur.
    
protected $profile = array("status" => 0);
    protected 
$id;
    
    
//constructeur, peut être appelé avec un identifiant ou un profil
    
public function __construct($profile$complete true)
    {
      
//cas du profil, on recopie les array 
      
if ($complete)
      {
        
$this->profile $profile;
        
$this->profile['status'] = 1;
      }
      else 
//cas de l'ientifiant, une requête est alors réalisée.
      
{    
        
$query 'SELECT * FROM player WHERE id_player="'.$profile.'"';    
        
$result connexion::GetSingleton()->query($query);
        if(
$data mysql_fetch_array($result))
            
$this->profile = array("status" => 1,
                                    
"id" => $data['id_player'],
                                    
"name" => $data['name'],
                                    
"life" => $data['life'],
                                    
"stamina" => $data['stamina'],
                                    
"money" => $data['wealth'],
                                    
"x" => $data['x'],
                                    
"y" => $data['y']);
      }
    }
... 
Cette classe sert à gérer un joueur quelconque. Elle s'instancie de deux manières, par identifiant ou par profil. Le profil permet d'utiliser une requête réalisée auparavant.

Une des actions en particulier, note le commentaire sur la mise à jour de factory.
Code PHP :
    public function GoToZone($id)
    {
        
//Vérification, de la distance, on récupère les coordonnées d'arrivée de la zone.
        
$query 'SELECT x,y from zone WHERE id_zone="'.$id.'"';
        
$result connexion::GetSingleton()->query($query);
        if (
$data mysql_fetch_array($result))
        {    
            
$x $data["x"]; $y $data["y"];
            
//test de distance, la somme des valeurs absolue doit être égale à 1 : ie on se déplace vers une case adjacente.
            
if ((abs($x $this->profile["x"]) + abs($y $this->profile["y"] )!= 1))
            {
                return 
false;
            }

            
//mise à joueur du joueur en deux étapes : 1 mise à jour du profil BDD
            
$query 'UPDATE player SET zone="'.$id.'", x="'.$x.'", y="'.$y.'" WHERE id_player="'.$this->profile["id"].'"';
            
//si la mise à jour fonctionne, on met à joueur le PlayerManager lui-même. Comme on l'a par pointeur, cela modifie aussi le profil dans Factory et en session.
            
if ($result connexion::GetSingleton()->query($query))
            {
                
$this->profile["zone"] = $id;
                
$this->profile["x"] = $x;
                
$this->profile["y"] = $y;
            }
            return 
$id;
        }
        return 
false;
    } 

Présentation de la classe Factory. En réalité Factory n'est pas une réelle Factory au sens du pattern, c'est à dire un classe qui en instancie d'autres. Elle en instancie mais une seule. Son but est de centraliser l'accès à la session et à la bdd pour les éléments appartenant au joueur.

Voyons déjà les membres

Code PHP :
class Factory
{
    
//le singleton
    
private static $instance;
    
//identifiant servant à générer le joueur. Il n'est pas conservé d'une page à l'autre
    
private static $id = -1;
    
//indique si la clsse dispose d'un identifiant valide.
    
private $loaded false;
    
//stocke les ids des différents sous-éléments, permet d'y accéder sans passer par les classes
    
protected $identifiers = array();
    
//indique une erreur de chargement
    
protected $status = -1;
    
    
//classes principales auxquelles on souhaite accéder.
    
protected $player;
    protected 
$zone;
    protected 
$region;
... 

Maintenant le pattern Singleton. A noter que dans ce cas spécifique, l'instance unique se cherche d'abord en mémoire, puis en session puis en BDD.

Code PHP :
//constructeur privé
    
private function __construct()
    {
      
//pour la génération, on récupère l'identifiant en cours
      
$this->identifier["player"] = self::$id;
      
//génération des classes
      
$this->GeneratePlayer();
      
$this->GenerateZone();
      
$this->GenerateRegion();           
      if (
$this->status$this->loaded;      
    }
    
    
//accesseur singleton : privé!
    
private static function  GetSingleton()
    {
        
//récupération de l'instance
        
if (!isset(self::$instance))
        {
            if (!isset(
$_SESSION["factory"]))
            {
                
//génération de l'instance et mise en session
                
self::$instance = new Factory();
                
$_SESSION["factory"] = self::$instance;
            }
            else
            {
                
//récupération de l'instancemise en session. Mise à jour de l'identifiant 
                
self::$instance $_SESSION["factory"];
                
self::$id self::$instance->identifier["player"];
            }
        }
        return 
self::$instance;
    } 

Remarque : GetSingleton est privé !! en effet le but n'est pas d'acceder à la Factory elle-même, on va donc utiliser d'autres méthodes statiques.

Maintenant comment accède-t-on à nos classes ? Par l'intermédiaire d'accesseurs statiques... On accède ainsi à CharacterManager, ZoneManager et RegionManager (bâties sur le même modèle)

Code PHP :
    //acesseurs utilisés dans le code : membres statiques, renvoie directement le pointeur vers la classe demandée
    
public static function GetPlayer()
    {
        return 
Factory::GetSingleton()->player;
    }
    public static function 
GetZone()
    {
        return 
Factory::GetSingleton()->zone;
    }
    public static function 
GetRegion()
    {
        return 
Factory::GetSingleton()->region;
    }
    
//pour récupérer les id sans paser par les classes, vérifie l'absence de problème.
    
public static function GetID($type)
    {
        
$fac Factory::GetSingleton();
        if (
$fac->loaded)
        {
            return 
$fac->identifier[$type];
        }
        else return 
false;
    } 

A ce stade en toute page, si je fais Factory::GetPlayer() je reçois un Pointeur sur une instance de CharacterManager, cette instance passe de page en page par le biais de la session. Si je la modifie (confère GoToZone) elle est également modifiée dans la Factory.

Dernière question : comment instancie-t-on au départ un joueur ? La technique utilisée pour le moment utilise le membre statique self:11îd (ce n'est pas la seue solution). Comme il est perdu au changement de page, on le régénère dans GetSingleton()

Code PHP :
    //régénère la factory : sert principalement au changement de joueur.
    
public static function ReCharge()
    {
        
self::$instance = new Factory();
        if(
self::$id != -1self::$instance->loaded true;
        
$_SESSION["factory"] = self::$instance;     
        return 
self::$instance;
    }
    
//changement de joueur
    
public static function SelectPlayer($id)
    {
        
$fac Factory::GetSingleton();
        
//on test si le changement  est nécessaire, et on applique la commande
        
if ($id != self::$id or !$fac->loaded)
        {
            
self::$id $id;        
            
$fac Factory::ReCharge();
        }
    } 

Voilà comment s'effectue un changement de joueur. Le membre loaded indique si on a une factory valide.

Enfin un exemple de la génération d'une des classes.

Code PHP :
//génère le joueur depuis la BDD
    
private function GeneratePlayer()
    {
        
//requête
        
$query 'SELECT player.*, zone.id_zone, region.id_region, region.name FROM player LEFT JOIN
        zone ON player.x=zone.x AND player.y=zone.y
        LEFT JOIN region ON zone.region=region.id_region WHERE id_player="'
.$this->identifier["player"] .'"';
        
$result connexion::GetSingleton()->query($query);
    
        if(
$data mysql_fetch_array($result))
        {
        
$player = array( "id" => $data['id_player'],
                         
"name" => $data[2],
                         
"life" => $data['life'],
                         
"stamina" => $data['stamina'],
                         
"money" => $data['wealth'],
                         
"x" => $data['x'],
                         
"y" => $data['y'],
                         
"zone" => $data['zone']);
                         
        
        
//utilisé pour les requêtes suivantes  : les requêtes sont séparées car peu fréquentes et maintenues unitaires
        
$this->identifier["zone"]  = $data['id_zone'];
        
$this->identifier["region"]  = $data['id_region'];
        
        
//le CharacterManager peut être crée à l'aide d'un identifiant ou d'un tableau contenant les données. Ici la seconde option est utilisée
        
$this->player = new CharacterManager($player);
        
$this->status 1;
        }
        else 
$this->status 0;    
    } 

On note que le chargement du joueur génère les identifiants de zone et région. Pourquoi ne pas faire une requête plus poussée avec des JOIN ? Pour garder les requêtes assez unitaires, elles ne seront pas appellées souvent, et restent plus faciles à mettre à jour ainsi. Note : je pourrais aussi les supprimer et m'appuyer entièrement sur des requêtes dans CharacterManager.

Une dernière chose, la mise à jour de certains éléments ne peut être faite uniquement via les pointeurs des classes. Pour changer de zone on tuiliser la méthode suivante :

Code PHP :
    //sert à modifier le sous-élément zone. Change en cascade la région si nécessaire
    
public static function SelectZone($id)
    {
        
$fact Factory::GetSingleton();
        if (
$fact->identifier["zone"]  != $id)
        {
            
$fact->identifier["zone"] = $id;
            
$idR $fact->identifier["region"];
            
$fact->GenerateZone();
            if (
$fact->identifier["region"]  != $idR$fact->GenerateRegion();
        }

    } 

On effectue ainsi une mise à jour si nécessaire de la région.
Ce dernier point est une faiblesse du script actuel, il faut créer des méthodes dépendantes de la hierarchie des éléments, mais c'est indispensable à un stade ou à un autre.
Pourquoi les identifiant sont stockés à part des classes ? Parce qu'il est inutile de vouloir les modifier comme des variables internes : changer d'indentifiant, c'est recharger la classe 34

Voilà j'espère que ce tutorial, à défaut de servir réellement pour votre jeu, vous montre un peu plus l'utilisation de la POO et des membres statiques.

[Image: Userbar_finale_WM.jpg]
Sur jeuPHP.net
(Ce message a été modifié le : 09-09-2008 11:16 AM par Sephi-Chan.)
01-20-2008 12:15 PM
Trouver tous les messages de cet utilisateur Citer ce message dans une réponse
Poster une réponse 



ContactJeuWeb (JeuPHP) - Crée ton jeu par navigateurRetourner en hautRetourner au contenuVersion bas-débit (Archivé)Syndication RSS