Pagina de start a forumului Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc
Comunitatea PHP Romania
 

SFactory
Vezi mesajul original

 
       Pagina de start a forumului Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc -> PHP Avansat
Subiectul anterior :: Subiectul următor  
Autor Mesaj
coditza



Data înscrierii: 23/Ian/2004
Mesaje: 298
Locație: cluj-napoca

Trimis: Mar Sep 27, 2005 7:38 am    Titlul subiectului: SFactory  

Smart Factory

Ce este un "Factory"?
Factory este un sablod de proiectare folosit in situatiile in care lucram cu mai multe obiecte diferite, dar care se comporta identic.

De exemplu:
- Dorim sa cream o galerie pentru imagini, cu ajutorul careia vom prezenta utilizatorilor o lista cu "thumbnailuri". Thumbnailurile vor fi create dinamic.
Vom folosi o fabrica de imagini, care in functie de un parametru, va crea un alt obiect care va stii sa-si creeze thumbnailul singur. Avem obiecte diferite, pentru imagini gif, jpeg, png etc care se comporta identic, functia makeThumb(); care va crea thumbnailul.
Obiectele create poarta numele de "Produse".

O implementare "clasica" a acestui sablon presupune un switch, dupa cum urmeaza:
an style="color: #000000"><?php class Factory {      function &create($type) {          switch($type) {              case 'gif':                  return new GifThumbnailer();                  break;              case 'jpeg':                  return new JpegThumbnailer();                  break;              //...              default:                  return false;                  break;          }      }  }    $f = new Factory();  $gif = $f->create('gif');  $gif->makeThumb(); 

Observam un mare incovenient: pentru a adauga un nou tip de Produs, pe langa implementarea clasei propriuzise, avem de modificat si clasa Factory, iar daca avem o colectie mai mare de tipuri de Produse, acest lucru poate pune probleme. Din experienta, pot spune ca mentinand numarul de fisiere modificate, in cazul unei modificari in proiect sau repararii unui bug, la minim si sansele de a introduce noi buguri este minima.

Pentru a evita acest lucru, vom folosi metoda lui Andrei Alexandrescu[1] pentru implementarea unei clase.

Inainte de a trece la implementarea propriuzisa, vom face cateva conventii:
1) Fiecare clasa va avea fisierul sau propriu.
2) Fisierele se vor numi: NumeClasa.class.php

Obiectul Factory va fi implementat ca un singleton, alt sablon de proiectare care ne asigura existenta unei singure instante al unui obiect. De asemenea, fabrica va avea 2 functii, register() si unregister() folosite pentru a inregistra un nou tip de Produs (si pentru al de-inregistra).

Clasa Factory va arata in felul urmator:

<?php  //clasa factory singleton  class Factory {        static $exists = false;      private $types;        //constructor :D - not used externaly      private function __construct() {          //init the types array          //because this is a singleton, we won't destroy          //the types array each time we get the instance          $this->types = array();      }        //get's the current instance of the factory      static function instance() {          if (self::$exists === false) {              self::$exists = new Factory();          }          return self::$exists;      }        //register a new type for the factory      function register($type,$creatorName) {          if (!is_array($this->types)) {              $this->types = array();          }          if (!in_array($type,array_keys($this->types))) {              $this->types[$type] = $creatorName;              return true;          }          return false;      }        //unregister a new type for the factory      function unregister($type) {          if (in_array($type,$this->types)) {              unset($this->types[$type]);          }          return false;      }          function &create($type) {          if (in_array($type,array_keys($this->types))) {              $class = $this->types[$type];              $pc = new $class();              return $pc;          }          //probably better to trow an exception or smth          return null;      }  }  ?>

Sa vedem cum arata o clasa Produs (renuntam la exemplul cu thumbnailurile, ca imi este extraordinar de lene sa rescriu clasele din exemplu :D):

<?php  class Circle {      function Circle() {          //just to show we can use functions too      }      function draw() {          echo __CLASS__.'<br />';      }  }    $f = Factory::instance();  $f->register('Circle','Circle');  ?>

Cele 2 linii
an style="color: #000000"><?php $f = Factory::instance();  $f->register('Circle','Circle'); 
reprezinta cheia acestei implementari:
Fiecare clasa se inregistreaza, mai mult sau mai putin, singura in tabloul de tipuri de Produse al clasei Factory.

Totusi, aceasta metoda presupune ca atat fisierul in care am definite clasa Factory, cat si toate fisierele cu Produsele pe care le vom folosi trebuiesc incluse in scripturile noastre. Ca de exemplu:
an style="color: #000000"><?php require_once('Factory.class.php');  require_once('Circle.class.php');  ... 
Observam aceeasi problema ca si in cazul implementarii standard a acestui sablon: o bucata mare de cod, greu de mentinut (si modificat).
PHP5 me pune insa la dispozitie un mecanism pentru a preveni acest lucru: __autoload();

In principiu, __autoload($nume_clasa) este o functie standard a PHP-ului, care poate fi suprascrisa si care va fi apelata in momentul in care parserul va intalni o referinta la o clasa pe care "nu o cunoaste".
Mutam toate fisierele in care avem definite obiecte Produs, intr-un subdirector. Ne asiguram ca in acel director nu avem si alte fisiere.

an style="color: #000000"><?php function __autoload($class_name) {      require_once($class_name.'.class.php');      if ($class_name=='Factory') {          $dir = opendir('Factory/');          //include all class files from the subdir          while(false !== ($file=readdir($dir))) {              if(strpos($file,'.class.php')!==false) {                  //include it                  require_once('Factory/'.$file);              }          }          closedir($dir);      }  } 

Fisierul de test va arata astfel:
<?php  1 function __autoload($class_name) {  2    require_once($class_name.'.class.php');  3    if ($class_name=='Factory') {  4        $dir = opendir('Factory/');  5        //include all class files from the subdir  6        while(false !== ($file=readdir($dir))) {  7            if(strpos($file,'.class.php')!==false) {  8                //include it  9                require_once('Factory/'.$file);  10            }  11        }  12        closedir($dir);  13    }  14 }    15 $f = Factory::instance();  16 $obj = $f->create('Square');  17 $obj->draw();  ?>

Numerele de la inceputul liniilor sunt numai pentru referinta.
Ce se intampla efectiv in script?

In linia 15 incercam sa atribuim lui $f o instanta a clasei Factory. Cum aceasta clasa nu este definita in scriptul nostru, PHP va apela functia __autoload() cu parametrul $class_name = 'Factory'. In linia 2, includem fisierul respectiv (bineinteles, acolo sunt necesare si ceva verificari).
In linia 3, verificam daca avem de inclus fisierul Factory.class.php, iar in caz afirmativ vom include si toate fisierele cu Produse din subdirectorul "Factory".

Avantajul acestei implementari este constituit de usurinta cu care putem adauga un nou tip de Produs. Avem de scris o singura clasa si ne putem concentra atentia asupra problemei pe care respectiva clasa trebuie sa o rezolve.

Si ca un ultim sfat: ideile bune sunt independente de limbaj.

[1] Programare Moderna In C++, Andrei Alexandrescu, http://www.moderncppdesign.com, editat de Pearson Education si reeditat in romana de mult iubita noastra Teora.
Sus  
carco



Data înscrierii: 27/Mai/2004
Mesaje: 2796
Locație: Bucuresti

Trimis: Mar Sep 27, 2005 8:37 am    Titlul subiectului:  

He he...nice! (dar cred ca-i cam mult pentru nivelul acestui forum :) )
Intrebare, daca tot suntem in spiritul OOP, iar clasa ta vrea sa fie _si_ singleton, nu era mai adecvat sa implementezi un singleton si apoi sfactory sa o derivezi din ea?

[offtopic]
Tot in acelasi spirit (sper ca nu te superi ca-ti stric threadul) cateva cuvinte despre MVC in PHP gasiti la adresa http://www.linux360.ro/portal/content/view/113/40/
[/offtopic]
Sus  
Radical



Data înscrierii: 16/Feb/2004
Mesaje: 327
Locație: Bucuresti

Trimis: Mar Sep 27, 2005 11:37 am    Titlul subiectului: Re: SFactory  

Subscriu lui em@il : cred ca-i cam mult pentru nivelul acestui forum...

Apropo de __autoload
coditza a scris: Fisierul de test va arata astfel:
<?php  1 function __autoload($class_name) {  2    require_once($class_name.'.class.php');  3    if ($class_name=='Factory') {  4        $dir = opendir('Factory/');  5        //include all class files from the subdir  6        while(false !== ($file=readdir($dir))) {  7            if(strpos($file,'.class.php')!==false) {  8                //include it  9                require_once('Factory/'.$file);  10            }  11        }  12        closedir($dir);  13    }  14 }  ?>

O problema iminenta legata de __autoload creat in acest mod cu opendir... este ordinea in care sistemul raspunde cu lista de fisiere... si mai precis ordinea lor...

Sa presupunem ca avem fisierul alpha.class.php in care este definita clasa alpha care extinde clasa betha definita si ea in fisierul betha.class.php...

opendir-ul va returna alpha.class.php, betha.class.php iar __autoload va incerca sa incarce clasa alpha dar va esua... pentru ca betha nu a fost definita inca... :D

Recent m-am lovit de asta...

Rezolvarea e simpla... dar poate complica lucrurile... in betha.class.php se pune require_once("alpha.class.php"); ... dar cum am spus ... poate complica lucrurile pentru aplicatii mari...
Sus  
coditza



Data înscrierii: 23/Ian/2004
Mesaje: 298
Locație: cluj-napoca

Trimis: Mar Sep 27, 2005 12:46 pm    Titlul subiectului:  

La problema cu __autoload(). Pai in directorul acela sunt numai clase Produs, la care sincer nu ma intereseaza ordinea in care sunt incarcare. Factory.class.php este incarcat din directorul care contine subdirectorul cu produsele.

Stai ca acuma am vazut ce zici de fapt :D
Pai nimic nu te impiedica sa modifici tu lista de fisiere incluse. Sau sa faci o substructura de directoare in directorul ala si subdirectoarele sa le parcurgi diferential. Sau sa "citesti" directorul intr-un array, sa-l sortezi dupa cum vrei si sa incarci fisierele separat. Asta era numai un mod de implementare, solutii se gasesc si pentru cele mai tricky situatii.

@em@il: pai nu neaparat. Singletonul este relativ usor de implementat si cu putin cod. Nu are rost sa implementezi o clasa care sa fie numai si numai singleton. Prefer sa-mi pastrez locul pentru "mostenire" :P pentru chestii mai complexe decat un singleton. Treaba cu "register" este de fapt frumusetzea acestei implementari, pana la urma Factoryul il poti face global si il initializezi inainte de prima linie de cod efectiva.
Sus  
johnny



Data înscrierii: 31/Iul/2004
Mesaje: 904
Locație: Bucuresti

Trimis: Mar Sep 27, 2005 1:07 pm    Titlul subiectului:  

Puteti sa studiati putin implementarea MVC din cake.
Mi se pare bine facuta, dar ei incearca o abordare cam abstracta pentru gusturile mele, si impune anumite reguli pentru designul bazei de date, si implementarea propriu zisa. Dar cam asta face orice RDF (Rapid Development Framework)
Sus  
punctweb



Data înscrierii: 24/Mar/2004
Mesaje: 507

Trimis: Mar Sep 27, 2005 2:19 pm    Titlul subiectului: Re: SFactory  

Radical a scris: Subscriu lui em@il : cred ca-i cam mult pentru nivelul acestui forum... (...)


8O
Sus  
coditza



Data înscrierii: 23/Ian/2004
Mesaje: 298
Locație: cluj-napoca

Trimis: Mar Sep 27, 2005 3:53 pm    Titlul subiectului: Re: SFactory  

Radical a scris:

O problema iminenta legata de __autoload creat in acest mod cu opendir... este ordinea in care sistemul raspunde cu lista de fisiere... si mai precis ordinea lor...

Sa presupunem ca avem fisierul alpha.class.php in care este definita clasa alpha care extinde clasa betha definita si ea in fisierul betha.class.php...

opendir-ul va returna alpha.class.php, betha.class.php iar __autoload va incerca sa incarce clasa alpha dar va esua... pentru ca betha nu a fost definita inca... :D

Recent m-am lovit de asta...


Quick fix:

in loc de 'require_once($class_name.'.class.php');'
an style="color: #000000"><?php if (file_exists($class_name.'.class.php')) {     require_once($class_name.'.class.php'); } else if (file_exists('Factory/'.$class_name.'.class.php')) {     require_once('Factory/'.$class_name.'.class.php'); } 

Cand php va include fisierul in care este definita clasa "alfa", care o extinde pe "betha", va da de "betha" care nu e definita si va apela functia __autoload() cu parametrul "betha". Intra pe a doua ramura din if si include clasa corecta. Cand ajunge la $file=="betha.class.php" nu o va mai include odata din cauza lui require_once.

Daca pastrezi o anumita structura a fisierelor, atunci nu vad de ce ar fi complicat sa rezolvi o problema de genul celei intalnite de tine :)
Sus  
aurelian



Data înscrierii: 01/Iun/2003
Mesaje: 833
Locație: Bucuresti

Trimis: Mie Sep 28, 2005 1:07 pm    Titlul subiectului:  

Nu prea sunt de acord cu implementarea ta, pentru ca tu practic cand creezi un Factory, stii ce tip de obiect sa astepti.

De ex:
an style="color: #000000"><?php 15 $f = Factory::instance(); 16 $obj = $f->create('Square'); 17 $obj->draw(); 

1. $obj e clar ca va fi un Sqare.
de ce sa nu scriu:
an style="color: #000000"><?php $sq= new Square(); $sq->draw() 
daca tot specific ca vreau un Square.
iar 2. numele de instance ma duce cu gandul la un singleton (instanta).
si 3. de ce trebuie sa includ de la inceput toate clasele Produs* ?

Eu as zice ca un factory ar trebui considerat ca un creator de obiecte de acelasi tip (au aceiasi clasa de baza) fara sa fiu insa interesat de tipul exact al obiectului returnat. BLA.

Exemplu cel mai bun ramane un Logger.
an style="color: #000000"><?php abstract class Logger {   public static function factory() {     try {       $logger= new FileLogger('log.txt');    } catch (IOException, $io) {       $logger = new StdoutLogger();       $logger->write($io->getMessage());   }      return $logger;   }   abstract function write($message); } class FileLogger extends Logger {   private $handler;   function __construct($file) {     if (!is_file($file)) throw new IOException ($file . ' No such File or directory');     // deschide fisierul verificare daca am permisiunea de scriere, etc.    $this->handler= fopen($file, 'w');   }   function write($message) {      // scrie mesajul in fisier   } } class StdoutLogger {   function write($message) {     echo $message;   } } // usage: $logger= Logger::factory(); $logger->write('Foo - Bar!'); 

Pana la urma eu vreau sa loghez un mesaj, nu ma intereseaza metoda prin care o fac (ca scriu intr-un fisier sa nu).
Vezi si:
http://amansio.blogspot.com/2005/01/php-5-factory.html
sau:
http://amansio.blogspot.com/2005/01/php-5-logger-diagrama-uml.html
Sus  
coditza



Data înscrierii: 23/Ian/2004
Mesaje: 298
Locație: cluj-napoca

Trimis: Joi Sep 29, 2005 12:26 am    Titlul subiectului:  

aurelian a scris: Nu prea sunt de acord cu implementarea ta, pentru ca tu practic cand creezi un Factory, stii ce tip de obiect sa astepti.

De ex:
an style="color: #000000"><?php 15 $f = Factory::instance(); 16 $obj = $f->create('Square'); 17 $obj->draw(); 

1. $obj e clar ca va fi un Sqare.
de ce sa nu scriu:
an style="color: #000000"><?php $sq= new Square(); $sq->draw() 
daca tot specific ca vreau un Square.

Pentru ca nu specifici ca vreu un Square. Cele doua linii la care faci referinta sunt un exemplu de utilizare, e adevarat, cam aiurea ales.


aurelian a scris: iar 2. numele de instance ma duce cu gandul la un singleton (instanta).
ESTE un singleton. Vezi explicatiile de la constructor.

aurelian a scris:
si 3. de ce trebuie sa includ de la inceput toate clasele Produs* ?

Pentru ca folosesti un factory atunci cand nu stii ce fel de obiect ai, ci doar ce trebuie respectivul obiect sa faca.

an style="color: #000000"><?php $f = Factory::instance(); $dir = opendir('blablabla'); while(false !== ($file = readdir($dir))) {     list(,,$type) = getImageSize($file);     $obj = $f->create($type);     $obj->makeThumb(); } 
E mai clar acuma?
Daca vrei sa poti crea si thumbnailuri pentru *.bmp, doar pui in directorul "Products/" un nou fisier, care va contine implementarea clasei pentru bmp-uri. Fara sa modifici nici o alta linie de cod din scripturile existente.

aurelian a scris:
Eu as zice ca un factory ar trebui considerat ca un creator de obiecte de acelasi tip (au aceiasi clasa de baza) fara sa fiu insa interesat de tipul exact al obiectului returnat. BLA.
ASta este "strategy", care bineinteles poate folosi un factory pentru a determina "strategia".
Sus  
Emil



Data înscrierii: 16/Noi/2003
Mesaje: 301
Locație: echo $REMOTE_ADDR

Trimis: Sâm Oct 01, 2005 1:10 am    Titlul subiectului:  

Un pic de ajutor (dintr-un material cu copyright pe care nu ar trebui sa-l reproduc aici dar.. whateva' si eu l-am obtinut pe cai ilicite (thanks u know who) :mrgreen: )

IN OBJECT-ORIENTED PROGRAMMING, the most common way to create an object is with the new
operator, the language construct provided to do just that. But in some cases, new can be problematic.
For instance, the creation of many kind of objects requires a series of steps: you may need to
compute or fetch the object’s initial settings; you might have to choose which of many sub classes to
instantiate; or perhaps you have to create a batch of other helper objects before you can create the
object you need. In those cases, new is a “process” more than an operation—a cog in a bigger machine.
The Problem
How can you create such “complex” objects easily and conveniently—without cut-and-paste programming?
The Solution
Create a “factory”—a function or a class method— to “manufacture” new objects. To understand the value of a factory, think about the difference between ...
an style="color: #000000"><?php $connection =& new MySqlConnection($user, $password, $database); 
... spread throughout your code, and the more concise ...
an style="color: #000000"><?php $connection =& create_connection(); 
The latter code snippet centralizes the code to create a database connection in the create_connection()
“factory,” and, following the analogy earlier, transforms the process of creating the database
connection to a simple operation—an operation just like new. The Factory pattern injects “intelligence”
to object creation. It encapsulates the creation of an object and returns the new object to the
caller.
Need to change the structure of an object and how it’s created? Just go to the object’s factory and
change the code once. (The Factory pattern is so useful, it’s foundational, meaning that it appears
again and again in many other complex patterns and applications.)

Sample Code
The Factory pattern encapsulates the creation of objects. You can create a Factory within the object
itself or in an external Factory class—the exact implementation depends on the needs of your application.
Let’s look at an example of a Factory.
The application code below repeats the same code to create a database connection in multiple
places:
an style="color: #000000"><?php // PHP4 class Product { function getList() { $db =& new MysqlConnection(DB_USER, DB_PW,  DB_NAME);      //... } function getByName($name) { $db =& new MysqlConnection(DB_USER,             DB_PW, DB_NAME);      //... } //... } 

Why is this bad? Connection parameters are spread all over, and while I’ve shown the parameters as
constants, implying you have a way to define them centrally and globally, the solution is obviously
not optimal:
• While you can change the values of the parameters easily, you cannot add or change the
order of parameters without changing (at least) two sections of code.
• You cannot easily instantiate a new class to use another kind of database connection, say
a PostgresqlConnection.
• It is difficult to separately test and validate the behavior of the connection object.
The code would be much improved with the use of a Factory:
an style="color: #000000"><?php class Product { function getList() { $db =& $this->_getConnection(); //... } function &_getConnection() { return new MysqlConnection(DB_USER, DB_PW, DB_NAME); } } 
The class method _getConnection() centralizes the otherwise repetitious new
MysqlConnection(DB_USER, DB_PW, DB_NAME) calls found in the class’s other methods.
Here’s another variation of a Factory, this one a static call to a Factory class:
an style="color: #000000"><?php class Product { function getList() { $db =& DbConnectionBroker::getConnection(); //... } } class DbConnectionBroker { function &getConnection() { return new MysqlConnection(DB_USER, DB_PW, DB_NAME); } } 

DbConnectionBroker::getConnection() produces the same result as the previous Factory, but has a distinct advantage: it replaces the repeated new MysqlConnection(DB_USER, DB_PW, DB_NAME) calls
in every method in every class that uses the database.
Yet another variation is a call to a Factory class that’s been previously associated with the object:
an style="color: #000000"><?php class Product { var $_db_maker; function setDbFactory(&$connection_factory) { $this->_db_maker =& $connection_factory; } function getList() { $db =& $this->_db_maker->getConnection(); //... } } 
Lastly, a Factory can be implemented as a procedural function, a reasonable way to achieve global
visibility for the Factory:
an style="color: #000000"><?php function &make_db_conn() { return new MysqlConnection(DB_USER, DB_PW, DB_NAME); } class Product { function getList() { $bar =& make_db_conn(); //... } } 
Sus  
Emil



Data înscrierii: 16/Noi/2003
Mesaje: 301
Locație: echo $REMOTE_ADDR

Trimis: Sâm Oct 01, 2005 1:18 am    Titlul subiectului:  

Un alt exemplu de aplicare a acestui pattern (nu stiu de ce "sablon" ma zgarie pe urechi) este in pachetele PEAR cum ar fi Db_DataObject

an style="color: #000000"><?php $produs = Db_DataObject::factory('Produse'); $produs->whereAdd('varsta > 18'); $produs->find(); while($produs->fetch()){    echo $produs->nume; } $user = Db_DataObject::factory('users'); //... 
Sus  
PHPRomania Bot
Bot Member


Data înscrierii: 27/Dec/2007
Mesaje: 1
Locaţie: Server Google
Trimis: Mie Dec 26, 2007 7:01 pm   Titlul subiectului: Ad  

Sus  
 
       Pagina de start a forumului Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc -> PHP Avansat
Pagina 1 din 1


Powered by phpBB 2.0.22 © 2001, 2002 phpBB Group
Varianta în limba română: Romanian phpBB online community