La belleza de los patrones de diseño Modo de 56 observadores (Parte 1): explicación detallada de las diferentes implementaciones del modo de observador en varios escenarios de aplicación

56 | Modo observador (Parte 1): explicación detallada de diferentes implementaciones del modo observador en varios escenarios de aplicación

A menudo dividimos los 23 patrones de diseño clásicos en tres categorías: creacional, estructural y conductual. Hemos aprendido sobre la creación y la estructura antes, y desde hoy, comenzamos a aprender sobre los patrones de diseño de comportamiento. Sabemos que el patrón de diseño creacional resuelve principalmente el problema de "creación de objetos", el patrón de diseño estructural resuelve principalmente el problema de "combinación o ensamblaje de clases u objetos", y el patrón de diseño conductual resuelve principalmente el problema de "interacción entre clases u objetos". "pregunta.

Hay más patrones de diseño de comportamiento, 11 de los cuales representan casi la mitad de los 23 patrones de diseño clásicos. Ellos son: modo observador, modo plantilla, modo estrategia, modo cadena de responsabilidad, modo estado, modo iterador, modo visitante, modo memo, modo comando, modo intérprete, modo intermediario.

Hoy aprendemos el primer patrón de diseño de comportamiento, que también es un patrón que se usa más en el desarrollo real: el patrón del observador. De acuerdo con diferentes escenarios de aplicación, el modo observador corresponderá a diferentes métodos de implementación de código: hay métodos de implementación de bloqueo sincrónicos, y también hay métodos de implementación sin bloqueo asincrónicos; hay métodos de implementación dentro del proceso y métodos de implementación entre procesos. Hoy me centraré en explicar los principios, la implementación y los escenarios de aplicación. En la próxima clase, lo llevaré a implementar un EventBus asíncrono y sin bloqueo basado en el patrón del observador para profundizar su comprensión de este patrón.

Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

Análisis de escenarios de principio y aplicación

El patrón de diseño del observador también se conoce como el patrón de diseño de publicación-suscripción. En el libro "Patrones de diseño" de GoF, se define así:

Defina una dependencia de uno a muchos entre objetos para que cuando un objeto cambie de estado, todos sus dependientes sean notificados y actualizados automáticamente.

Traducido al chino es: definir una dependencia de uno a muchos entre objetos, cuando cambia el estado de un objeto, todos los objetos dependientes serán notificados automáticamente.

En general, el objeto dependiente se llama Observable, y el objeto dependiente se llama Observer . Sin embargo, en el desarrollo de proyectos reales, los nombres de estos dos objetos son relativamente flexibles y hay varios nombres, como: Sujeto-Observador, Editor-Suscriptor, Productor-Consumidor, Emisor de eventos-Oyente de eventos, Despachador-Oyente. No importa cómo lo llame, siempre que el escenario de la aplicación se ajuste a la definición que se acaba de dar, puede considerarse como el modo observador.

De hecho, el modo Observador es un modo relativamente abstracto.De acuerdo con los diferentes escenarios y requisitos de la aplicación, existen métodos de implementación completamente diferentes, de los que hablaremos en detalle más adelante. Ahora, veamos una de las implementaciones más clásicas. Este es también el método de implementación más común dado por muchos libros o materiales cuando se habla de este modo. El código específico es el siguiente:

public interface Subject {
  void registerObserver(Observer observer);
  void removeObserver(Observer observer);
  void notifyObservers(Message message);
}

public interface Observer {
  void update(Message message);
}

public class ConcreteSubject implements Subject {
  private List<Observer> observers = new ArrayList<Observer>();

  @Override
  public void registerObserver(Observer observer) {
    observers.add(observer);
  }

  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }

  @Override
  public void notifyObservers(Message message) {
    for (Observer observer : observers) {
      observer.update(message);
    }
  }

}

public class ConcreteObserverOne implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverOne is notified.");
  }
}

public class ConcreteObserverTwo implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverTwo is notified.");
  }
}

public class Demo {
  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.registerObserver(new ConcreteObserverOne());
    subject.registerObserver(new ConcreteObserverTwo());
    subject.notifyObservers(new Message());
  }
}

De hecho, el código anterior se considera el "código de plantilla" del modo observador, que solo puede reflejar las ideas generales de diseño. En el desarrollo de software real, no es necesario copiar el código de plantilla anterior. Hay varias formas de implementar el modo Observador. El nombre de las funciones y las clases variará mucho según los diferentes escenarios comerciales. Por ejemplo, la función de registro también se puede llamar adjuntar, y la función de eliminar también se puede llamar separar, etc. Sin embargo, todo sigue igual y las ideas de diseño son casi las mismas.

El principio y la implementación del código son muy simples y fáciles de entender, por lo que no necesito demasiada explicación. Centrémonos en un ejemplo específico, ¿bajo qué circunstancias necesitamos usar este patrón de diseño? En otras palabras, ¿qué problemas puede resolver este patrón de diseño?

Supongamos que estamos desarrollando un sistema de gestión de patrimonio e inversiones P2P. Después de que el usuario se registre con éxito, emitiremos fondos de experiencia de inversión para el usuario. La implementación del código se ve más o menos así:

