[Patrones de diseño] Aplicación de varios patrones de diseño en el trabajo práctico: Patrón de construcción | Patrón de cadena de responsabilidad | Patrón de observador y mecanismo de evento de resorte | Patrón de adaptador | Patrón de decorador

Aplicación de varios patrones de diseño en el trabajo práctico.

Pregunta 1: ¿Cuál es la diferencia entre el patrón de fábrica y el patrón de constructor? ¿Ha utilizado alguna vez el patrón de constructor en su trabajo?

1. ¿Qué es el patrón constructor?

El patrón constructor , también conocido como patrón generador , es un patrón de diseño creacional.

  • Definición: Separar la construcción y representación de un objeto complejo para que un mismo proceso de construcción pueda crear diferentes representaciones.

Problemas a resolver por el patrón constructor

  • El patrón constructor puede separar partes de su proceso de ensamblaje y crear un objeto complejo paso a paso. El usuario solo necesita especificar el tipo de objeto complejo para obtener el objeto sin conocer los detalles de su construcción interna.

imagen.png

2. La estructura del modo constructor.

imagen.png

El modo Constructor incluye los siguientes 4 roles:

  • Clase de constructor abstracto (Builder): esta interfaz especifica qué partes del objeto complejo se van a crear y no implica la creación de objetos componentes específicos.
  • Concrete Builder Class (ConcreteBuilder): implementa la interfaz Builder para completar los métodos de creación específicos de cada componente de un producto complejo. Una vez completado el proceso de construcción, proporcione un método para devolver el objeto de producto responsable creado.
  • Clase de producto (Producto): objeto complejo a crear (contiene múltiples componentes).
  • Clase de director (Director): Llame al constructor específico para crear cada parte del objeto complejo. El director no involucra información específica del producto, y solo es responsable de garantizar que cada parte del objeto se cree completamente o en un orden determinado (clientes generalmente solo necesita interactuar con el comandante).

3. Aplicación del modo constructor en el desarrollo real

análisis de la demanda:

  • Operaciones de usuario front-end, selección de condiciones de consulta, agrupación, clasificación, paginación y otros parámetros SQL —> Oracle, MySQL
  • El sistema necesita operar bases de datos de diferentes productos: Oracle,MySQL,DB2…
  • Escriba una clase de constructor específica para cada base de datos y deje que el comandante empalme seleccione, dónde, ordene, agrupe y paginación en orden de acuerdo con los parámetros
  • Genere instrucciones SQL de tipo cadena y listas de parámetros
  1. categoria de producto
 // 产品类
 public class SqlQuery {
    
    
     private String select;
     private String from;
     private String where;
     private String groupBy;
     private String orderBy;
     private String limit;
 
     public SqlQuery(String select, String from) {
    
    
         this.select = select;
         this.from = from;
     }
 
     public String getSelect() {
    
    
         return select;
     }
 
     public void setSelect(String select) {
    
    
         this.select = select;
     }
 
     public String getFrom() {
    
    
         return from;
     }
 
     public void setFrom(String from) {
    
    
         this.from = from;
     }
 
     public String getWhere() {
    
    
         return where;
     }
 
     public String getGroupBy() {
    
    
         return groupBy;
     }
 
     public String getOrderBy() {
    
    
         return orderBy;
     }
 
     public String getLimit() {
    
    
         return limit;
     }
 
     public void setWhere(String where) {
    
    
         this.where = where;
     }
 
     public void setGroupBy(String groupBy) {
    
    
         this.groupBy = groupBy;
     }
 
     public void setOrderBy(String orderBy) {
    
    
         this.orderBy = orderBy;
     }
 
     public void setLimit(String limit) {
    
    
         this.limit = limit;
     }
 
     @Override
     public String toString() {
    
    
         StringBuilder sb = new StringBuilder();
 
         sb.append("SELECT ").append(select).append(" FROM ").append(from);
 
         if (where != null && !where.isEmpty()) {
    
    
             sb.append(" WHERE ").append(where);
         }
 
         if (groupBy != null && !groupBy.isEmpty()) {
    
    
             sb.append(" GROUP BY ").append(groupBy);
         }
 
         if (orderBy != null && !orderBy.isEmpty()) {
    
    
             sb.append(" ORDER BY ").append(orderBy);
         }
 
         if (limit != null && !limit.isEmpty()) {
    
    
             sb.append(" LIMIT ").append(limit);
         }
 
         return sb.toString();
     }
 }
  1. constructor abstracto
 /**
  * 抽象建造者类
  * @author zxl
  * @date 2023/4/20
  **/
 public abstract class SqlQueryBuilder {
    
    
 
     protected SqlQuery sqlQuery;
 
     public void createSqlQuery(String select, String from) {
    
    
         sqlQuery = new SqlQuery(select, from);
     }
 
     public SqlQuery getSqlQuery() {
    
    
         return sqlQuery;
     }
 
     //抽取共性步骤
     public abstract void buildWhere();
     public abstract void buildGroupBy();
     public abstract void buildOrderBy();
     public abstract void buildLimit();
 
 }
  1. constructor de concreto
 /**
  * 具体建造者类:用于创建MySQL数据库的SQL查询语句
  * @author zxl
  * @date 2023/4/20
  **/
 public class MySqlQueryBuilder extends SqlQueryBuilder {
    
    
     
     @Override
     public void buildWhere() {
    
    
         sqlQuery.setWhere("1 = 1"); // MySQL不需要限制行数
     }
 
     @Override
     public void buildGroupBy() {
    
    
         sqlQuery.setGroupBy("deptno, ename, hiredate");
     }
 
     @Override
     public void buildOrderBy() {
    
    
         sqlQuery.setOrderBy("hiredate DESC");
     }
 
     @Override
     public void buildLimit() {
    
    
         sqlQuery.setLimit("0, 10"); // MySQL分页从0开始
     }
 }
 
 /**
  * 用于创建Oracle数据库的SQL查询语句
  * @author zxl
  * @date 2023/4/20
  **/
 public class OracleQueryBuilder extends SqlQueryBuilder {
    
    
 
     @Override
     public void buildWhere() {
    
    
         sqlQuery.setWhere("rownum <= 1000"); // Oracle查询最多返回1000行数据
     }
 
     @Override
     public void buildGroupBy() {
    
    
         // Oracle需要将GROUP BY字段放到SELECT字段中
         sqlQuery.setGroupBy("deptno, ename, hiredate");
         sqlQuery.setSelect(sqlQuery.getSelect() + ", deptno, ename, hiredate");
     }
 
     @Override
     public void buildOrderBy() {
    
    
         sqlQuery.setOrderBy("hiredate");
     }
 
     @Override
     public void buildLimit() {
    
    
         sqlQuery.setLimit("10");
     }
 
 }
  1. cliente
 public class Client {
    
    
 
     public static void main(String[] args) {
    
    
         // 创建MySQL建造者
         SqlQueryBuilder mySqlQueryBuilder = new MySqlQueryBuilder();
 
         // 创建Oracle建造者
         SqlQueryBuilder oracleQueryBuilder = new OracleQueryBuilder();
 
         // 指导者
         SqlQueryDirector sqlQueryDirector = new SqlQueryDirector();
 
         // 构建MySQL查询语句
         sqlQueryDirector.setSqlQueryBuilder(mySqlQueryBuilder);
         sqlQueryDirector.buildSqlQuery("*", "table1");
 
         SqlQuery mySqlQuery = mySqlQueryBuilder.getSqlQuery();
         System.out.println("MySQL Query: " + mySqlQuery);
 
         // 构建Oracle查询语句
         sqlQueryDirector.setSqlQueryBuilder(oracleQueryBuilder);
         sqlQueryDirector.buildSqlQuery("*", "table2");
         SqlQuery oracleQuery = oracleQueryBuilder.getSqlQuery();
         System.out.println("Oracle Query: " + oracleQuery);
     }
 }

