¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Aprender la Programación Orientada a Objetos con el lenguaje Python
  3. Vista rápida de algunos design patterns
Extrait - Aprender la Programación Orientada a Objetos con el lenguaje Python (con ejercicios prácticos y corregidos)
Extractos del libro
Aprender la Programación Orientada a Objetos con el lenguaje Python (con ejercicios prácticos y corregidos)
1 opinión
Volver a la página de compra del libro

Vista rápida de algunos design patterns

Introducción

Las nociones de programación orientada a objetos que se han visto hasta ahora, son piezas elementales. Se trata del material básico para construir de manera robusta y eficiente, soluciones para resolver problemas comunes de programación: cómo realizar el procesamiento en una jerarquía de clases sin tener que modificar su implementación, cómo asegurar que solo hay una instancia única de tal clase en todo el programa, cómo cambiar la forma en que se representan dichos datos utilizando la menor cantidad de código posible, etc.

Al combinar las piezas básicas de la POO según ciertos esquemas ya probados, las clases y las relaciones entre ellas pueden formar herramientas fantásticas que responden con robustez y elegancia a estos problemas comunes de programación. Estas combinaciones se denominan "design patterns" o "patrones de diseño" en español. Como el término inglés es el más extendido, será el que se utilizará en el resto de este capítulo.

Un design pattern no es un fragmento de código que podamos copiar y pegar en un programa, para que funcione como se desea. Tampoco es solo una librería para importar. Algunas librerías pueden facilitar la implementación del design pattern deseado, pero de hecho, depende del desarrollador codificarlo adaptándolo...

Singleton

El uso de variables globales es una práctica que se debe evitar en la mayoría de los casos: contaminan el espacio de nombres global, ocupan recursos durante toda la vida del programa y son difíciles de controlar, ya que todo el mundo puede acceder a ellas. Sin embargo, en algunos casos específicos, es mucho más conveniente poder acceder a la información de forma global, en lugar de transferirla de un objeto a otro.

class Madrid: 
 
    def __init__(self, sol): 
        self.sol = sol 
 
    def clima(self): 
        if (self.sol): 
           return "Soleado" 
        return "Nublado" 
 
class Espana: 
 
    def __init__(self, sol): 
        self.sol = sol 
        self.ciudades = [Madrid(sol)] 
 
class Europa: 
 
    def __init__(self, sol): 
        self.sol = sol 
        self.pais = [Espana(sol)] 
 
class Tierra: 
 
    def __init__(self, sol): 
        self.sol = sol 
        self.continentes = [Europa(sol)] 
 
class SistemaSolar: 
 
    def __init__(self, sol): 
        self.sol = Sol() 
        self.tierra = Tierra(sol) 

En este ejemplo, la instancia de Sol pertenece lógicamente al sistema solar. También vemos que cada lugar representado por las clases declaradas en el ejemplo, puede tener acceso a esta instancia -única- de Sol, para realizar eventuales procesamiento (visualización del clima en una ciudad, por ejemplo). Sin embargo, si queremos evitar tener una instancia global, es imperativo que cada clase que deba acceder al Sol reciba esta instancia en su constructor, para almacenarla y usarla. De ahí la cadena de constructores que toman el Sol como segundo argumento. Usar un objeto global simplificaría enormemente el código: 

class Madrid: 
 
    def clima(self): 
        if (sol): 
            return "Soleado" 
        return "Nublado" ...

Visitante

1. Presentación

Las estructuras de datos son omnipresentes cuando se trata de manipular información. Listas, diccionarios, árboles, tablas, etc. Existen muchos contenedores para ofrecer diferentes rendimientos y funcionalidades de acuerdo con la naturaleza de los datos almacenados. La necesidad de recorrer estos datos y realizar operaciones sobre ellos es muy clásica.

Un archivo XML, por ejemplo, se puede comparar con una estructura de árbol (relación padre-hijo), en la que cada elemento o nodo es una instancia de una clase. Por ejemplo:

<zoo> 
  <animales> 
    <leon nombre="Lorenzo" edad="10" sexo="M"/> 
    <serpiente nombre="Severus" edad="2" sexo="H"/> 
    <pinguino nombre="Esmo" edad="5" sexo="H"/> 
  </animales> 
</zoo> 

Podemos transponer este árbol en objetos, según el siguiente diseño:

images/cap5_pag11a.png

