Patrones de diseño: una guía rápida para el patrón Observador.

El patrón de observador es un patrón muy utilizado. De hecho, es tan común que se está estandarizando en muchos lenguajes / bibliotecas de programación. En Java, existe injava.util.Observer (en desuso en Java 9). En Python, tan cerca como apip install pattern-observer. En C ++, a veces podemos usar la biblioteca de impulso, más precisamente #include . Sin embargo, es ampliamente utilizado en la industria como una solución personalizada. Para poder usarlo correctamente y comprender su complejidad, necesitamos sumergirnos y explorarlo.

El patrón de observador se clasifica entre los patrones de diseño de comportamiento. Los patrones de diseño conductual se refieren más específicamente a la comunicación entre clases / objetos. [por Patrones de diseño explicados simplemente]

¿Qué es un patrón de observación? Además de un monitor para caminar que transmite televisión analógica (como en la imagen). El objetivo del patrón es definir una relación de uno a muchos de modo que cuando un objeto cambie de estado, los demás sean notificados y actualizados automáticamente. Más precisamente, desea ser informado sobre los eventos que suceden en el sistema. Vamos a juntar las piezas del rompecabezas en tres pasos.

Paso 1 - Palabras clave

Definir palabras clave es la receta secreta en esta serie de guías rápidas. Este método me ayudó a comprender realmente los patrones de diseño, codificarlos en mi mente y comprender las diferencias entre otros patrones de diseño.

  1. Asunto: Se considera el guardián de la información, de los datos o de la lógica empresarial.
  2. Registrarse / Adjuntar: los observadores se registran en el tema porque desean recibir una notificación cuando haya un cambio.
  3. Evento: los eventos actúan como desencadenantes en el tema, de modo que todos los observadores son notificados.
  4. Notificar: Dependiendo de la implementación, el sujeto puede "enviar" información a los observadores, o los observadores pueden "tirar" si necesitan información del sujeto.
  5. Actualización: los observadores actualizan su estado independientemente de otros observadores, sin embargo, su estado puede cambiar según el evento desencadenado.

Paso 2 - Diagrama

Vamos a dividir este diseño en diferentes clases para simplificarlo un poco.

  • Los ConcreteObservers son clases que contienen información específica de la instancia actual. La función de actualización se llama mediante la operación de notificación del sujeto. Los observadores se actualizan independientemente en función de su estado actual.
  • El observador es la clase madre de los observadores concretos. Contiene una instancia de sujeto. Cuando se inicializa un observador, se registra / se adhiere al sujeto.
  • La clase Asunto tiene una lista o una colección de observadores. Cuando se activa un evento, llama a la operación notify () que recorre a todos los observadores llamando a su función de actualización.

Paso 3 - Código por ejemplo

Sugeriría copiar el código clase por clase desde mi repositorio git "Andreas Poyias" o los fragmentos a continuación (en el orden proporcionado) y pegarlo en cualquiera de los editores C ++ en línea disponibles como c ++ shell, jdoodle, onlineGDB y ejecutar para observar la salida. Luego lea los comentarios o la descripción a continuación. Tómese su tiempo, léalo detenidamente (eso significa un minuto, ni menos ni más).

Ejemplo: considere un juego de fútbol. Muchos seguidores están viendo el juego. Dividimos a los seguidores en dos categorías por edad, jóvenes y viejos. Cuando su equipo marca un gol, los aficionados reaccionan de manera diferente según su edad y su nivel de emoción.
Ahora, hablemos con los términos utilizados para el patrón de observador:

  • El juego es el tema y los seguidores son los observadores.
  • Todos los observadores están adjuntos / registrados al sujeto y se les notifica cuando su equipo de fútbol anota (el evento de activación es si su equipo anota).
  • Los observadores actualizan su comportamiento en función de la notificación recibida.

Tema
Para esta clase, necesitamos acceso a una lista de observadores. Cuando los observadores están a punto de registrarse, llaman a la función theattach (this) para agregarse a la lista disponible (esta es una instancia del observador). Cuando se desencadena un evento, wenotify () todos los observadores actualizan independientemente su estado. En este ejemplo, el desencadenante es si el equipo de fútbol del observador anotó.

#include 
#include 
usando el espacio de nombres estándar;
tema de clase {
    observadores de vector ;
    bool anotado; // evento de disparo
público:
    // registrar observadores
    anular adjunto (Observador * obs) {
        observers.push_back (obs);
    }
   
   // Este es el EVENTO
   // establece el if anotado y notifica a TODOS los observadores
   nulo setScored (Bool Score) {
      puntuado = puntaje;
      notificar();
   }
bool getScored () {
      retorno anotado;
   }
   // notificar que la implementación está más abajo
   // para que el script se compile y se ejecute
   anular notificar ();
};

Observador
Esta clase depende del tema con el que está registrada. Cuando los observadores concretos se inicializan, se unen al Sujeto. En este ejemplo, el estado de cada observador es su emoción: Deja el juego.

Observador de clase
{
    Asunto * subj;
    int excitementLevel; // estado
  público:
    Observador (Asunto * mod, int excLevel)
    {
        subj = mod;
        excitementLevel = excLevel;
        // Los observadores se registran / adjuntan al sujeto
        subj-> adjuntar (esto);
    }
    actualización vacía virtual () = 0;
  protegido:
    Asunto * getSubject () {
       volver subj;
    }
    void setExcitementLevel (int excLevel) {
       excitementLevel = excLevel;
    }
    int getExcitementLevel () {
       volver excitación Nivel;
    }
};

Esta es la declaración Subjeto :: notificar () y, como mencionamos antes, su trabajo es notificar a todos los observadores para actualizar su estado.