5. La diferencia entre el modo de constructor y el modo de fábrica

  • El patrón de fábrica se usa para crear tipos de objetos diferentes pero relacionados (un grupo de subclases que heredan la misma clase principal o interfaz), y los parámetros dados determinan qué tipo de objeto crear. Para abstraer el producto --> producto específico
  • El modo constructor se utiliza para crear un tipo de objeto complejo Al establecer diferentes parámetros opcionales, se pueden crear diferentes objetos "personalizados" y el generador de herramientas se puede ampliar y modificar.

Fundamentos del diseño de software

  • Lo más importante es analizar la similitud y la variabilidad.

El significado de los patrones de diseño.

  • Usar la abstracción para fijar la comunalidad (principio de clausura)
  • Encapsular la variabilidad con clases concretas (principio abierto)

Pregunta 2: Cuénteme sobre el modelo de cadena de responsabilidad ¿El modelo de cadena de responsabilidad es útil en su trabajo?

1. ¿Qué es el modelo de cadena de responsabilidad?

cadena de definición de patrón de responsabilidad

  • Evite acoplar el remitente y el receptor de una solicitud, permitiendo que múltiples objetos tengan la oportunidad de procesar la solicitud.
  • Conecte los objetos que reciben la solicitud en una cadena y pase la solicitud a lo largo de la cadena hasta que un objeto pueda manejarla.

imagen.png

2. El papel del modelo de cadena de responsabilidad

  • Desacople la solicitud y el procesamiento de la solicitud para mejorar la escalabilidad del código.

3. Estructura del Modelo de Cadena de Responsabilidad

imagen.png

El patrón de Cadena de Responsabilidad incluye principalmente los siguientes roles:

  • Rol de controlador abstracto (Handler): defina una interfaz para procesar solicitudes, incluidos métodos de procesamiento abstracto y una conexión posterior (cada controlador en la cadena tiene una variable de miembro para mantener una referencia al siguiente controlador, como en el sucesor de la figura anterior).
  • Rol de controlador concreto (Concrete Handler): implementa el método de procesamiento del controlador abstracto, juzga si la solicitud se puede procesar, si puede manejar la solicitud, procesarla, de lo contrario, transferir la solicitud a su sucesor.
  • Rol de clase de cliente (Cliente): cree una cadena de procesamiento y envíe una solicitud al objeto de procesador específico en la cabeza de la cadena. No le importan los detalles de procesamiento y el proceso de entrega de la solicitud.

Tenga en cuenta que en el desarrollo real, esta cadena estándar de estructura de responsabilidad no se adoptará, pero se realizarán algunos cambios, como agregar un administrador de cadena de responsabilidad para administrar estos procesadores específicos.

4. Aplicación del modelo de cadena de responsabilidad en el desarrollo real

En SpringBoot, hay varias formas de practicar el modo de cadena de responsabilidad.Hoy presentamos principalmente tres formas de practicar.

Tomemos como ejemplo un proceso de pedido y lo dividamos en múltiples lógicas de inspección independientes, el posible proceso de verificación de datos es el siguiente:

imagen.png

