Buscar..


Introducción

Un patrón de diseño es una solución general a un problema común en el desarrollo de software. Este tema de documentación está dirigido específicamente a proporcionar ejemplos de patrones de diseño comunes en Python.

Patrón de estrategia

Este patrón de diseño se llama patrón de estrategia. Se utiliza para definir una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. El patrón de diseño de estrategia permite que un algoritmo varíe independientemente de los clientes que lo utilizan.

Por ejemplo, los animales pueden "caminar" de muchas maneras diferentes. Caminar podría considerarse una estrategia que es implementada por diferentes tipos de animales:

from types import MethodType


class Animal(object):
    
    def __init__(self, *args, **kwargs):
        self.name = kwargs.pop('name', None) or 'Animal'
        if kwargs.get('walk', None):
            self.walk = MethodType(kwargs.pop('walk'), self)

    def walk(self):
        """
        Cause animal instance to walk
        
        Walking funcionallity is a strategy, and is intended to
        be implemented separately by different types of animals.
        """
        message = '{} should implement a walk method'.format(
            self.__class__.__name__)
        raise NotImplementedError(message)


# Here are some different walking algorithms that can be used with Animal
def snake_walk(self):
    print('I am slithering side to side because I am a {}.'.format(self.name))

def four_legged_animal_walk(self):
    print('I am using all four of my legs to walk because I am a(n) {}.'.format(
        self.name))

def two_legged_animal_walk(self):
    print('I am standing up on my two legs to walk because I am a {}.'.format(
        self.name))

Ejecutar este ejemplo produciría el siguiente resultado:

generic_animal = Animal()
king_cobra = Animal(name='King Cobra', walk=snake_walk)
elephant = Animal(name='Elephant', walk=four_legged_animal_walk)
kangaroo = Animal(name='Kangaroo', walk=two_legged_animal_walk)

kangaroo.walk()
elephant.walk()
king_cobra.walk()
# This one will Raise a NotImplementedError to let the programmer
# know that the walk method is intended to be used as a strategy.
generic_animal.walk()

    # OUTPUT:
    #
    # I am standing up on my two legs to walk because I am a Kangaroo.
    # I am using all four of my legs to walk because I am a(n) Elephant.
    # I am slithering side to side because I am a King Cobra.
    # Traceback (most recent call last):
    #   File "./strategy.py", line 56, in <module>
    #     generic_animal.walk()
    #   File "./strategy.py", line 30, in walk
    #     raise NotImplementedError(message)
    # NotImplementedError: Animal should implement a walk method 

Tenga en cuenta que en lenguajes como C ++ o Java, este patrón se implementa utilizando una clase abstracta o una interfaz para definir una estrategia. En Python tiene más sentido simplemente definir algunas funciones externamente que pueden agregarse dinámicamente a una clase usando types.MethodType .

Introducción a los patrones de diseño y patrón Singleton.

Los patrones de diseño proporcionan soluciones a los commonly occurring problems en el diseño de software. Los patrones de diseño fueron introducidos por primera vez por GoF(Gang of Four) donde describían los patrones comunes como problemas que ocurren una y otra vez y soluciones a esos problemas.

Los patrones de diseño tienen cuatro elementos esenciales:

  1. The pattern name es un identificador que podemos usar para describir un problema de diseño, sus soluciones y consecuencias en una o dos palabras.
  2. The problem describe cuándo aplicar el patrón.
  3. The solution describe los elementos que conforman el diseño, sus relaciones, responsabilidades y colaboraciones.
  4. The consequences son los resultados y las compensaciones de aplicar el patrón.

Ventajas de los patrones de diseño:

  1. Son reutilizables en múltiples proyectos.
  2. El nivel arquitectónico de problemas puede ser resuelto.
  3. Han sido probados y probados con el tiempo, lo cual es la experiencia de desarrolladores y arquitectos.
  4. Tienen confiabilidad y dependencia.

Los patrones de diseño se pueden clasificar en tres categorías:

  1. Patrón creacional
  2. Patrón estructural
  3. Patrón de comportamiento

Creational Pattern : se ocupan de cómo se puede crear el objeto y aíslan los detalles de la creación del objeto.

Structural Pattern : diseñan la estructura de clases y objetos para que puedan componerse para lograr resultados más grandes.

Behavioral Pattern : se ocupan de la interacción entre los objetos y la responsabilidad de los objetos.

Patrón Singleton :

Es un tipo de creational pattern que proporciona un mecanismo para tener solo uno y un objeto de un tipo dado y proporciona un punto de acceso global.

Por ejemplo, Singleton se puede usar en operaciones de base de datos, donde queremos que el objeto de la base de datos mantenga la consistencia de los datos.

Implementación

Podemos implementar Singleton Pattern en Python creando solo una instancia de la clase Singleton y sirviendo nuevamente el mismo objeto.