public class UserController {
  private UserService userService; // 依赖注入
  private PromotionService promotionService; // 依赖注入

  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);
    promotionService.issueNewUserExperienceCash(userId);
    return userId;
  }
}

Aunque la interfaz de registro hace dos cosas, registrar y emitir fondos de experiencia, lo que viola el principio de responsabilidad única, pero si no hay necesidad de expansión y modificación, la implementación del código actual es aceptable. Si tiene que usar el modo observador, debe introducir más clases y estructuras de código más complejas, lo cual es un diseño excesivo.

Por el contrario, si la demanda cambia con frecuencia, por ejemplo, después de que el usuario se registre con éxito, ya no se emitirá la tarifa de experiencia, sino que se emitirán cupones en su lugar, y se enviará una carta en el sitio de "Bienvenido al registro exitoso". enviado al usuario. En este caso, necesitamos modificar con frecuencia el código en la función register(), lo que viola el principio de apertura y cierre. Además, si es necesario realizar más y más operaciones de seguimiento después de un registro exitoso, la lógica de la función register() se volverá cada vez más complicada, lo que afectará la legibilidad y la capacidad de mantenimiento del código.

En este momento, el modo observador puede resultar útil. Usando el patrón Observer, refactoricé el código anterior. El código después de la refactorización se ve así:

public interface RegObserver {
  void handleRegSuccess(long userId);
}

public class RegPromotionObserver implements RegObserver {
  private PromotionService promotionService; // 依赖注入

  @Override
  public void handleRegSuccess(long userId) {
    promotionService.issueNewUserExperienceCash(userId);
  }
}

public class RegNotificationObserver implements RegObserver {
  private NotificationService notificationService;

  @Override
  public void handleRegSuccess(long userId) {
    notificationService.sendInboxMessage(userId, "Welcome...");
  }
}

public class UserController {
  private UserService userService; // 依赖注入
  private List<RegObserver> regObservers = new ArrayList<>();

  // 一次性设置好,之后也不可能动态的修改
  public void setRegObservers(List<RegObserver> observers) {
    regObservers.addAll(observers);
  }

  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);

    for (RegObserver observer : regObservers) {
      observer.handleRegSuccess(userId);
    }

    return userId;
  }
}

Cuando necesitemos agregar un nuevo observador, por ejemplo, después de que el usuario se registre correctamente, envíe la información de registro del usuario al sistema de información de crédito de big data, según la implementación del código del modo observador, la función de registro () de la clase UserController no necesita modificarse en absoluto, solo debe agregar otra clase que implemente la interfaz RegObserver y registrarla en la clase UserController a través de la función setRegObservers().

Sin embargo, puede decir que cuando reemplazamos el envío de dinero de experiencia con el envío de cupones, necesitamos modificar el código de la función handleRegSuccess() en la clase RegPromotionObserver ¿Sigue siendo esto una violación del principio de abrir-cerrar? Tiene razón, pero en comparación con la función register(), la lógica de la función handleRegSuccess() es mucho más simple, la modificación es menos propensa a errores y el riesgo de introducir errores es menor.

Hemos aprendido mucho de patrones de diseño antes, no sé si has descubierto que, de hecho, lo que hacen los patrones de diseño es desacoplar. El modo de creación es desacoplar los códigos de creación y uso, el modo estructural es desacoplar diferentes códigos funcionales y el modo de comportamiento es desacoplar diferentes códigos de comportamiento.Específico del modo observador, es el desacoplamiento del observador y el Código observado. Con la ayuda de patrones de diseño, usamos una mejor estructura de código para dividir una gran cantidad de código en clases más pequeñas con más responsabilidades únicas, de modo que cumplan con las características de los principios de apertura y cierre, alta cohesión y acoplamiento flexible, etc., para que como para controlar y tratar problemas de código Complejidad, mejorando la escalabilidad del código.

Diferentes métodos de implementación basados ​​en diferentes escenarios de aplicación.

El modo de observador tiene una amplia gama de escenarios de aplicación, que van desde el desacoplamiento a nivel de código hasta el desacoplamiento de sistema a nivel de arquitectura, o algunas ideas de diseño de productos, todos los cuales tienen sombras de este modo, como suscripciones de correo electrónico, fuentes RSS, esencialmente el patrón del observador.

Bajo diferentes escenarios y requisitos de aplicación, este modo también tiene métodos de implementación completamente diferentes. También mencionamos al principio que existen métodos de implementación de bloqueo síncrono y métodos de implementación sin bloqueo asíncrono; hay métodos de implementación dentro del proceso y métodos de implementación entre procesos .modo de realizacion.

El método de implementación mencionado anteriormente, desde la perspectiva del método de clasificación de ahora, es un método de implementación de bloqueo síncrono. El observador y el código observado se ejecutan en el mismo subproceso, y el observado se bloquea hasta que se ejecutan todos los códigos del observador antes de que se ejecute el código siguiente. En comparación con el ejemplo de registro de usuario mencionado anteriormente, la función register() llama y ejecuta la función handleRegSuccess() de cada observador por turno, y el resultado no se devolverá al cliente hasta que se complete la ejecución.