4.1 Método de implementación 1

  1. Crear Pojo, ordenar objeto
public class OrderContext {
    
    

    /**
     * 请求唯一序列ID
     */
    private String seqId;

    /**
     * 用户ID
     */
    private String userId;

    /**
     * 产品skuId
     */
    private Long skuId;

    /**
     * 下单数量
     */
    private Integer amount;

    /**
     * 用户收货地址ID
     */
    private String userAddressId;

}
  1. Para crear una interfaz de procesador, definimos una interfaz de procesamiento de datos OrderHandleIntercept para una ejecución estandarizada.
public interface OrderHandleIntercept {
    
    

    /**
     * 指定执行顺序
     * @return
     */
    int sort();

    /**
     * 对参数进行处理
     * @param context
     * @return
     */
    OrderContext handle(OrderContext context);
}
  1. Para crear una clase de controlador específica, creamos tres clases de implementación de interfaz diferentes y especificamos el orden de ejecución
/**
 * 处理器1 重复下单逻辑验证
 * @author zxl
 * @date 2023/4/18
 **/
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    @Override
    public int sort() {
    
    
        //执行顺序为 1
        return 1;
    }

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("通过seqId,检查客户是否重复下单");
        return context;
    }
}

/**
 * 处理器2: 用于验证请求参数是否合法
 * @author zxl
 * @date 2023/4/18
 **/
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    @Override
    public int sort() {
    
    
        //执行顺序为 2
        return 2;
    }

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("检查请求参数是否合法,并且获取客户的银行账户");
        return context;
    }
}

/**
 * 处理器3: /用于检查客户账户余额是否充足
 * @author zxl
 * @date 2023/4/18
 **/
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    @Override
    public int sort() {
    
    
        //执行顺序为 3
        return 3;
    }

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额");
        return context;
    }
}
  1. Para la clase de cadena de procesadores, también necesitamos crear un administrador de verificación de datos de pedidos OrderHandleChainService para administrar las clases de implementación anteriores.

    Todo el proceso de procesamiento de la solicitud se administra a través de HandleChain. Para el cliente que envía la solicitud, solo necesita llamar a HandleChain y entregar la solicitud a HandleChain para su procesamiento.

    Para el cliente, no sabe cómo se procesa la solicitud, es decir, si la lógica en el procesamiento de la solicitud ha cambiado, el cliente no lo sabe.

/**
 * 实现ApplicationContextAware,方便获取Spring容器ApplicationContext,从而可以获取容器内的Bean
 * @author zxl
 * @date 2023/4/18
 **/
@Component
public class OrderHandleChainService implements ApplicationContextAware {
    
    

    //保存责任链中的处理者
    private List<OrderHandleIntercept> handleList = new ArrayList<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    

        //获取指定接口实现类,并按照sort进行排序,放入List
        //getBeansOfType这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)
        Map<String, OrderHandleIntercept> serviceMap = applicationContext.getBeansOfType(OrderHandleIntercept.class);

        handleList = serviceMap.values().stream()
                .sorted(Comparator.comparing(OrderHandleIntercept::sort))
                .collect(Collectors.toList());
    }
  
  
    /**
     * 执行处理
     */
    public OrderContext execute(OrderContext context){
    
    
        for (OrderHandleIntercept handleIntercept : handleList) {
    
    
             context = handleIntercept.handle(context);
        }
  
        return context;
    }
  
}
  1. ejecutar pruebas unitarias
@Autowired
private OrderHandleChainService orderHandleChainService;

@Test
public void test02(){
    
    

    orderHandleChainService.execute(new OrderContext());
}
  1. Los resultados de la ejecución son los siguientes.
通过seqId,检查客户是否重复下单
检查请求参数是否合法,并且获取客户的银行账户
检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额

Si necesita continuar agregando el proceso de verificación o el proceso de procesamiento, solo necesita volver a implementar la interfaz OrderHandleIntercept, ¡y no es necesario cambiar otros códigos!

4.2 Implementación 2

Si no queremos usar sort() para especificar el orden de ejecución de la clase de procesador, también podemos usar anotaciones para especificar el orden.

Especifique la ordenación anotando @Order

/**
 * 指定注入顺序为1
 *
 */
@Order(1)
@Component
public class RepeatOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    //...省略
}

/**
 * 指定注入顺序为2
 *
 */
@Order(2)
@Component
public class ValidOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    //...省略
}

/**
 * 指定注入顺序为3
 *
 */
@Order(3)
@Component
public class BankOrderHandleInterceptService implements OrderHandleIntercept {
    
    
    //...省略
}

Modifique OrderHandleChainService, agregue @Autowired para inyectar automáticamente objetos en cada cadena de responsabilidad

/**
 * 责任链管理类
 *  实现ApplicationContextAware,获取IOC容器
 * @author zxl
 * @date 2023/4/18
 **/
@Component
public class OrderHandleChainService {
    
    

    //保存责任链中的处理者
    @Autowired
    private List<OrderHandleIntercept> handleList;


    /**
     * 执行处理
     */
    public OrderContext execute(OrderContext context){
    
    
        for (OrderHandleIntercept handleIntercept : handleList) {
    
    
             context = handleIntercept.handle(context);
        }

        return context;
    }
}

El resultado de la impresión es el siguiente

检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额
通过seqId,检查客户是否重复下单
检查请求参数是否合法,并且获取客户的银行账户

4.3 Método de implementación 3

Para implementar el patrón de diseño de la cadena de responsabilidad definiendo una clase abstracta, o tomando el caso anterior como ejemplo, primero debemos definir una clase abstracta, como AbstractOrderHandle.