class Singleton(object):
    def __new__(cls):
        # hasattr method checks if the class object an instance property or not.
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance

s = Singleton()
print ("Object created", s)

s1 = Singleton()
print ("Object2 created", s1)

Salida:

('Object created', <__main__.Singleton object at 0x10a7cc310>)
('Object2 created', <__main__.Singleton object at 0x10a7cc310>)

Tenga en cuenta que en lenguajes como C ++ o Java, este patrón se implementa haciendo que el constructor sea privado y creando un método estático que realice la inicialización del objeto. De esta manera, un objeto se crea en la primera llamada y la clase devuelve el mismo objeto a partir de entonces. Pero en Python, no tenemos forma de crear constructores privados.

Patrón de fábrica

El patrón de fábrica es también un Creational pattern . El término factory significa que una clase es responsable de crear objetos de otros tipos. Hay una clase que actúa como una fábrica que tiene objetos y métodos asociados con ella. El cliente crea un objeto llamando a los métodos con ciertos parámetros y la fábrica crea el objeto del tipo deseado y lo devuelve al cliente.

from abc import ABCMeta, abstractmethod

class Music():
    __metaclass__ = ABCMeta
    @abstractmethod
    def do_play(self):
        pass

class Mp3(Music):
    def do_play(self):
        print ("Playing .mp3 music!")
    
class Ogg(Music):
    def do_play(self):
        print ("Playing .ogg music!")
    
class MusicFactory(object):
    def play_sound(self, object_type):
        return eval(object_type)().do_play()
    
if __name__ == "__main__":
    mf = MusicFactory()
    music = input("Which music you want to play Mp3 or Ogg")
    mf.play_sound(music)

Salida:

Which music you want to play Mp3 or Ogg"Ogg"
Playing .ogg music!

MusicFactory es la clase de fábrica aquí que crea un objeto de tipo Mp3 o Ogg según la elección que proporciona el usuario.

Apoderado

El objeto proxy se usa a menudo para garantizar el acceso protegido a otro objeto, cuya lógica empresarial interna no queremos contaminar con los requisitos de seguridad.

Supongamos que queremos garantizar que solo los usuarios con permisos específicos puedan acceder a los recursos.

Definición de proxy: (garantiza que solo los usuarios que realmente puedan ver las reservas puedan reservar el servicio al consumidor)

from datetime import date
from operator import attrgetter

class Proxy:
    def __init__(self, current_user, reservation_service):
        self.current_user = current_user
        self.reservation_service = reservation_service

    def highest_total_price_reservations(self, date_from, date_to, reservations_count):
        if self.current_user.can_see_reservations:
            return self.reservation_service.highest_total_price_reservations(
                date_from,
                date_to,
                reservations_count
              )
        else:
            return []

#Models and ReservationService:

class Reservation:
    def __init__(self, date, total_price):
        self.date = date
        self.total_price = total_price

class ReservationService:
    def highest_total_price_reservations(self, date_from, date_to, reservations_count):
        # normally it would be read from database/external service
        reservations = [
            Reservation(date(2014, 5, 15), 100),
            Reservation(date(2017, 5, 15), 10),
            Reservation(date(2017, 1, 15), 50)
        ]

        filtered_reservations = [r for r in reservations if (date_from <= r.date <= date_to)]

        sorted_reservations = sorted(filtered_reservations, key=attrgetter('total_price'), reverse=True)

        return sorted_reservations[0:reservations_count]


class User:
    def __init__(self, can_see_reservations, name):
        self.can_see_reservations = can_see_reservations
        self.name = name

#Consumer service:

class StatsService:
    def __init__(self, reservation_service):
        self.reservation_service = reservation_service

    def year_top_100_reservations_average_total_price(self, year):
        reservations = self.reservation_service.highest_total_price_reservations(
            date(year, 1, 1),
            date(year, 12, 31),
            1
        )

        if len(reservations) > 0:
            total = sum(r.total_price for r in reservations)

            return total / len(reservations)
        else:
            return 0

#Test:
def test(user, year):
    reservations_service = Proxy(user, ReservationService())
    stats_service = StatsService(reservations_service)
    average_price = stats_service.year_top_100_reservations_average_total_price(year)
    print("{0} will see: {1}".format(user.name, average_price))

test(User(True, "John the Admin"), 2017)
test(User(False, "Guest"),         2017)

BENEFICIOS
  • estamos evitando cualquier cambio en ReservationService cuando se cambian las restricciones de acceso.
  • no estamos mezclando datos relacionados con la empresa ( date_from , date_to , reservations_count ) con conceptos de dominio no relacionados (permisos de usuario) en servicio.
  • El consumidor ( StatsService ) también está libre de la lógica relacionada con los permisos

CUEVAS
  • La interfaz de proxy es siempre exactamente la misma que el objeto que oculta, por lo que el usuario que consume el servicio envuelto por el proxy ni siquiera estaba al tanto de la presencia del proxy.


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow