Hay muchos artículos que explican qué son los patrones de diseño y cómo implementarlos; ¡La web no necesita otro de esos artículos! En cambio, en este artículo, discutiremos más cuando y por qué, en lugar de cual y cómo.
Presentaré diferentes situaciones y casos de uso para patrones, y también proporcionaré definiciones cortas para ayudar a aquellos de ustedes que no están familiarizados con estos patrones específicos. Empecemos.
Tutorial republicadoCada pocas semanas, revisamos algunas de las publicaciones favoritas de nuestros lectores de toda la historia del sitio. Este tutorial se publicó por primera vez en octubre de 2012..
Este artículo cubre algunos de los diversos Patrones de diseño ágil, documentado en los libros de Robert C. Martin. Estos patrones son adaptaciones modernas de los patrones de diseño originales definidos y documentados por La cuadrilla de cuatro en 1994. Los patrones de Martin presentan una visión mucho más reciente de los patrones de GoF, y funcionan mejor con técnicas y problemas de programación modernos. De hecho, alrededor del 15% de los patrones originales fueron reemplazados por patrones más nuevos, y los patrones restantes se modernizaron ligeramente..
El patrón de fábrica se inventó para ayudar a los programadores a organizar la información relacionada con la creación de objetos. Los objetos a veces tienen muchos parámetros de constructor; otras veces, deben completarse con información predeterminada inmediatamente después de su creación. Estos objetos deben crearse en fábricas, manteniendo toda la información relacionada con su creación e inicialización dentro de un solo lugar..
Cuando Use un patrón de fábrica cuando se encuentre escribiendo un código para recopilar la información necesaria para crear objetos.
Por qué: Las fábricas ayudan a contener la lógica de creación de objetos en un solo lugar. También pueden romper dependencias para facilitar el acoplamiento suelto y la inyección de dependencias para permitir mejores pruebas.
Hay dos patrones de uso frecuente para recuperar información de una capa de persistencia o fuente de datos externa.
Este patrón define un canal de comunicación entre una solución de persistencia y la lógica empresarial. Para aplicaciones más simples, puede recuperar o recrear objetos completos por sí mismo, pero la creación de objetos es responsabilidad de las fábricas en la mayoría de las aplicaciones complejas. Los gateways simplemente recuperan y persisten los datos en bruto..
Cuando: Cuando necesitas recuperar o conservar información..
Por qué: Ofrece una interfaz pública simple para operaciones de persistencia complicadas. También encapsula el conocimiento de la persistencia y desacopla la lógica de negocios de la lógica de persistencia.
De hecho, el patrón de la puerta de enlace es solo una implementación particular de otro patrón de diseño que trataremos en breve: el patrón del adaptador.
Hay ocasiones en las que no puede (o no quiere) exponer el conocimiento de la capa de persistencia a sus clases de negocios. El patrón proxy es una buena manera de engañar a sus clases de negocios para que piensen que están usando objetos ya existentes.
Cuando: Debe recuperar información de una capa de persistencia o de una fuente externa, pero no quiere que su lógica empresarial sepa esto.
Por qué: Para ofrecer un enfoque no intrusivo para crear objetos detrás de escena. También abre la posibilidad de recuperar estos objetos sobre la marcha, perezosamente y de diferentes fuentes..
Un proxy implementa efectivamente la misma interfaz que un objeto real e imita su funcionalidad. La lógica de negocios simplemente lo usa como si fuera un objeto real, pero de hecho, el proxy crea el objeto si no existe..
El patrón de objeto activo también jugó un papel en los primeros sistemas multitarea.
Bien bien. Eso es genial y todo, pero ¿cómo podemos encontrar los objetos que necesitamos crear??
El patrón de repositorio es muy útil para implementar métodos de búsqueda y lenguajes de mini consulta. Toma estas consultas y usa una puerta de enlace para obtener los datos de una fábrica para producir los objetos que necesita.
El patrón de repositorio es diferente de los otros patrones; existe como parte del Diseño Dirigido por Dominio (DDD), y no se incluye como parte del libro de Robert C. Martin.
Cuando: Debe crear varios objetos en función de los criterios de búsqueda, o cuando necesite guardar varios objetos en la capa de persistencia.
Por qué: Para permitir que los clientes que necesitan objetos específicos trabajen con un lenguaje de persistencia y consulta común y bien aislado. Elimina aún más código relacionado con la creación de la lógica de negocios.
Pero ¿y si el repositorio no puede encontrar los objetos? Una opción sería devolver un NULO
Valor, pero hacerlo tiene dos efectos secundarios:
if (is_null ($ param)) return;
) en su código.Un mejor enfoque es devolver un nulo
objeto.
Un objeto nulo implementa la misma interfaz de sus otros objetos, pero los miembros del objeto devuelven un valor neutral. Por ejemplo, un método que devuelve una cadena devolverá una cadena vacía; otro miembro que devuelva un valor numérico devolvería cero. Esto lo obliga a implementar métodos que no devuelven datos significativos, pero puede usar estos objetos sin preocuparse por el legado rechazado o ensuciar su código con controles nulos.
Cuando: Con frecuencia verificas nulo
o te has negado legados.
Por qué: Puede agregar claridad a su código y lo obliga a pensar más sobre el comportamiento de sus objetos.
No es inusual llamar a muchos métodos en un objeto antes de que pueda hacer su trabajo. Hay situaciones en las que debe preparar un objeto después de su creación antes de poder utilizarlo realmente. Esto lleva a la duplicación de código al crear esos objetos en diferentes lugares.
Cuando: Cuando tienes que realizar muchas operaciones para preparar objetos para su uso..
Por qué: Para mover la complejidad del código consumidor al código de creación..
Esto suena bien, ¿no? De hecho, es bastante útil en muchas situaciones. El patrón de comando se usa ampliamente para implementar transacciones. Si añades un sencillo deshacer()
método a un objeto de comando, puede rastrear todas las transacciones de deshacer que realizó y revertirlas si es necesario.
Así que ahora tiene diez (o más) objetos de comando, y quiere que se ejecuten simultáneamente. Puedes juntarlos en un objeto activo..
El objeto activo simple e interesante solo tiene una responsabilidad: mantener una lista de objetos de comando y ejecutarlos.
Cuando: Varios objetos similares tienen que ejecutarse con un solo comando.
Por qué: Obliga a los clientes a realizar una sola tarea y afectar a múltiples objetos.
Un objeto activo elimina cada comando de su lista después de la ejecución del comando; Es decir, puede ejecutar el comando una sola vez. Algunos ejemplos reales de un objeto activo son:
Los patrones de diseño están aquí para resolver problemas..
comprar()
El comando de cada producto los elimina del carrito..El patrón de objeto activo también jugó un papel en los primeros sistemas multitarea. Cada objeto dentro de un objeto activo mantendría una referencia al objeto activo. Ejecutarían una parte de sus trabajos y luego se volverían a poner en la cola. Incluso en los sistemas actuales, puede usar un objeto activo para permitir que otros objetos funcionen mientras espera la respuesta de otra aplicación..
Estoy seguro de que ha escuchado la gran promesa de la programación orientada a objetos: la reutilización del código. Los primeros en adoptar OOP imaginaron usar bibliotecas y clases universales en millones de proyectos diferentes. Bueno nunca sucedió.
Este patrón permite la reutilización parcial del código. Es práctico con múltiples algoritmos que solo difieren ligeramente entre sí..
Cuando: Elimina la duplicación de forma sencilla..
Por qué: Hay duplicación y la flexibilidad no es un problema..
Pero la flexibilidad es agradable. ¿Y si realmente lo necesito??
Cuando: La flexibilidad y la reutilización son más importantes que la simplicidad..
Por qué: Úselo para implementar grandes porciones intercambiables de lógica complicada, mientras mantiene una firma de algoritmo común.
Por ejemplo, puedes crear un genérico. Calculadora
y luego usar diferentes Estrategia de computación
Objetos para realizar los cálculos. Este es un patrón de uso moderado, y es más poderoso cuando tiene que definir muchos comportamientos condicionales..
A medida que los proyectos crecen, se vuelve cada vez más difícil para los usuarios externos acceder a nuestra aplicación. Esa es una razón para ofrecer un punto de entrada bien definido a la aplicación o módulo en cuestión. Otras razones pueden incluir el deseo de ocultar el funcionamiento interno y la estructura del módulo.
Una fachada es esencialmente una API, una interfaz agradable y orientada al cliente. Cuando un cliente llama a uno de estos buenos métodos, la fachada delega una serie de llamadas a las clases que oculta para proporcionar al cliente la información requerida o el resultado deseado..
Cuando: Para simplificar su API u ocultar intencionalmente la lógica empresarial interna.
Por qué: Puede controlar la API y las implementaciones reales y la lógica de forma independiente.
El control es bueno, y muchas veces es necesario realizar una tarea cuando algo cambia. Los usuarios deben ser notificados, los LED rojos deben parpadear, debe sonar una alarma ... se entiende la idea.
El popular marco Laravel hace un excelente uso del Patrón de Fachada..
Un objeto nulo implementa la misma interfaz que tus otros objetos..
El patrón de observador ofrece una manera fácil de monitorear objetos y tomar acciones cuando las condiciones cambian. Hay dos tipos de implementaciones de observadores:
Cuando: Para proporcionar un sistema de notificación dentro de su lógica de negocios o al mundo exterior.
Por qué: El patrón ofrece una forma de comunicar eventos a cualquier número de objetos diferentes.
Los casos de uso para este patrón son las notificaciones por correo electrónico, los demonios de registro o los sistemas de mensajería. Por supuesto, en la vida real, hay muchas otras formas de usarlo..
El patrón del observador puede extenderse con un patrón mediador. Este patrón toma dos objetos como parámetros. El mediador se suscribe al primer parámetro, y cuando ocurre un cambio en el objeto observado, el mediador decide qué hacer en el segundo objeto..
Cuando: Los objetos afectados no pueden conocer los objetos observados..
Por qué: Para ofrecer un mecanismo oculto de afectar a otros objetos en el sistema cuando un objeto cambia.
A veces, necesita objetos especiales que son únicos en su aplicación y desea asegurarse de que todos los consumidores puedan ver cualquier cambio realizado en estos objetos. También desea evitar la creación de múltiples instancias de dichos objetos por ciertas razones, como un largo tiempo de inicialización o problemas con acciones concurrentes en algunas bibliotecas de terceros..
Un singleton es un objeto que tiene un constructor privado y un público. obtener Instancia()
método. Este método asegura que solo existe una instancia del objeto..
Cuando: Debe alcanzar la singularidad y desea una solución multiplataforma, evaluada perezosamente que también ofrezca la posibilidad de creación mediante derivación..
Por qué: Ofrecer un único punto de acceso cuando sea necesario..
Otro enfoque de la singularidad es el patrón de diseño de monostatos. Esta solución utiliza un truco ofrecido por los lenguajes de programación orientados a objetos. Tiene métodos públicos dinámicos que obtienen o establecen los valores de variables privadas estáticas. Esto, a su vez, asegura que todas las instancias de dichas clases compartan los mismos valores.
Cuando: La transparencia, la derivabilidad y el polimorfismo se prefieren junto con la singularidad..
Por qué: Para ocultar a los usuarios / clientes el hecho de que el objeto ofrece singularidad..
Presta especial atención a la singularidad. Contamina el espacio de nombres global y, en la mayoría de los casos, puede ser reemplazado por algo más adecuado para esa situación particular.
El patrón de repositorio es bastante útil para implementar métodos de búsqueda ...
Así que tienes un interruptor y una luz. El interruptor puede encender y apagar la luz, pero ahora ha comprado un ventilador y desea usar su interruptor viejo con él. Eso es fácil de lograr en el mundo físico; Toma el interruptor, conecta los cables, y viola..
Desafortunadamente, no es tan fácil en el mundo de la programación. Usted tiene un Cambiar
clase y un Ligero
clase. Si tu Cambiar
usa el Ligero
, ¿Cómo podría usar el Ventilador
?
¡Fácil! Copia y pega el Cambiar
, y cambiarlo para usar el Ventilador
. Pero eso es duplicación de código; Es el equivalente a comprar otro interruptor para el ventilador. Usted podría extender Cambiar
a FanSwitch
, y usa ese objeto en su lugar. Pero que tal si quieres usar un Botón
o Control remoto
, en vez de una Cambiar
?
Este es el patrón más simple jamás inventado. Solo utiliza una interfaz. Eso es todo, pero hay varias implementaciones diferentes..
Cuando: Necesitas conectar objetos y mantener la flexibilidad..
Por qué: Porque es la forma más sencilla de lograr flexibilidad, respetando tanto el principio de inversión de dependencia como el principio de cierre abierto.
PHP se escribe dinámicamente. Esto significa que puede omitir interfaces y usar diferentes objetos en el mismo contexto, lo que pone en riesgo un legado rechazado. Sin embargo, PHP también permite la definición de interfaces, y le recomiendo que use esta gran funcionalidad para proporcionar claridad a la intención de su código fuente..
¿Pero ya tienes un montón de clases con las que quieres hablar? Sí, por supuesto. Hay muchas bibliotecas, API de terceros y otros módulos con los que uno tiene que hablar, pero esto no significa que nuestra lógica empresarial tenga que conocer los detalles de tales cosas..
El patrón del adaptador simplemente crea una correspondencia entre la lógica de negocios y algo más. Ya hemos visto un patrón así en acción: el patrón de pasarela.
Cuando: Debe crear una conexión con un módulo, biblioteca o API preexistente y potencialmente cambiante.
Por qué: Para permitir que la lógica de su negocio dependa solo de los métodos públicos que ofrece el adaptador, y permitir cambiar el otro lado del adaptador fácilmente.
Si alguno de los patrones anteriores no encaja con su situación, entonces podría usar ...
Este es un patrón muy complicado. Personalmente no me gusta porque generalmente es más fácil adoptar un enfoque diferente. Pero para esos casos especiales, cuando otras soluciones fallan, puede considerar el patrón de puente.
Cuando: El patrón del adaptador no es suficiente, y cambia las clases en ambos lados de la tubería.
Por qué: Ofrecer mayor flexibilidad a costa de una complejidad significativa..
Tenga en cuenta que tiene una secuencia de comandos con comandos similares y desea realizar una sola llamada para ejecutarlos. ¡Espere! ¿No hemos visto ya algo como esto antes? El patrón de objeto activo.?
Sí, sí lo hicimos. Pero este es un poco diferente. Es el patrón compuesto, y al igual que el patrón de objeto activo, mantiene una lista de objetos. Pero llamar a un método en un objeto compuesto llama al mismo método en todos sus objetos sin eliminarlos de la lista. Los clientes que llaman a un método piensan que están hablando con un solo objeto de ese tipo en particular, pero de hecho, sus acciones se aplican a muchos, muchos objetos del mismo tipo..
Cuando: Tienes que aplicar una acción a varios objetos similares..
Por qué: Para reducir la duplicación y simplificar cómo se llaman los objetos similares.
Aquí hay un ejemplo: usted tiene una aplicación que es capaz de crear y colocar Pedidos
. Supongamos que tiene tres órdenes: $ orden1
, $ orden2
y $ orden3
. Podrias llamar lugar()
en cada uno de ellos, o podría contener esos pedidos en un $ compositeOrder
objeto, y llamar a su lugar()
método. Esto, a su vez, llama a la lugar()
Método sobre todo lo contenido. Orden
objetos.
Las pasarelas solo recuperan y persisten datos en bruto.
Una máquina de estados finitos (FSM) es un modelo que tiene un número finito de estados discretos. Implementar un FSM puede ser difícil, y la forma más fácil de hacerlo es con la confianza cambiar
declaración. Cada caso
La declaración representa un estado actual en la máquina y sabe cómo activar el siguiente estado..
Pero todos sabemos eso cambiar ... caso
Las declaraciones son menos deseables porque producen un gran despliegue no deseado en nuestros objetos. Así que olvida el cambiar ... caso
declaración, y en su lugar considerar el patrón de estado. El patrón de estado se compone de varios objetos: un objeto para coordinar cosas, una interfaz que representa un estado abstracto y luego varias implementaciones, una para cada estado. Cada estado sabe qué estado viene después de él, y el estado puede notificar al objeto coordinador para establecer su nuevo estado en el siguiente en línea.
Cuando: Se requiere la lógica tipo FSM para ser implementado.
Por qué: Para eliminar los problemas de una cambiar ... caso
declaración, y para encapsular mejor el significado de cada estado individual.
Un dispensador de alimentos podría tener un principal
clase que tiene una referencia a una estado
clase. Las posibles clases de estado podrían ser algo como: A la espera de la moneda
, InsertarCoina
, Producto seleccionado
, Esperando confirmación
, EntregandoProducto
, VolviendoCambiar
. Cada estado realiza su trabajo y crea el siguiente objeto de estado para enviar a la clase coordinadora.
Hay ocasiones en que implementas clases o módulos en una aplicación, y no puedes modificarlos sin afectar radicalmente al sistema. Pero, al mismo tiempo, debe agregar una nueva funcionalidad que sus usuarios requieren.
El patrón decorador puede ayudar en estas situaciones. Es muy simple: tomar la funcionalidad existente y agregarla. Esto se logra extendiendo la clase original y proporcionando una nueva funcionalidad en tiempo de ejecución. Los clientes antiguos continúan usando el nuevo objeto como lo harían con uno antiguo, y los clientes nuevos usarán la funcionalidad antigua y nueva.
Cuando: No puedes cambiar las clases antiguas, pero debes implementar un nuevo comportamiento o estado.
Por qué: Ofrece una forma discreta de agregar nuevas funcionalidades..
Un ejemplo simple es la impresión de datos. Imprime cierta información al usuario como texto simple, pero también desea proporcionar la capacidad de imprimir en HTML. El patrón decorador es una de esas soluciones que le permite mantener ambas funcionalidades..
Si su problema de extensión de la funcionalidad es diferente, por ejemplo, tiene una estructura compleja de objetos en forma de árbol y desea agregar funcionalidad a muchos nodos a la vez, no es posible una iteración simple, pero un visitante puede ser una solución viable. La desventaja, sin embargo, es que la implementación de un patrón de visitante requiere modificaciones a la clase anterior si no fue diseñada para aceptar un visitante.
Cuando: Un decorador no es apropiado y algo de complejidad extra es aceptable.
Por qué: Permitir un enfoque organizado para definir la funcionalidad de varios objetos pero al precio de mayor complejidad..
Usa patrones de diseño para resolver tus problemas, pero solo si encajan.
Los patrones de diseño ayudan a resolver problemas. Como recomendación de implementación, nunca nombre sus clases según los patrones. En su lugar, encuentra los nombres correctos para las abstracciones correctas. Esto le ayuda a discernir mejor cuando realmente necesita un patrón en lugar de solo implementar uno porque puede.
Algunos pueden decir que si no nombras a tu clase con el nombre del patrón, entonces a otros desarrolladores les resultará difícil entender tu código. Si es difícil reconocer un patrón, entonces el problema está en la implementación del patrón.
Usa patrones de diseño para resolver tus problemas, pero solo si encajan. No abuses de ellos. Encontrarás que una solución más simple se adapta a un pequeño problema; mientras que, descubrirá que necesita un patrón solo después de implementar algunas otras soluciones.
Si eres nuevo en el diseño de patrones, espero que este artículo te haya dado una idea de cómo los patrones pueden ser útiles en tus aplicaciones. Gracias por leer!