/**
 * @author zxl
 * @date 2023/4/18
 **/
public abstract class AbstractOrderHandle {
    
    

    /**
     * 责任链中的下一个节点
     */
    private AbstractOrderHandle next;

    public AbstractOrderHandle getNext() {
    
    
        return next;
    }

    public void setNext(AbstractOrderHandle next) {
    
    
        this.next = next;
    }

    /**
     * 对参数进行处理, 具体参数拦截逻辑,给子类去实现
     * @param orderContext
     * @return: com.mashibing.designboot.responsibility.pojo.OrderContext
     */
    public abstract OrderContext handle(OrderContext orderContext);


    /**
     * 执行入口
     * @param context
     * @return: com.mashibing.designboot.responsibility.pojo.OrderContext
     */
    public OrderContext execute(OrderContext context){
    
    

        //每个处理器 要执行的处理逻辑
        context= handle(context);

        //判断是否还有下一个责任链节点,没有的话,说明是最后一个节点
        if(getNext() != null){
    
    
            getNext().execute(context);
        }

        return context;
    }
}

Luego, cree tres clases de procesamiento respectivamente y organice los números de serie.

@Component
@Order(1)
public class RepeatOrderHandle extends AbstractOrderHandle {
    
    

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("通过seqId,检查客户是否重复下单");
        return context;
    }
}

@Component
@Order(2)
public class ValidOrderHandle extends AbstractOrderHandle {
    
    

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("检查请求参数,是否合法,并且获取客户的银行账户");
        return context;
    }
}

@Component
@Order(3)
public class BankOrderHandle extends AbstractOrderHandle {
    
    

    @Override
    public OrderContext handle(OrderContext context) {
    
    
        System.out.println("检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额");
        return context;
    }
}

Cree una cadena de administrador de responsabilidad, como OrderHandleManager

@Component
public class OrderHandleManager {
    
    

    @Autowired
    private List<AbstractOrderHandle> orderHandleList;

    /**
     * 实现Bean初始化之前的自定义操作
     *     Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的初始化方法)
     *     @PostConstruct注解的功能:当依赖注入完成后用于执行初始化的方法,并且只会被执行一次
     * @return: null
     */
    @PostConstruct
    public void initChain(){
    
    

        int size = orderHandleList.size();

        for (int i = 0; i < size; i++) {
    
    
            if(i == size -1){
    
    
                //责任链上,最后一个处理者
                orderHandleList.get(i).setNext(null);
            } else {
    
    
                //进行链式连接
                orderHandleList.get(i).setNext(orderHandleList.get(i + 1));
            }
        }
  
    }

    /**
     * 执行处理
     * @param context
     * @return: com.mashibing.designboot.responsibility.pojo.OrderContext
     */
    public OrderContext execute(OrderContext context){
    
    
        OrderContext execute = orderHandleList.get(0).execute(context);

        return context;
    }
}

prueba

    @Autowired
    private OrderHandleManager orderHandleManager;

    @Test
    public void test02(){
    
    

//        orderHandleChainService.execute(new OrderContext());
        orderHandleManager.execute(new OrderContext());
    }

¡Los resultados en ejecución son consistentes con las expectativas!

通过seqId,检查客户是否重复下单
检查请求参数,是否合法,并且获取客户的银行账户
检查银行账户是否合法,调用银行系统检查银行账户余额是否满足下单金额

Lo anterior explica cómo SpringBoot introduce el modo de cadena de responsabilidad e introduce tres métodos de implementación.

El segundo tipo se usa más, luego el segundo tipo y el tercer tipo se usa menos. El tercer tipo es esencialmente un método de escritura en cadena, pero no es tan intuitivo de entender como el primer tipo. Se verifica la legibilidad, pero el efecto es el mismo.

5. Resumen del Modelo de Cadena de Responsabilidad

1) Ventajas del modelo de cadena de responsabilidad:

  • Acoplamiento reducido entre objetos
  • Mayor flexibilidad en la delegación de responsabilidades a los objetos
  • La conexión entre objetos se simplifica y la responsabilidad compartida es más clara

2) Desventajas del modelo de cadena de responsabilidad:

  • No hay garantía de que no se procesará ninguna solicitud.
  • El rendimiento del sistema puede verse afectado
  • Mayor complejidad del cliente

3) Usar análisis de escena

  • Para utilizar varios objetos para procesar una solicitud en tiempo de ejecución
  • No quiero que los usuarios conozcan la lógica de procesamiento específica.

Pregunta 3: ¿Cuál es la diferencia entre el modo observador y el modo publicar-suscribir? ¿Ha utilizado alguna vez el modo observador en su trabajo?

1. Patrón de observador

1.1 ¿Qué es el patrón del observador?

El modo observador se utiliza para establecer una relación de dependencia entre objetos. Cuando un objeto cambia, automáticamente notificará a otros objetos y estos responderán en consecuencia.

imagen.png

En el modo observador, existen los siguientes roles:

  • Sujeto: sujeto abstracto (resumen observado), el rol de sujeto abstracto guarda todos los objetos de observador en una colección, cada sujeto puede tener cualquier número de observadores, el sujeto abstracto proporciona una interfaz, puede agregar y eliminar objetos de observador.
  • ConcreteSubject: Sujeto específico (observador específico), este rol almacena el estado relevante en el objeto observador específico y envía una notificación a todos los observadores registrados cuando cambia el estado interno del sujeto específico.
  • Observador: el observador abstracto es una clase abstracta de observador, que define una interfaz de actualización, de modo que se actualiza cuando se le notifican cambios de tema.
  • ConcrereObserver: Observador concreto, que implementa la interfaz de actualización definida por el observador abstracto, para actualizar su propio estado cuando es notificado de cambios de tema. Mantenga una referencia al objeto de destino específico en el observador específico, que almacena el estado relevante del observador específico, y estos estados deben ser coherentes con el objetivo específico.

