¿Qué patrones de diseño usa Spring?

En cuanto a los patrones de diseño, si se usan correctamente, harán que nuestro código sea más conciso y más extensible. Este artículo explica principalmente cómo usar el patrón de estrategia, el patrón de método de fábrica y el patrón de constructor en Spring.

patrón de estrategia

Con respecto al uso del patrón de estrategia, en realidad es relativamente simple en Spring En esencia, el patrón de estrategia es que hay múltiples clases de implementación bajo una interfaz, y cada clase de implementación manejará una situación determinada.

La implementación de recursos de Spring se basa en el patrón de estrategia, que también es una aplicación típica del patrón de estrategia.
La interfaz de recursos en sí misma no proporciona una lógica de implementación para acceder a ningún recurso subyacente. Para diferentes recursos subyacentes, Spring proporcionará diferentes clases de implementación de recursos, y diferentes clases de implementación son responsables de diferentes lógicas de acceso a los recursos. Tales como: UrlResource, ClassPathResource, FileSystemResource.

El ApplicationContext del framework Spring no es solo el contenedor Spring, sino también el "tomador de decisiones" de la estrategia de acceso a recursos, es decir, el objeto Context en el patrón de estrategia.
Una instancia de la clase de implementación de la interfaz ResourceLoader puede obtener una instancia de Resource.

Tomemos las recompensas como ejemplo para explicarlo. Por ejemplo, en nuestro sistema de lotería, hay muchos métodos de recompensa para elegir, como puntos, moneda virtual y efectivo. Al almacenar, debemos usar un campo similar a tipo para representar este tipo de recompensas, por lo que aquí podemos usar polimorfismo para distribuir recompensas. Por ejemplo, abstraemos una interfaz PrizeSender, que se declara de la siguiente manera:

public interface PrizeSender {

    /**
   * 用于判断当前实例是否支持当前奖励的发放
   */
    boolean support(SendPrizeRequest request);

    /**
   * 发放奖励
   */
    void sendPrize(SendPrizeRequest request);

}

Hay dos métodos principales en esta interfaz: support() y sendPrize(), donde el método support() se usa principalmente para juzgar si cada subclase admite el procesamiento del tipo de datos actual, mientras que sendPrize() se usa principalmente para fines específicos. Procesamiento comercial, como la distribución de recompensas aquí. Los siguientes son los códigos específicos para nuestros tres tipos diferentes de distribución de recompensas:

// 积分发放
@Component
public class PointSender implements PrizeSender {

    @Override
    public boolean support(SendPrizeRequest request) {
        return request.getPrizeType() == PrizeTypeEnum.POINT;
    }

    @Override
    public void sendPrize(SendPrizeRequest request) {
        System.out.println("发放积分");
    }
}
// 虚拟币发放
@Component
public class VirtualCurrencySender implements PrizeSender {

    @Override
    public boolean support(SendPrizeRequest request) {
        return PrizeTypeEnum.VIRTUAL_CURRENCY == request.getPrizeType();
    }

    @Override
    public void sendPrize(SendPrizeRequest request) {
        System.out.println("发放虚拟币");
    }
}
// 现金发放
@Component
public class CashSender implements PrizeSender {

    @Override
    public boolean support(SendPrizeRequest request) {
        return PrizeTypeEnum.CASH == request.getPrizeType();
    }

    @Override
    public void sendPrize(SendPrizeRequest request) {
        System.out.println("发放现金");
    }
}

Se puede ver aquí que en cada subtipo, solo necesitamos pasar un determinado parámetro de la solicitud en el método support() para controlar si la solicitud actual es un tipo que la instancia actual puede manejar. Si es así, la lógica de control externa será Pasar la solicitud a la instancia actual para su procesamiento. Hay algunos puntos a tener en cuenta sobre el diseño de esta clase:

  • Use la anotación @Component para anotar la clase actual y declararla como un bean administrado por el contenedor Spring;
  • Declare un método similar a support() que devuelva un valor booleano y use este método para controlar si la instancia actual es una instancia que procesa la solicitud de destino;
  • Declare un método similar a sendPrize() para procesar la lógica comercial. Por supuesto, los nombres de los métodos declarados de acuerdo con diferentes negocios deben ser diferentes. Esto es solo una abstracción del procesamiento comercial unificado;
  • Ya sea el método support() o el método sendPrize(), debe pasar un objeto en lugar de una variable simple del tipo básico. La ventaja de esto es que si desea agregar un campo a la Solicitud más tarde, debe no es necesario modificarlo La definición de la interfaz y la lógica de cada subclase que se ha implementado;

