
Si crees que el patrón número uno es Singleton, entonces estas equivocado, este patrón ya está en desuso, aunque es posible que te encuentres con software que todavía hace uso de este patron. Echemos un vistazo a los 5 patrones de diseño de software más utilizados en el mundo PHP en estos días.
Factory
Debes usar el patron Factory cuando quieras construir un objeto, así es, construir y no crear. No querrás tener una fábrica solo para crear un nuevo objeto. Cuando construye el objeto, primero lo crea y luego lo inicializa.
Por lo general, requiere realizar múltiples pasos y aplicar cierta lógica. Tiene mucho sentido tener todo eso en un solo lugar y reutilizarlo cada vez que necesite un objeto nuevo construido de la misma manera. Básicamente, ese es el punto del patrón Factory. Es una buena idea definir una interfaz para su fábrica y que su código dependa de ella y no de una fábrica concreta. Con eso, puede reemplazar fácilmente una fábrica por otra cuando lo necesite.
interface FriendFactoryInterface {
public function create() : Friend
}
A continuación, implementamos nuestra interfaz con la siguiente clase:
class FriendFactory implements FriendFactoryInterface {
public function create() : Friend {
$friend = new Friend();
// inicializar objeto
return $friend;
}
}
Es un patrón de diseño bastante simple pero potente!
Strategy
Se utiliza para ocultar detalles de la implementación de algoritmos necesarios para realizar una operación. Teniendo estrategias, el cliente puede elegir el algoritmo necesario sin conocer la implementación real y aplicarlo para realizar la operación.
Supongamos que necesitamos crear una librería que transfiera los datos de una fuente de datos a otra. Por ejemplo, necesitamos transferir los datos de la base de datos al archivo csv, o de la hoja de cálculo al archivo json. ¿Cómo lo harías tú?
Primero, necesitamos crear estrategias respectivas para leer los datos de los almacenamientos. Vamos a llamarlos lectores (Readers). Luego, necesitamos crear estrategias respectivas para escribir los datos en los almacenamientos. Vamos a llamarlos escritores (Writers).
Por lo tanto, tendremos 2 lectores para leer los datos de la base de datos o de la hoja de cálculo. En consecuencia, tendremos 2 escritores para escribir los datos en el archivo csv o en el archivo json.
Importante: el cliente que trabajará con nuestras estrategias no debería preocuparse por sus implementaciones. Por lo tanto, también debemos definir interfaces para nuestras estrategias. De esa manera, el cliente solo sabrá acerca de los métodos definidos por las interfaces y trabajará solo con ellos, y lo que sucede detrás de escena no es su problema. Finalmente, necesitamos crear la clase que seleccionará las estrategias necesarias en función de dónde y a dónde necesita transferir los datos.
interface ReaderInterface {
public function start() : void;
public function read() : array;
public function stop() : void;
}
interface WriterInterface {
public function start() : void;
public function write(array $data) : void;
public function stop() : void;
}
class DatabaseReader implements ReaderInterface {
...
}
class SpreadsheetReader implements ReaderInterface {
...
}
class CsvWriter implements WriterInterface {
...
}
class JsonWriter implements WriterInterface {
...
}
class Transformer {
...
public function transform(string $from, string $to) : void {
$reader = $this->findReader($from);
$writer = $this->findWriter($to);
$reader->start();
$writer->start();
try {
foreach ($reader->read() as $row) {
$writer->write($row);
}
} finally {
$writer->stop();
$reader->stop();
}
}
...
}
Como puede ver, la clase Transformer es el cliente de nuestras estrategias realmente no se preocupa por las implementaciones con las que funciona. Lo único que le importa son los métodos definidos por nuestras interfaces estratégicas.
Adapter
Se utiliza para convertir una interfaz externa en una interfaz común. Supongamos que en el proyecto obtiene los datos de algún almacenamiento utilizando la siguiente clase.
class Storage {
private $source;
public function __constructor(AdapterInterface $source) {
$this->source = $source;
}
public function getOne(int $id) : ?object {
return $this->source->find($id);
}
public function getAll(array $criteria = []) : Collection {
return $this->source->findAll($criteria);
}
}
Tenga en cuenta que la clase Storage no funciona directamente con la fuente de datos, sino que funciona con el adaptador de la fuente de datos. Además, la clase Storage no sabe nada sobre adaptadores en concreto. Solamente se refiere a la interfaz del adaptador. Por lo tanto, la implementación concreta del adaptador es una caja negra para ella.
Aquí hay un ejemplo de la interfaz del adaptador
interface AdapterInterface {
public function find(int $id) : ?object;
public function findAll(array $criteria = []) : Collection;
}
Ahora, supongamos que usamos alguna librería para acceder a la base de datos MySQL. La librería dicta su propia interfaz y tiene el siguiente aspecto:
$row = $mysql->fetchRow(...);
$data = $mysql->fetchAll(...);
Como puede ver, no podemos integrar esta librería así en nuestra clase Storage. Necesitamos crear un adaptador para ello, como se muestra a continuación:
class MySqlAdapter implements AdapterInterface {
...
public function find(int $id) : ?object {
$data = $this->mysql->fetchRow(['id' => $id]);
//alguna transformación de datos
}
public function findAll(array $criteria = []) : Collection {
$data = $this->mysql->fetchAll($criteria);
//alguna transformación de datos
}
...
}
Después de eso, podemos inyectarlo en la clase Storage de la siguiente manera:
$storage = new Storage(new MySqlAdapter($mysql));
Si más tarde decidimos usar otra librería en lugar de esa, solo tendremos que crear otro adaptador para esa librería como lo hicimos anteriormente, y luego, inyectar el nuevo adaptador en la clase Storage. Como puede ver, para usar una librería diferente para obtener los datos de la base de datos, no necesitamos tocar nada dentro de la clase Storage. Ese es el poder del patrón de diseño Adapter.
Observer
Se utiliza para notificar al resto del sistema sobre ciertos eventos en cierto lugar. Para comprender mejor los beneficios de este patrón, revisemos dos soluciones del mismo problema.
Digamos que necesitamos crear una clase teatro(Theater) para mostrar películas a los críticos. Definimos la clase teatro con el método present. Antes de mostrar la película, queremos enviar mensajes a los teléfonos celulares de los críticos. Luego, en medio de la película, queremos detener la película durante 5 minutos para que los críticos tengan un descanso. Finalmente, después de que termina la película, queremos pedirles a los críticos que dejen sus comentarios.
Veamos cómo se vería esto en el código:
class Theater {
public function present(Movie $movie) : void {
$critics = $movie->getCritics();
$this->messenger->send($critics, '...');
$movie->play();
$movie->pause(5);
$this->progress->break($critics)
$movie->finish();
$this->feedback->request($critics);
}
}
Se ve limpio y prometedor. Ahora, después de un tiempo, el jefe nos dijo que antes de comenzar la película también queremos apagar las luces. Además, en medio de la película, cuando se detiene, queremos mostrar el anuncio. Finalmente, cuando termina la película, queremos comenzar la limpieza automática de la habitación.
Bueno, uno de los problemas aquí es que para lograr eso necesitamos modificar nuestra clase de Teatro, y eso rompe los principios SOLID. Particularmente, rompe el principio abierto/cerrado. Además, este enfoque hará que la clase de teatro dependa de varios servicios adicionales, lo que tampoco es bueno.
¿Qué pasa si ponemos las cosas al revés? En lugar de agregar más y más complejidad y dependencias a la clase de Teatro, distribuiremos la complejidad en todo el sistema y, con eso, reduciremos las dependencias de la clase de Teatro como un extra.
Así es como se verá esto en acción:
class Theater {
public function present(Movie $movie) : void {
$this->getEventManager()
->notify(new Event(Event::START, $movie));
$movie->play();
$movie->pause(5);
$this->getEventManager()
->notify(new Event(Event::PAUSE, $movie));
$movie->finish();
$this->getEventManager()
->notify(new Event(Event::END, $movie));
}
}
$theater = new Theater();
$theater
->getEventManager()
->listen(Event::START, new MessagesListener())
->listen(Event::START, new LightsListener())
->listen(Event::PAUSE, new BreakListener())
->listen(Event::PAUSE, new AdvertisementListener())
->listen(Event::END, new FeedbackListener())
->listen(Event::END, new CleaningListener());
$theater->present($movie);
Como puede ver, el método present se vuelve extremadamente sencillo. No le importa lo que sucede fuera de la clase. Simplemente hace lo que se supone que debe hacer y notifica al resto del sistema sobre los hechos. Cualquier cosa que esté interesada en esos hechos puede escuchar los eventos respectivos y ser notificado sobre ellos y hacer lo que tiene que hacer.
Con este enfoque, también se vuelve bastante fácil agregar complejidad adicional. Todo lo que tiene que hacer es crear un nuevo listener y poner allí la lógica necesaria.
Decorator
Se utiliza cuando desea ajustar el comportamiento de un objeto en tiempo de ejecución y, con eso, reducir las herencias redundantes y el número de clases. Te estarás preguntando por qué necesito eso. Bueno, podría explicarse mejor con ejemplos.
Digamos que tenemos clases Ventana y Puerta, y ambas implementan OpenerInterface.
interface OpenerInterface {
public function open() : void;
}
class Door implements OpenerInterface {
public function open() : void {
// cierra la ventana
}
}
class Window implements OpenerInterface {
public function open() : void {
// abre la ventana
}
}
Tanto las ventanas como las puertas tienen el mismo comportamiento para abrirse. Ahora, necesitamos otras puertas y ventanas con funcionalidad adicional que les indique a los usuarios la temperatura exterior cuando abran las puertas o ventanas. Podemos resolver este problema con la herencia así:
class SmartDoor extends Door {
public function open() : void {
parent::open();
$this->temperature();
}
}
class SmartWindow extends Window {
public function open() : void {
parent::open();
$this->temperature();
}
}
Al final, tenemos 4 clases en total por ahora. Sin embargo, con el patrón Decorator podríamos resolver este problema solo con 3 clases, de la siguiente manera:
class SmartOpener implements OpenerInterface {
private $opener;
public function __construct(OpenerInterface $opener) {
$this->opener = $opener;
}
public function open() : void {
$this->opener->open();
$this->temperature();
}
}
$door = new Door();
$window = new Window();
$smartDoor = new SmartOpener($door);
$smartWindow = new SmartOpener($window);
Hemos introducido la clase SmartOpener que actúa como un proxy pero con una funcionalidad adicional. Eso es lo que hace el truco.
Espero que hayas encontrado este artículo útil e interesante.
Contenido del articulo
- Comentarios
Comentarios
No hay comentarios. Inicia sesión para comentar.