1.2 Implementación del modo observador

  • observador
/**
 * 抽象观察者
 * @author zxl
 * @date 2022/10/11
 **/
public interface Observer {
    
    

    //update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    public void update();
}

/**
 * 具体观察者
 * @author zxl
 * @date 2022/10/11
 **/
public class ConcreteObserverOne implements Observer {
    
    

    @Override
    public void update() {
    
    
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverOne 得到通知!");
    }
}

/**
 * 具体观察者
 * @author zxl
 * @date 2022/10/11
 **/
public class ConcreteObserverTwo implements Observer {
    
    

    @Override
    public void update() {
    
    
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverTwo 得到通知!");
    }
}
  • Observado
/**
 * 抽象目标类
 * @author zxl
 * @date 2022/10/11
 **/
public interface Subject {
    
    

     void attach(Observer observer);
     void detach(Observer observer);
     void notifyObservers();
}

/**
 * 具体目标类
 * @author zxl
 * @date 2022/10/11
 **/
public class ConcreteSubject implements Subject {
    
    

    //定义集合,存储所有观察者对象
    private ArrayList<Observer> observers = new ArrayList<>();


    //注册方法,向观察者集合中增加一个观察者
    @Override
    public void attach(Observer observer) {
    
    
        observers.add(observer);
    }

    //注销方法,用于从观察者集合中删除一个观察者
    @Override
    public void detach(Observer observer) {
    
    
        observers.remove(observer);
    }

    //通知方法
    @Override
    public void notifyObservers() {
    
    
        //遍历观察者集合,调用每一个观察者的响应方法
        for (Observer obs : observers) {
    
    
            obs.update();
        }
    }
}
  • clase de prueba
public class Client {
    
    

    public static void main(String[] args) {
    
    
        //创建目标类(被观察者)
        ConcreteSubject subject = new ConcreteSubject();

        //注册观察者类,可以注册多个
        subject.attach(new ConcreteObserverOne());
        subject.attach(new ConcreteObserverTwo());

        //具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
        subject.notifyObservers();
    }
}

2. La diferencia entre el modo publicar-suscribir y el modo observador

2.1 Diferencias en las definiciones

El modelo de publicación-suscripción pertenece al modelo de observador en un sentido amplio.

  • El modo de publicación-suscripción es la implementación más utilizada del modo de observador y, desde la perspectiva del desacoplamiento y la reutilización, es mejor que el típico modo de observador.

2.2 La diferencia entre los dos

Echemos un vistazo a las diferencias estructurales entre el modo de observador y el modo de publicación-suscripción

imagen.png

La diferencia en el proceso de operación.

  • Modo observador: la fuente de datos notifica directamente a los suscriptores de los cambios.
  • Modo de publicación-suscripción: la fuente de datos le dice al tercero (canal de eventos) que se ha producido un cambio, y el tercero notifica al suscriptor que se ha producido el cambio.

3. Aplicación del modo observador en el desarrollo real

3.1 Escenarios de demanda en el desarrollo real

En nuestro desarrollo comercial diario, el modo observador juega un papel importante en la realización del desacoplamiento de los negocios . Tomando como ejemplo el escenario de registro de usuario, supongamos que cuando se completa el registro de usuario, es necesario enviar correos electrónicos, cupones, etc., al usuario, como se muestra en la siguiente figura:

imagen.png

Después de usar el patrón del observador

imagen.png

  • Una vez que UserService completa su propia lógica de registro de usuario, solo necesita publicar un evento UserRegisterEvent sin prestar atención a otra lógica de expansión.
  • Otros servicios pueden suscribirse al evento UserRegisterEvent para implementar una lógica de expansión personalizada.

3.2 Mecanismo de evento de primavera

Basado en el patrón del observador, Spring implementa su propio mecanismo de eventos, que consta de tres partes:

imagen.png

  • Evento ApplicationEvent: implemente eventos personalizados heredándolos . Además, el origen del evento se puede obtener a través de sus sourcepropiedades y el tiempo de ocurrencia se puede obtener a través de las propiedades.timestamp
  • Editor de eventos ApplicationEventPublisher: A través de él se pueden publicar eventos.
  • Escucha de eventos ApplicationListener: al implementarlo , se monitorea el tipo de evento especificado.

3.3 Implementación del código

(1) Evento de registro de usuario

  • Cree la clase de evento UserRegisterEvent, herede la clase ApplicationEvent y registre el evento para el usuario. el código se muestra a continuación:
/**
 * 用户注册事件
 * @author zxl
 * @date 2023/4/19
 **/
public class UserRegisterEvent extends ApplicationEvent {
    
    
  
    /**
     * 用户名
     */
    private String username;

    public UserRegisterEvent(Object source) {
    
    
        super(source);
    }

    public UserRegisterEvent(Object source,String  username) {
    
    
        super(source);
        this.username = username;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }
}

(2) UserService (origen del evento + publicación del evento)

  • Cree una clase UserService con el siguiente código:
/**
 * 事件源角色+事件发布
 * @author zxl
 * @date 2023/4/19
 **/
@Service
public class UserService implements ApplicationEventPublisherAware {
    
    


