 |
Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc Comunitatea PHP Romania
|
| 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 |
|
| |
|