Asunto nulo :: notificar () {
  para (int i = 0; i  update ();
}

Observadores concretos
Los observadores concretos heredan de la clase Observador y todos deben tener la función de actualización. En este ejemplo, los observadores concretos se distinguen entre partidarios jóvenes y viejos. Si su nivel de emoción aumenta demasiado, los partidarios mayores tienen el riesgo de sufrir ataques cardíacos y los más jóvenes tienen el riesgo de beber y conducir. Su estado se actualiza de forma independiente, como lo demostraremos en la función principal más adelante.

clase Old_ConcreteObserver: observador público
{
   público:
     // Llama al constructor padre para registrarse con el sujeto
     Old_ConcreteObserver (Asunto * mod, int div)
        : Observador (mod, div) {}
     // Para las personas mayores, si el nivel de emoción
     // son más de 150 corren el riesgo de un ataque al corazón
     actualización nula ()
     {
        bool anotado = getSubject () -> getScored ();
        setExcitementLevel (getExcitementLevel () + 1);
        if (puntuado && getExcitementLevel ()> 150)
        {
          cout << "¡¡El equipo de Old Observer anotó !!"
               << "Su nivel de emoción es"
               << getExcitementLevel ()
               << "cuidado con los ataques al corazón!" << endl;
        }más{
          cout << "El equipo no anotó. Yeeeih no hay nada de qué preocuparse"
               << endl;
        }
    } // finaliza la actualización ()
};
clase Young_ConcreteObserver: observador público
{
   público:
     // Llama al constructor padre para registrarse con el sujeto
     Young_ConcreteObserver (Asunto * mod, int div)
       : Observador (mod, div) {}
     // Para las personas mayores, si el nivel de emoción
     // tiene más de 100 corren el riesgo de un ataque al corazón
     actualización nula ()
     {
        bool anotado = getSubject () -> getScored ();
        setExcitementLevel (getExcitementLevel () + 1);
        if (puntuado && getExcitementLevel ()> 100)
        {
          cout << "¡¡El equipo de Young Observer anotó !!"
               << "Su nivel de emoción es"
               << getExcitementLevel ()
               << "no beba y maneje !!" << endl;
        }más{
          cout << "El equipo no anotó. Yeeh, nada de qué preocuparse"
               << endl;
       }
    } // finaliza la actualización ()
};

Función principal
Los observadores concretos se registran en la instancia del sujeto. Su estado es el nivel de emoción, que es el segundo parámetro. Cuando se desencadena el evento "subj.setScored (true)", se llama a Sububject :: notify () para actualizar a los observadores registrados. En el escenario a continuación, tenemos tres observadores, el youngObs1 está sobreexcitado y corre el riesgo de beber y conducir, el oldObs1is también sobreexcitado corre un riesgo diferente (de ataque cardíaco). Finalmente, youngObs2, que también es joven como el primero, no tiene nada de qué preocuparse, ya que no está sobreexcitado.

Es importante notar que los tres observadores se actualizaron de forma independiente en función de su estado (nivel de emoción) y su tipo (joven o viejo).
int main () {
   Sujeto subj;
   Young_ConcreteObserver youngObs1 (& subj, 100);
   Old_ConcreteObserver oldObs1 (& subj, 150);
   Young_ConcreteObserver youngObs2 (& subj, 52);
   subj.setScored (verdadero);
}
// Salida
// ¡El equipo de Young Observer anotó! Su nivel de emoción es 101
// ¡no bebas y conduzcas!
// ¡¡¡El equipo de Old Observer anotó !! Su nivel de emoción es 151 reloj
// fuera de los ataques al corazón! El equipo no anotó.
// Yeeh, nada de qué preocuparse

Hay algunos beneficios para el uso del patrón Observador y algunos puntos a tener en cuenta cuando se debe abordar este patrón [Patrones de diseño de Python de aprendizaje].

  • El patrón de observador proporciona un diseño en el que el sujeto y el observador están acoplados libremente. El sujeto no necesita saber sobre la clase ConcreteObserver. Se puede agregar cualquier nuevo observador en cualquier momento. No es necesario modificar el Asunto cuando se agrega un nuevo Observador. Los observadores y los sujetos no están atados y son independientes entre sí, por lo tanto, los cambios en el sujeto u observador no se afectarán entre sí.
  • No hay opción para la composición, ya que la interfaz del Observador puede ser instanciada.
  • Si el observador se usa incorrectamente, puede agregar fácilmente complejidad y generar problemas de rendimiento.
  • Las notificaciones pueden no ser confiables y pueden resultar en condiciones de carrera o inconsistencia.

El próximo blog será una guía rápida del patrón de diseño de Bridge. Es un patrón de diseño estructural que se usa bastante en la industria. No olvides dar me gusta / aplaudir mi blog y seguir mi cuenta. Esto es para darme la satisfacción de haber ayudado a otros desarrolladores y empujarme a seguir escribiendo. Si hay un patrón de diseño específico sobre el que le gustaría aprender, avíseme para que pueda proporcionárselo en el futuro.

Otras guías rápidas sobre patrones de diseño:

  1. Patrones de diseño: una guía rápida de Abstract Factory.
  2. Patrones de diseño: una guía rápida para el patrón de puente.
  3. Patrones de diseño: una guía rápida para el patrón de construcción.
  4. Patrones de diseño: una guía rápida para el patrón de decorador.
  5. Patrones de diseño: una guía rápida para el patrón de fachada.
  6. Patrones de diseño: una guía rápida para el patrón de observador.
  7. Patrones de diseño: una guía rápida del patrón Singleton.