    private Logger logger = LoggerFactory.getLogger(getClass());


    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    
    
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username){
    
    
        //... 执行注册逻辑
        logger.info("[register][执行用户{}的注册逻辑]",username);

        /**
         * publishEvent方法, 参数是: ApplicationEvent的实现类对象
         * 每当事件发布时,所有的ApplicationListener就会被自动的触发.
         */
        //... 发布用户注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this,username));
    }
}
  • Implementa ApplicationEventPublisherAwarela interfaz , ApplicationEventPublisherinyectando en ella.
  • Después de ejecutar la lógica de registro, ApplicationEventPublisherllame publishEvent(ApplicationEvent event)al método y publique UserRegisterEventel evento .
  • La implementación del mecanismo de eventos requiere tres partes, el origen del evento, el evento y el detector de eventos. El ApplicationEvent descrito anteriormente es equivalente al evento, y ApplicationListener es equivalente al detector de eventos. El origen del evento aquí se refiere a ApplicationEventPublisher .

(3) Crear servicio de correo electrónico

/**
 * 事件监听角色
 * @author zxl
 * @date 2023/4/19
 **/
@Service  //实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。
public class EmailService implements ApplicationListener<UserRegisterEvent> {
    
    

    private Logger logger = LoggerFactory.getLogger(getClass());

  
    //实现 #onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
    
    

        logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }
}
  • Implemente la interfaz ApplicationListener y establezca eventos interesados ​​a través de Egenéricos .
  • Después de implementar la interfaz ApplicationListener, debe implementar el método onApplicationEvent() para realizar un procesamiento personalizado en el evento UserRegisterEvent monitoreado. Este método se ejecutará después de que el contenedor inicialice todos los beans.

(4) Servicio de cupones

@Service
public class CouponService {
    
    

    private Logger logger = LoggerFactory.getLogger(getClass());

    //添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent。
    @EventListener 
    public void addCoupon(UserRegisterEvent event) {
    
    
  
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}

(5) Controlador de demostración

  • Proporcionar interfaz /demo/registerde registro
@RestController
@RequestMapping("/demo")
public class DemoController {
    
    

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username) {
    
    
        userService.register(username);
        return "success";
    }

}

3.4 Prueba de código

① Ejecute la clase DemoApplication para iniciar el proyecto.

② Llame a http://127.0.0.1:8080/demo/register?username=mashibing interface para registrarse. La consola IDEA imprime el registro de la siguiente manera:

// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService       : [register][执行用户mashibing的注册逻辑]

//EmailService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService     : [onApplicationEvent][给用户(mashibing) 发送邮件]

//CouponService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService    : [addCoupon][给用户(mashibing) 发放优惠劵]

4. Resumen del patrón del observador

1) Ventajas del patrón de observador

  • Reducir el acoplamiento entre las clases objetivo y los observadores.
  • Se puede implementar un mecanismo de transmisión.

2) Desventajas del patrón del observador

  • El envío de notificaciones tomará una cierta cantidad de tiempo
  • Los observadores tienen dependencias circulares, lo que puede provocar fallas en el sistema

*3) Escenarios de uso común del modo observador

  • Cuando un objeto cambia, otros objetos necesitan ser cambiados
  • Cuando un objeto cambia, necesita ser notificado

Pregunta 4: ¿Cómo aplicar el patrón de adaptador en el trabajo real?

1. Introducción al modo adaptador

1.1 Introducción al Modo Adaptador

La definición original del patrón de adaptador (patrón de adaptador) es: convertir la interfaz de una clase en otra interfaz que los clientes esperan, y el adaptador puede hacer que dos clases incompatibles funcionen juntas.

La función principal del modo adaptador es unificar las interfaces originalmente incompatibles a través de la adaptación y modificación, haciéndola conveniente para que los usuarios las usen, al igual que las líneas de datos de carga universal y de múltiples interfaces que mencionamos, todas son para adaptarse a varias La interfaz es compatible.

imagen.png

¿Por qué cambiar de interfaz?

  • Tanto la interfaz original como la interfaz de destino ya existen, y no es fácil modificar el código de la interfaz.
  • La interfaz abstracta espera reutilizar la lógica de los componentes existentes.

1.2 Estructura del patrón de adaptador

El patrón de adaptador (Adaptador) contiene las siguientes funciones principales:

  • Interfaz de destino (Target): la interfaz esperada por el negocio del sistema actual, que puede ser una clase abstracta o una interfaz.
  • Clase de adaptador (Adaptee): el adaptador es el rol que se va a adaptar, que es la interfaz del componente en la biblioteca de componentes existente para acceder y adaptar.
  • Clase de adaptador (Adapter): es un convertidor que convierte la interfaz del adaptador en la interfaz de destino al heredar o hacer referencia al objeto del adaptador, lo que permite a los clientes acceder al adaptador en el formato de la interfaz de destino.

El patrón adaptador se divide en:

  • adaptador de clase

    imagen.png

  • adaptador de objetos

    imagen.png

La diferencia entre los dos es: la relación entre el adaptador y el adaptador, el adaptador de clase es una relación de herencia y el adaptador de objetos es una relación de agregación. De acuerdo con el principio de diseño, la agregación tiene prioridad sobre la herencia, y los adaptadores de objetos deben ser usado más

2. Aplicación del modo adaptador en el desarrollo real

2.1 Descripción de requisitos

Para mejorar la velocidad del sistema, algunos datos se almacenan en caché en la memoria en forma de KV, y la plataforma proporciona obtener, colocar, eliminar y otras API y mecanismos de administración relacionados.