La transformación del contenido textual en una estructura de objetos, permite un manejo más fácil e intuitivo si desea realizar procesamientos de negocio en las entidades. Estos tratamientos pueden ser diversos y variados, como guardar estos objetos en un archivo de un determinado formato o representarlos en una interfaz gráfica, o incluso modificar los atributos de cada uno en función de una determinada condición. El principio general sigue siendo el mismo:

  • acceder al primer elemento;

  • realizar un tratamiento en él;

  • pasar al siguiente elemento;

  • volver a empezar.

La dificultad reside en el hecho de que se desea realizar un tratamiento diferente en función el tipo concreto de los elementos. Para retomar el ejemplo de una representación gráfica de objetos: sería preferible llamar a una función diferente para cada tipo de objeto diferente. En el caso de una jerarquía de clases, ya existe un mecanismo para hacer esto: el polimorfismo, que permite llamar a un método diferente en función del tipo real del objeto. Pero este mecanismo tiene una limitación: para aprovecharlo es necesario modificar los propios objetos, colocando en ellos la implementación de las diferentes sobrecargas del método polimórfico. Sin embargo, modificar la implementación...

Modelo - Vista - Controlador (MVC)

1. Presentación

Una vez más, un concepto principal de la POO es la separación de responsabilidades. Cuando estas responsabilidades son numerosas y no están correlacionadas, es esencial abstraer tanto como sea posible las interfaces entre las clases en cuestión. Un ejemplo típico se refiere a las IHM (interfaces hombre-máquina). Imagine un gran proyecto donde cada cambio es costoso en tiempo de prueba e implementación. En este proyecto todo funciona a la perfección: desde los componentes gráficos que muestran la lista de los próximos vuelos para un destino determinado (por ejemplo), hasta la comunicación con los servidores de los aeropuertos, para actualizar esta lista. Desafortunadamente, se descubre un error en el objeto que representa la lista de vuelos y, por lo tanto, debe ser reemplazado por otro contenedor, cuyos métodos de acceso son ligeramente diferentes.

Si alguna vez este software, por funcional que sea, se ha modelado como un monolito, es decir, sin separar responsabilidades, entonces a la fuerza todo se debe volver a probar y desplegar. Mientras que si, como lo propugna el design pattern MVC, los datos y la forma en que se almacenan (el modelo), los componentes de la interfaz gráfica (la vista) y la capa de comunicaciones entre los dos (el controlador) se implementan en módulos separados, solo el módulo en cuestión, por lo tanto, el modelo (el que realmente contiene los datos), debe volver a probarse y desplegarse. Por lo tanto, dividimos aproximadamente la duración de las pruebas entre 3.

Estas tres entidades no son necesariamente clases estrictamente hablando, sino bloques del software:

  • El modelo es responsable del almacenamiento y de la actualización de los datos que maneja el software. Podemos imaginarlo como una base de datos de donde extraerán las otras clases, aunque en realidad la información se puede almacenar en contenedores (listas, tablas, diccionarios), en archivos, en un servidor remoto, etc. Lo importante es que, desde un punto de vista exterior al modelo, esta información está disponible a través de una interfaz pública que no debe cambiar...

Abstract Factory

1. Presentación

La noción de dispatch (enlace) dinámico es omnipresente en la programación orientada a objetos y una gran parte de los design patterns son aplicaciones del mismo. El design pattern Visitante, por ejemplo, muestra la utilidad de un doble dispatch. Abstract Factory, por su parte, aplica el método del dispatch a la construcción de los objetos.

Pero antes de describir Abstract Factory, hablemos de Factory. Aunque estos dos términos a menudo se usan indistintamente, originalmente existe una diferencia. El término Factory designa cualquier método, cualquier patrón basado en la idea de abstraer la creación de objetos (como, en cierto sentido, Singleton, que delega la instanciación a un método que verifica si la clase aún no ha sido instanciada). La idea de Factory es separar la responsabilidad por el uso de un objeto y la de su creación.

Imagínese por ejemplo una pieza de software encargada de hacer la interfaz entre un documento y varias impresoras que funcionan de manera diferente. Es deseable que la interfaz vista por el documento sea la misma, independientemente de la impresora de destino: no importa cómo, no importa la impresora, lo importante es que el documento se imprima. Para hacer esto, nada podría ser más simple: solo necesita tener una clase abstracta Impresora y representar cada impresora por una clase que herede de ella.

images/cap5_pag25.png

Pero el problema es que no queremos permitir que el cliente cree el objeto ImpresoraA o ImpresoraB. Idealmente, saber cuál es el objeto exacto debería ser solo un detalle de implementación; lo que uno quisiera sería tener una función que creara el objeto adaptado, en función del resto de elementos. Si, por ejemplo, el usuario tiene una lista de nombres de impresoras:

impresora = recuperar_impresora("printer_427") ...