patrón de método de fábrica

Arriba explicamos cómo usar Spring para declarar un patrón de estrategia, luego cómo inyectar diferentes beans para diferentes lógicas comerciales, o cómo se ve la lógica de control externo, aquí podemos usar el patrón de método de fábrica.

El llamado patrón de método de fábrica es para definir un método de fábrica, devolver una instancia a través de los parámetros entrantes y luego procesar la lógica comercial posterior a través de la instancia. Generalmente, el tipo de valor de retorno del método de fábrica es un tipo de interfaz, y la lógica de seleccionar una instancia de subclase específica se encapsula en el método de fábrica. De esta forma, la lógica de llamada externa se separa de la lógica de adquisición de subclase específica. La siguiente figura muestra un diagrama esquemático del patrón del método de fábrica:

Se puede ver que el método de fábrica encapsula la selección de instancias específicas y el cliente, es decir, nuestra persona que llama solo necesita llamar al método específico de la fábrica para obtener una instancia específica, independientemente de cuál sea la implementación de la instancia específica.

Anteriormente explicamos cómo usar el patrón de estrategia para declarar la lógica de procesamiento en Spring, pero no hablamos sobre cómo elegir una estrategia específica. Aquí podemos usar el patrón de método de fábrica.

Aquí hay una PrizeSenderFactory que declaramos:

@Component
public class PrizeSenderFactory {

    @Autowired
    private List<PrizeSender> prizeSenders;

    public PrizeSender getPrizeSender(SendPrizeRequest request) {
        for (PrizeSender prizeSender : prizeSenders) {
            if (prizeSender.support(request)) {
                return prizeSender;
            }
        }

        throw new UnsupportedOperationException("unsupported request: " + request);
    }
}

Aquí declaramos un método de fábrica getPrizeSender(), cuyo parámetro de entrada es SendPrizeRequest, y el valor de retorno es una instancia que implementa la interfaz de PrizeSender. Puede ver que de esta manera, hemos movido hacia abajo el método de selección específico al específico En el subclase, porque la subclase específica implementa si el bean que actualmente implementa PrizeSender admite el procesamiento de la solicitud actual.

En este método de fábrica, tampoco tenemos ninguna lógica relacionada con subclases específicas, es decir, esta clase puede detectar dinámicamente instancias de subclases recién agregadas. Esto se logra principalmente a través de la inyección automática de Spring, principalmente porque lo que inyectamos aquí es una Lista, es decir, si hay una nueva instancia de la subclase PrizeSender, siempre que sea administrada por Spring, se inyectará en come aquí. El siguiente es un fragmento de código que escribimos para probar y simular la llamada de la persona que llama:

@Service
public class ApplicationService {

    @Autowired
    private PrizeSenderFactory prizeSenderFactory;

    public void mockedClient() {
        SendPrizeRequest request = new SendPrizeRequest();
        request.setPrizeType(PrizeTypeEnum.POINT);  // 这里的request一般是根据数据库或外部调用来生成的
        PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
        prizeSender.sendPrize(request);
    }
}

En el código del cliente, primero obtenga una instancia de PrizeSender a través de PrizeSenderFactory y luego distribuya recompensas específicas a través de su método sendPrize(). De esta manera, la lógica de distribución de recompensas específicas se desacopla de la llamada del cliente. Y de acuerdo con la explicación anterior, también sabemos que si se agrega un nuevo método de recompensa, solo necesitamos declarar un nuevo bean que implemente PrizeSender sin modificar el código existente.

modo constructor

Con respecto al modo Builder, creo que los estudiantes que han usado lombok definitivamente dirán que el modo builder es muy simple. Solo necesita declararlo con la anotación @Builder en un bean, y lombok puede declararlo automáticamente como un bean Builder para nosotros. . Con respecto a esta forma de uso, no me comprometo, pero según tengo entendido, hay dos puntos principales que debemos entender:

① En cuanto a su nombre, el modo Builder es un constructor, prefiero entenderlo como la generación final de un objeto a través de ciertos parámetros y cierta lógica de negocio. Si solo usa este método de lombok, aún crea un bean simple en esencia, que no es muy diferente de construir un bean a través de getter y setter;