El proceso iterativo de realización de funciones, desde HashMap a Memcached y luego a Redis, debe garantizar que, al agregar nuevos componentes de caché más adelante, se pueda cambiar libremente y cumplir con el principio de apertura y cierre.

imagen.png

Pregunta de diseño:

  1. Cómo realizar la ampliación de funciones bajo la premisa de ajustarse al principio de apertura y cierre
  2. Las dos API de cliente son diferentes, cómo garantizar el cambio gratuito

Usa el patrón adaptador

2.2 Realización de funciones

Utilice el modo de adaptador para unificar varios componentes de terceros (esquemas de implementación) con funciones similares en la API que necesita, y el código comercial solo depende de la API unificada, no de la API de terceros.

(1) Primero defina una interfaz de caché, incluidos obtener, colocar, eliminar y otros métodos de operación. Por ejemplo:

public interface Cache {
    
    
    void put(String key, Object value);
    Object get(String key);
    void remove(String key);
}

(2) Luego implemente los tres adaptadores de la interfaz, correspondientes a los tres esquemas de caché de HashMap, Memcached y Redis respectivamente. Por ejemplo:

public class HashMapCacheAdapter implements Cache {
    
    
    private Map<String, Object> cache = new HashMap<>();

    @Override
    public void put(String key, Object value) {
    
    
        cache.put(key, value);
    }

    @Override
    public Object get(String key) {
    
    
        return cache.get(key);
    }

    @Override
    public void remove(String key) {
    
    
        cache.remove(key);
    }
}

public class MemcachedCacheAdapter implements Cache {
    
    
    private MemcachedClient memcachedClient;

    public MemcachedCacheAdapter(MemcachedClient memcachedClient) {
    
    
        this.memcachedClient = memcachedClient;
    }

    @Override
    public void put(String key, Object value) {
    
    
        memcachedClient.set(key, 0, value);
    }

    @Override
    public Object get(String key) {
    
    
        return memcachedClient.get(key);
    }

    @Override
    public void remove(String key) {
    
    
        memcachedClient.delete(key);
    }
}


public class RedisCacheAdapter implements Cache {
    
    
    private Jedis jedis;

    public RedisCacheAdapter(Jedis jedis) {
    
    
        this.jedis = jedis;
    }

    @Override
    public void put(String key, Object value) {
    
    
        jedis.set(key, value.toString());
    }

    @Override
    public Object get(String key) {
    
    
        return jedis.get(key);
    }

    @Override
    public void remove(String key) {
    
    
        jedis.del(key);
    }
}

Finalmente, necesitamos una clase de fábrica para crear el adaptador de caché correspondiente según la configuración en el archivo de configuración. Por ejemplo:

public class CacheAdapterFactory {
    
    
  
    public static Cache createCacheAdapter(String type) {
    
    

        if ("HashMap".equals(type)) {
    
    
  
            return new HashMapCacheAdapter();
        } else if ("Memcached".equals(type)) {
    
    
  
            MemCachedClient memCachedClient = new MemCachedClient();
            return new MemcachedCacheAdapter(memCachedClient);
        } else if ("Redis".equals(type)) {
    
    

            Jedis jedis = new Jedis("localhost", 6379);
            return new RedisCacheAdapter(jedis);
        } else {
    
    
  
            throw new IllegalArgumentException("Invalid cache type: " + type);
        }
    }
}

Al usar, solo necesita llamar al método createCacheAdapter de la clase de fábrica y pasar el tipo de caché para obtener el adaptador de caché correspondiente. Por ejemplo:

Cache cache = CacheAdapterFactory.createCacheAdapter("Redis");
cache.put("key", "value");
Object result = cache.get("key");
cache.remove("key");

3. Resumen del modo de adaptador

1) Ventajas del modo adaptador

  • Permite que dos clases no relacionadas se ejecuten juntas
  • Mejore la reutilización de las clases y puede unificar múltiples interfaces diferentes
  • Ocultar la clase de implementación de la interfaz existente
  • Alta flexibilidad, se puede adaptar libremente

2) Desventajas del modo adaptador

  • Usando adaptadores de clase, como máximo se puede adaptar una clase de adaptador a la vez
  • El uso excesivo de adaptadores aumentará la complejidad del sistema

3) Escenarios en los que se aplica el modo de adaptador

  • Unificar la interfaz de múltiples clases
  • Cuando la interfaz original no se puede modificar, pero existe la necesidad de compatibilidad

Pregunta 5: ¿Dime la diferencia entre el modo decorador y el modo proxy?¿Cómo usar el modo decorador en tu trabajo?

1. Definiciones respectivas

Modo decorador: el modo decorador mejora la función de la clase original sin cambiar la interfaz de la clase original y admite el uso anidado de varios decoradores .

imagen.png

  • Rol de componente abstracto de componente: es la clase principal de componentes concretos y clases de decoración abstracta, y declara los métodos comerciales implementados en componentes concretos, de modo que el cliente pueda manejar objetos decorados y no decorados de manera consistente.
  • Componente concreto Función del componente concreto: es una subclase de la clase de componente abstracto, que define el objeto de construcción específico e implementa los métodos declarados en la construcción abstracta. Una clase de decorador puede agregarle responsabilidades adicionales (métodos).
  • Decorador Función de decoración abstracta: es una subclase de la clase de componente abstracto, que se utiliza para agregar responsabilidades a componentes específicos y mantener una referencia al objeto del componente abstracto para lograr el propósito de la decoración.
  • Decorador de Concreto Rol de decoración de concreto: Es una subclase de la clase de decoración abstracta, responsable de agregar nuevas responsabilidades a los componentes. Cada clase de decoración concreta define algunos comportamientos nuevos, que pueden llamar a los métodos definidos y agregar nuevos métodos.