Si la interfaz de registro es una interfaz a la que se llama con frecuencia y es muy sensible al rendimiento, y esperamos que el tiempo de respuesta de la interfaz sea lo más corto posible, entonces podemos cambiar la implementación de bloqueo sincrónico a una implementación sin bloqueo asincrónico para reducir el tiempo de respuesta. Específicamente, cuando se ejecuta la función userService.register(), comenzamos un nuevo hilo para ejecutar la función handleRegSuccess() del observador, de modo que la función userController.register() no necesita esperar hasta que todas las funciones handleRegSuccess() se ejecutan Después de la finalización, el resultado se devuelve al cliente. La función userController.register() solo necesita ejecutar 3 declaraciones SQL para regresar, y solo necesita ejecutar 1 declaración SQL para regresar, y el tiempo de respuesta se reduce aproximadamente a 1/3 del original.

Entonces, ¿cómo implementar un modo de observador asíncrono sin bloqueo? Un enfoque más simple es crear un nuevo subproceso para ejecutar el código en cada función handleRegSuccess(). Sin embargo, tenemos una forma más elegante de implementarlo, que se basa en EventBus. Hoy, no nos extenderemos en la explicación. En la próxima lección, usaré el tiempo de una clase para aprender de la idea de diseño del marco Google Guava EventBus y guiarlo para desarrollar un marco EventBus que admita asincrónico y sin bloqueo. Se puede reutilizar en cualquier escenario de aplicación que requiera un patrón de observador asíncrono sin bloqueo.

Los dos escenarios que acabamos de mencionar, ya sea una implementación de bloqueo síncrono o una implementación sin bloqueo asíncrono, son todos implementaciones en proceso. Si el registro del usuario es exitoso, necesitamos enviar la información del usuario al sistema de información de crédito de big data, y el sistema de información de crédito de big data es un sistema independiente, y la interacción con él es a través de diferentes procesos, entonces, ¿cómo lograr una relación cruzada? proceso de observación o modo?

Si el sistema de información crediticia de big data proporciona una interfaz RPC para enviar información de registro de usuario, aún podemos usar la idea de implementación anterior y llamar a la interfaz RPC en la función handleRegSuccess() para enviar datos. Sin embargo, también tenemos un método de implementación más elegante y de uso común, que se basa en Message Queue (como ActiveMQ).

Por supuesto, este método de implementación también tiene desventajas, es decir, es necesario introducir un nuevo sistema (cola de mensajes), lo que aumenta los costos de mantenimiento. Sin embargo, sus beneficios también son muy evidentes. En la implementación original, el observador debe estar registrado en el observado, y el observado debe atravesar el observador a su vez para enviar mensajes. Según la implementación de la cola de mensajes, el desacoplamiento entre lo observado y el observador es más completo, y el acoplamiento entre las dos partes es más pequeño. Lo observado es completamente inconsciente del observador, y por la misma razón, el observador es completamente inconsciente de lo observado. La persona observada solo envía mensajes a la cola de mensajes, y el observador solo lee mensajes de la cola de mensajes para ejecutar la lógica correspondiente.

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos juntos, en qué necesitas concentrarte.

Lo que hacen los patrones de diseño es desacoplar. Los patrones creacionales desacoplan los códigos de creación y uso. Los patrones estructurales desacoplan diferentes códigos funcionales. Los patrones de comportamiento desacoplan diferentes códigos de comportamiento. Específico del patrón de observador, que desacopla el observador y el código observado. Con la ayuda de patrones de diseño, usamos una mejor estructura de código para dividir una gran cantidad de código en clases más pequeñas con más responsabilidades individuales, para que puedan cumplir con las características de apertura y cierre, alta cohesión y bajo acoplamiento, etc., para que como para controlar y tratar problemas de código Complejidad, mejorando la escalabilidad del código.

El modo de observador tiene una amplia gama de escenarios de aplicación, que van desde el desacoplamiento a nivel de código hasta el desacoplamiento de sistema a nivel de arquitectura, o algunas ideas de diseño de productos, todos los cuales tienen sombras de este modo, como suscripciones de correo electrónico, fuentes RSS, esencialmente el patrón del observador. Bajo diferentes escenarios y requisitos de aplicación, este modo también tiene métodos de implementación completamente diferentes, incluida la implementación de bloqueo síncrono y la implementación sin bloqueo asíncrono; hay implementaciones dentro del proceso e implementaciones entre procesos.

discusión en clase

  1. Compare la diferencia y la conexión entre el modelo "productor-consumidor" y el modelo observador.
  2. Además de los diversos escenarios de aplicación del modo observador mencionados hoy, como las suscripciones de correo electrónico, ¿puede pensar en otros escenarios de aplicación?

Bienvenido a dejar un mensaje y compartir sus pensamientos conmigo. Si obtienes algo, puedes compartir este artículo con tus amigos.

Supongo que te gusta

Origin blog.csdn.net/fegus/article/details/130498818
Recomendado
Clasificación