El fin de semana pasado fui a una conferencia de JavaScript que se realizo aca en Argentina (y me olvideo de postear aca) llamada JSConf, 2 dias de puro JS con oradores de bastantes partes del mundo, algunas charlas medio trilladas, otras muy buenas.
Una que me gustó bastante por mi amor a la arquitectura de software fue una que hablaba de tecnicas utilizadas en la creacion de juegos pasadas a la web. La mayoria aplicables sobre Canvas y WebGL, pero una sobretodo me gustó, y fue un patrón de diseño que no conocia, llamado Object Pool Pattern (o Patrón de Cola de Objetos).
Y al haberme reportado un bug en un paquete para PHP que hice para manejar la cache (HybridCache) me acordé y empece a darle un poco de bola, me decidía sacar la versión 1.0 estable y una buena idea fue agregarle este concepto a la clase principal que maneja la cache, ya que las instancias se crean y mueren en periodos muy cortos, y en scripts grandes puede ser usadas varias veces.
Asi que simplemente me puse a jugar para ver como lo haría yo, hasta escribir este post no me fije en otras formas pero ya probé esta forma y funciona como dice la especificación.
El principio es simple, tenemos que controlar la creación de instancias (como en el patrón Singleton) pero en vez de almacenarla en una variable estática, debemos hacerlo en un array (lista).
El primer paso igualmente seria de hacer estos objectos (reciclables), osea que tengan por lo menos un metodo en que reinicien sus valores, desenlacen sus dependecias inyectadas (si las hay), etc.
Vamos a empezar con una clase muy simpe:
class a {
private function __construct($name) {
$this->name = $name;
echo "Naci\n";
}
private function reset($name) {
$this->name = $name;
echo "Me reseteo\n";
}
}
De esta forma, usando el método reset podemos re-usar el objeto como si fuera una instancia nueva. Obviemente si se necesitan recibir datos en el constructor deberemos simularlo.
Ahora el tema es como saber que un objeto muere, como sabrán PHP esta inspirado en varios lenguajes, como C++, Java, Perl, etc. Pero la mayor parte del manejo de Objectos lo tiene inspirado en Java. Java desarrollo gracias a su VM un método para limpiar la memoria automaticamente de objectos no referenciados llamado Garbage Collector (Recolector de basura) a diferencia de C++ donde hay que limpiar la memoria manualmente (por eso los famosos memory leaks), PHP implementa esto (al principio mal, ahora funciona) por lo que un objeto no referenciado es borrado de la memora, ¿y a que me refiero a no referenciado? Si tienen bien estudiada la programación orientada a objetos sabran que los objetos son instancias únicas que no se copian, ni se pueden comparar, si uno hace $a = $instancia; lo unico que esta haciendo es que $a sea una referencia a esa instancia. Si un objeto no esta referenciado por ninguna variable, colección, o lo que sea, no puede volver a ser referenciado, asi que queda inútil en la nada.
Para capturar la muerte de un objeto PHP nos brinda el método magico __destruct, el cual se ejecutará cuando el objeto quede en esta nada. Luego de este metodo, si el objeto sigue dejando de ser referenciado muere. Podemos hacer una prueba:
class a {
private function __construct($name) {
$this->name = $name;
$this->id = uniqid('object_');
echo "Naci\n";
}
private function reset($name) {
$this->name = $name;
echo "Me reseteo\n";
}
function __destruct() {
echo "Mori\n";
}
}
$a = new a("Pedro");
echo "creo \$a\n";
$b = new a("Juan");
echo "creo \$b\n";
$a = "otra cosa";
echo "sigue b";
print_r($b);
De esa forma, al reemplazar la variable $a por un string, el objeto queda en la nada y muere. Ahora como dije este método se ejecuta antes del que el objeto muera, por lo que podemos seguir accediendo al él atravez de $this, en este caso si queremos “salvarlo” podremos referenciarlo, por ejemplo, de un array statico, ya que está para identificar los objetos y manejar la creacion de instancias vamos a implementar el metodo estatico create, que al igual de getInstance en singleton podrá devolver una instancia ya exitente, pero en este caso no única, sino que controlada.
class a {
private static $pool = array();
public $id;
public static function create($name) {
if (count(static::$pool) >= 1) {
$instance = array_shift(static::$pool);
$instance->reset($name);
} else {
$instance = new self($name);
}
return $instance;
}
private function __construct($name) {
$this->name = $name;
$this->id = uniqid('object_');
echo "Naci\n";
}
private function reset($name) {
$this->name = $name;
echo "Me reseteo\n";
}
function __destruct() {
array_push(static::$pool, $this);
echo "Mori\n";
}
}
$a = a::create("Pedro");
echo "creo \$a con id {$a->id}\n";
$b = a::create("Juan");
echo "creo \$b con id {$b->id}\n";
$a = "otra cosa";
echo "sigue b";
print_r($b);
$c = a::create("Roberto");
echo "creo \$c con id {$c->id}\n";
print_r($c);
Ahora.. ¿Que pasa aca? simple, tenemos el metodo create que se fijará si tiene alguna instancia ya creada en el array estático $pool, en el caso de terner lo resetea con los nuevos datos de creación y lo saca del pool para entragarlo, de lo contrario simplemente lo crea como una instancia nueva.
Por otro lado en el metodo __destruct, cuando el objeto se deje de usar, lo agrega al array $pool, el objeto al estar referenciado no es borrado de la memoria y espera en el pool, para ser usado nuevamente.
Si ejecutan este código tendran algo pareceido a esto:
Naci
creo $a con id object_4fc17c87774c0
Naci
creo $b con id object_4fc17c8777531
Mori
sigue ba Object
(
[id] => object_4fc17c8777531
[name] => Juan
)
Me reseteo
creo $c con id object_4fc17c87774c0
a Object
(
[id] => object_4fc17c87774c0
[name] => Roberto
)
Mori
Como podran ver, la instancia $a se deja de usar, pero en vez de morir queda en el pool, cuando creamos el objeto $c reutiliza el que ya existia (se ve por el id del objeto).
Bueno, la base está, ahora solo quedan otras cosas, como por ejemplo limpiar la instancia, si el objeto ocupa mucho espacio en memoria al momento de meterlo en el pool va a seguir siendo un gasto grande de ram. Para evitar eso se puede incluir un metodo para “limpiar” el objeto, borrando todo valor que se setee en la inicialización, sea requerido o no. En este caso tenemos un solo miembro seteable, asi que en nuestro muy simple ejemplo seria:
private function clear () {
$this->name = null;
}
Otra cosa es tener valores maximos de objetos y de reusos, asi podemos decir que una instancia solo puede ser reutilizada x veces y el pool solo puede mantener en memoria 10 instancias, para eso, a la hora de meter la instancia al pool podemos revisar:
function __destruct() {
if (count(static::$pool) < 10 && $this->resets >= 5) {
array_push(static::$pool, $this);
}
echo "Mori\n";
}
En ese caso revisamos, si hay menos de 10 instancias en el pool, y si el objeto ya fue utilizado 5 veces.
Bueno eso es todo, luego se prodria hacer un Trait para PHP 5.4.
Artículos relacionados