ejemplo de código

/**
 * 抽象构件类
 * @author zxl
 * @date 2022/9/27
 **/
public abstract class Component {
    
    

    //抽象方法
    public abstract void operation();
}

/**
 * 具体构建类
 * @author spikeCong
 * @date 2022/9/27
 **/
public class ConcreteComponent extends Component {
    
    

    @Override
    public void operation() {
    
    
        //基础功能实现(复杂功能通过装饰类进行扩展)
    }
}
/**
 * 抽象装饰类-装饰者模式的核心
 * @author zxl
 * @date 2022/9/27
 **/
public abstract class Decorator extends Component{
    
    

    //维持一个对抽象构件对象的引用
    private Component component;

    //注入一个抽象构件类型的对象
    public Decorator(Component component) {
    
    
        this.component = component;
    }


    @Override
    public void operation() {
    
    
        //调用原有业务方法(这里并没有真正实施装饰,而是提供了一个统一的接口,将装饰过程交给子类完成)
        component.operation();
    }
}


/**
 * 具体装饰类
 * @author zxl
 * @date 2022/9/27
 **/
public class ConcreteDecorator extends Decorator {
    
    


    public ConcreteDecorator(Component component) {
    
    
        super(component);
    }

    @Override
    public void operation() {
    
    
        super.operation(); //调用原有业务方法
        addedBehavior(); //调用新增业务方法
    }

    //新增业务方法
    public void addedBehavior(){
    
    
        //......
    }
}

Modo proxy: el modo proxy define una clase proxy para la clase original sin cambiar la interfaz de la clase original. El propósito principal es controlar el acceso, no mejorar las funciones . Esta es la mayor diferencia entre este y el modo decorador.

imagen.png

2. Principal diferencia

propósito diferente

  • Modo proxy: control -> por ti mismo
  • Patrón de decorador: mejora —> para clase objetivo

diferencia de uso

  • Modo proxy: tiene control absoluto sobre el objeto proxy, puede ejecutar o no
  • Modo decorador: sin control, definitivamente se ejecutará, agregando una capa de función de decoración

para el cliente

  • Modo proxy: más preocupado por la función del objeto proxy
  • Modo decorador: más preocupado por potenciar la funcionalidad del decorador

3. Aplicación del patrón decorador en el desarrollo real

Requisito: cuando hay múltiples decoraciones, cómo asegurarse de que la última decoración se decore sobre la base de la decoración anterior. Por ejemplo, los caracteres deben cifrarse y comprimirse. ¿Cómo hacer compresión, compresión basada en encriptación?

  1. Crear una interfaz de componente de personaje
public interface StringComponent {
    
    
    String transform(String str);
}
  1. Implementar componentes de carácter
//字符组件
public class StringEncryptor implements StringComponent {
    
    
    @Override
    public String transform(String str) {
    
    

        //base64编码
        String encoderStr = Base64.getEncoder().encodeToString(str.getBytes());
        return encoderStr;
    }
}
  1. decorador de cifrado de caracteres
public class StringEncryptorDecorator implements StringComponent {
    
    
  
    private StringComponent component;

    public StringEncryptorDecorator(StringComponent component) {
    
    
        this.component = component;
    }

    @Override
    public String transform(String str) {
    
    
        String encrypted = component.transform(str); // 先调用前面一个装饰器或组件的方法
        // 这里演示一个简单的压缩方法,将字符串压缩成一行
        return encrypted.replaceAll("\\s+", "");
    }
}
  1. Decorador para compresión de caracteres
public class StringCompressorDecorator implements StringComponent {
    
    
    private StringComponent component;

    public StringCompressorDecorator(StringComponent component) {
    
    
        this.component = component;
    }

    @Override
    public String transform(String str) {
    
    
        String compressed = component.transform(str); // 先调用前面一个装饰器或组件的方法
        // 这里演示一个简单的压缩方法,将字符串压缩成一行
        return compressed.replaceAll("\\s+", "");
    }
}
  1. cliente
public class Client {
    
    
    public static void main(String[] args) {
    
    
        StringComponent component = new StringEncryptor(); // 创建字符加密组件
        component = new StringEncryptorDecorator(component); // 用字符加密装饰器装饰它
        component = new StringCompressorDecorator(component); // 用字符压缩装饰器再次装饰它

        String original = "Hello, world!"; // 原始字符串
        String transformed = component.transform(original); // 转换后的字符串
        System.out.println(transformed);
    }
}

El resultado es: Ifmmp-!xpsme", que es el resultado de cifrar y comprimir la cadena original. Puede verse que la operación de compresión se realiza sobre la base de la operación de cifrado.

4. Resumen del patrón del decorador

1) Ventajas del patrón decorador:

  • El patrón decorador es más flexible que la herencia .
  • Se puede decorar varias veces y diferentes órdenes de decoración pueden lograr diferentes comportamientos.

2) Desventajas del modo decorador:

  • produciría demasiados objetos pequeños
  • Modo decorador, más propenso a errores

3) Escenarios aplicables del patrón decorador

  • Extensión dinámica
  • El escenario de heredar clases extendidas no es compatible

Supongo que te gusta

Origin blog.csdn.net/a772304419/article/details/130730112
Recomendado
Clasificación