② En el marco de trabajo de Spring, el mayor problema con el uso de patrones de diseño es que si los beans de Spring se pueden inyectar en cada bean de patrón, si se puede inyectar, entonces su uso se ampliará considerablemente. Porque realmente podemos implementar algunos parámetros simples que se pasan y luego combinar los beans inyectados por Spring para cierto procesamiento para construir un frijol determinado que necesitamos. Obviamente, esto no es posible con lombok;

En cuanto al modo Constructor, podemos tomar como ejemplo a explicar la construcción de la anterior SendPrizeRequest para la emisión de recompensas. Al construir un objeto de solicitud, debe procesarse a través de ciertos parámetros que se pasan en primer plano y, finalmente, se genera un objeto de solicitud. Entonces podemos usar el patrón Builder para construir una SendPrizeRequest.

Aquí se supone que podemos obtener el ID de premio y el ID de usuario de acuerdo con la llamada en primer plano, luego podemos crear un SendPrizeRequest de la siguiente manera:

public class SendPrizeRequest {

    private final PrizeTypeEnum prizeType;
    private final int amount;
    private final String userId;

    public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {
        this.prizeType = prizeType;
        this.amount = amount;
        this.userId = userId;
    }

    @Component
    @Scope("prototype")
    public static class Builder {

        @Autowired
        PrizeService prizeService;

        private int prizeId;
        private String userId;

        public Builder prizeId(int prizeId) {
            this.prizeId = prizeId;
            return this;
        }

        public Builder userId(String userId) {
            this.userId = userId;
            return this;
        }

        public SendPrizeRequest build() {
            Prize prize = prizeService.findById(prizeId);
            return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);
        }
    }

    public PrizeTypeEnum getPrizeType() {
        return prizeType;
    }

    public int getAmount() {
        return amount;
    }

    public String getUserId() {
        return userId;
    }
}

Este es un ejemplo del uso de Spring para mantener un patrón Builder. El método de mantenimiento específico es usar anotaciones @Component y @Scope en la clase Builder para marcar la clase Builder, de modo que podamos inyectar las instancias que necesitamos en la clase Builder para se procesa cierto negocio. Aquí hay algunas cosas a tener en cuenta sobre este modo:

  • La anotación @Scope debe usarse en la clase Builder para marcar la instancia como un tipo de prototipo, porque obviamente, nuestra instancia Builder aquí tiene estado y no puede ser compartida por varios subprocesos;
  • En el método Builder.build(), podemos realizar cierto procesamiento comercial a través de los parámetros entrantes y beans inyectados, para obtener los parámetros necesarios para construir un SendPrizeRequest;
  • La clase Builder debe estar decorada con estática, porque en Java, si la clase interna no está decorada con estática, entonces la instancia de la clase debe depender de una instancia de la clase externa, y aquí esencialmente esperamos construir la instancia de la clase externa a través de la instancia de clase interna, es decir, cuando existe la instancia de clase interna, la instancia de clase externa aún no existe, por lo que aquí se debe usar la modificación estática;
  • De acuerdo con el uso del modo Builder estándar, cada parámetro de la clase externa debe estar decorado con final, y luego solo debe declararse el método getter.

Arriba hemos mostrado cómo usar Spring para declarar una clase de modo Builder, entonces, ¿cómo lo usamos? El siguiente es uno de nuestros ejemplos de uso:

@Service
public class ApplicationService {

    @Autowired
    private PrizeSenderFactory prizeSenderFactory;

    @Autowired
    private ApplicationContext context;

    public void mockedClient() {
        SendPrizeRequest request = newPrizeSendRequestBuilder()
            .prizeId(1)
            .userId("u4352234")
            .build();

        PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
        prizeSender.sendPrize(request);
    }

    public Builder newPrizeSendRequestBuilder() {
        return context.getBean(Builder.class);
    }
}

En el código anterior, principalmente debemos observar el método newPrizeSendRequestBuilder() En Spring, si una clase es del tipo de varias instancias, es decir, está marcada con @Scope("prototipo"), cada vez que obtenga el bean, debe usar el método ApplicationContext.getBean() para obtener una nueva instancia.

Aquí creamos un objeto Builder a través de un método separado, y luego configuramos parámetros como PrizeId y UserId a través de la transmisión, y finalmente construimos una instancia de SendPrizeRequest a través del método build(), y usamos esta instancia para emitir recompensas posteriores.

Supongo que te gusta

Origin blog.csdn.net/vcit102/article/details/131800186
Recomendado
Clasificación