2010-10-04 4 views
5

J'ai une classe comme ce qui suit:Quelle est la façon OO de refactoriser ces deux classes très similaires en PHP?

class DreamsImagesStore 
{ 
    public $table = 'dreams_images'; 

    public function insertNewDreamImage($dream_id, $pid) 
    { 
    try { 
     $values = array($dream_id, $pid); 
     $sth = $this->dbh->prepare("INSERT INTO {$this->table} 
           (dream_id, pid) 
           VALUES (?, ?)"); 
     if($sth->execute($values)) { 
     return true; 
     } 
    } catch(PDOException $e) { 
     $this->errorLogger($e); 
    } 
    } 
... 
} 

Je vais être la mise en œuvre d'une nouvelle classe appelée InterestsImagesStore dans laquelle les seules différences entre ces classes seront la valeur de $table, $dream_id sera $interest_id et dream_id dans SQL sera interest_id.

Je sais qu'il y a une meilleure façon de le faire, et je vais mettre en place des classes similaires dans le futur qui ont de si petites différences.

Quel est le meilleur moyen de refactoriser mon code pour éviter les doublons et augmenter la maintenabilité?

+3

+1 pour reconnaître quand demander de l'aide * avant * créer un désordre bâclé qui doit être refaçonné par quelqu'un d'autre – Zak

+0

@Zak Merci. J'essaie de coder comme la personne qui vient après moi est un maniaque homicide. Et quand je ne peux pas comprendre pour la plupart de ce que l'on dit des livres sur les patrons, je me suis dit qu'un exemple concret et significatif m'aiderait à en faire le tour. –

Répondre

11

Créer une classe de base ImagesStore:

class ImagesStore 
{ 
    // See comments about accessors below. 
    private $table; 
    private $id_column; 

    public function insertImage($id, $pid) { 
    try { 
     $values = array($id, $pid); 
     $table = $this->getTable(); 
     $id_column = $this->getIdColumn(); 
     $sth = $this->dbh->prepare("INSERT INTO {$table} ($id_column, pid) VALUES (?, ?)"); 
     if ($sth->execute($values)) { 
     return true; 
     } 
    } 
    catch (PDOException $e) { 
     $this->errorLogger($e); 
    } 
    } 

    protected function __construct($table, $id_column) { 
    $this->table = $table; 
    $this->id_column = $id_column; 
    } 

    // These accessors are only required if derived classes need access 
    // to $table and $id_column. Declaring the fields "private" and providing 
    // "protected" getters like this prevents the derived classes from 
    // modifying these values which might be a desirable property of these 
    // fields. 
    protected function getTable() {return $this->table;} 
    protected function getIdColumn() {return $this->id_column;} 

    // More implementation here... 
    // Initialize $dbh to something etc. 
    // Provide "errorLogger" method etc. 
} 

Et créer DreamsImagesStore et InterestsImagesStore spécialisations:

class DreamsImagesStore extends ImagesStore { 
    public function __construct() { 
    parent::__construct('dreams_images', 'dream_id'); 
    } 
} 

class InterestsImagesStore extends ImagesStore { 
    public function __construct() { 
    parent::__construct('interests_images', 'interest_id'); 
    } 
} 

La méthode originale insertNewDreamImage peut être renommé insertImage comme il est vraiment plus général que l'original nom suggère.

Notez que ImagesStore peut également être déclaré abstract si vous souhaitez empêcher son instanciation directe.

Une autre approche qui peut être adoptée est de ne pas déranger les classes provenant de ImagesStore du tout et juste instancier directement en faisant la méthode __constructpublic et de l'appeler comme suit:

$dreamsImagesStore = new ImagesStore("dreams_images", "dream_id"); 

Une autre approche pourrait également être implémenter une méthode d'usine statique dans ImagesStore.

+0

Merci pour ça. Dans 'insertImage()', ne voulez-vous pas dire '$ this-> table' plutôt que' $ table'? Si non, pourquoi? –

+3

+1, mais est-il vraiment nécessaire de sous-classer les valeurs codées en dur? Qu'en est-il de la création d'une fonction usine qui prend le type de magasin en tant que param et renvoie une instance appropriée du magasin d'images? – Zak

+2

@Josh Smith: Les deux approches fonctionnent. Cette implémentation illustre l'accès à ces champs via les méthodes getter, mais les champs peuvent également être accédés directement en tant que '$ this-> table' et' $ this-> id_column' à partir de méthodes de classe de base.À la place, les méthodes des classes dérivées devraient utiliser 'getTable' et' getIdColumn' puisque j'ai déclaré les champs 'private'. Pour le code dans les méthodes de classe de base est soit correct et il revient à goûter. –

2

utilisant la classe ImagesStore créée par Richard Cook, cela pourrait également se produire:

function FactoryLoadImageStore($imageStoreType) 
{ 
    switch($imageStoreType) 
    { 
     case "Interests": 
      return new ImageStore('interests_images', 'interest_id'); 
     case "Dreams": 
      return new ImageStore('dreams_images', 'dreams_id'); 
     default: 
      throw new Exception("ImageStore type $imageStoreType not found") 
; } 

} 

ou vous pourriez même obtenir colombophile et faire quelque chose comme

function FactoryLoadImageStore($imageStoreType) 
{ 
    $tableName = $imageStoreType . 's_images'; 
    $idColumnName = $imageStoreType . 's_id'; 
    $tableExists = false; 
    $sql= "Show tables like '$tableName' "; 
    foreach ($this->dbh->query($sql) as $row) 
    { 
     if ($row['tableName'] == $tableName) 
     { 
      $tableExists = true; 
      break; 
     } 
    } 
    if(!$tableExists) 
    { 
     throw new Exception ("No matching table exists for the requested image store $imageStoreType"); 
    } 

    return new ImageStore($tableName, $idColumnName); 
} 

appel comme suit

$dreamImageStore = ImageStore::FactoryLoadImageStore('dream'); 
+0

ne peut pas se souvenir si le nom de la table revient en tant que tableName dans la ligne .. doit être cochée ... – Zak

Questions connexes