Patrón de diseño para envolver el registro alrededor de la ejecución


11

Introducción

Estoy implementando una clase Java abstracta de un marco de procesamiento *. Al implementar la función execute, puedo agregar funcionalidad lógica de negocios. Me gustaría agregar el registro al inicio y al final de cada implementación de cada una de mis funciones de ejecución. También entre algunos registros, si se hacen cosas específicas. Sin embargo, me gustaría hacer esto de manera uniforme. Mi pensamiento era heredar la clase de marco a una nueva clase propia que implemente el registro y proporcione algunas executeWithLoggingfunciones nuevas , que están envolviendo el registro alrededor de las partes específicas. No estoy seguro de si esa es la mejor idea y si puedo usar un patrón de diseño que haga que todo el esfuerzo sea elegante. ¿Cómo seguiría con todo el asunto?

Desafíos (¡ten en cuenta estos!)

  1. Un desafío en mi caso es que la clase original tiene solo una executefunción, sin embargo, necesitaría iniciar sesión en varias partes.
  2. La segunda cosa es: usar un patrón de decorador (también fue mi primera idea) no funciona del todo, si estoy usando también un execute, porque la super.execute()función debería llamarse en la primera línea de mi nuevo execute(), ¿no? ?
  3. Habría al menos 4 clases involucradas: BaseFunctiondel marco, LoggingFunction extends BaseFunctionde mí, MyBusinessFunction extends LoggingFunctionde mí, MyBusinessClassque instancia MyBusinessFunction.
  4. No solo necesito iniciar sesión al principio y al final execute, sino también en el medio.
  5. El "registro" no es solo un simple registro de Java, sino que en realidad se registra en una base de datos. Esto no cambia nada acerca de los principios, pero demuestra que el registro puede ser más que una sola línea de código.

Tal vez un ejemplo de cómo lo haría todo sería bueno, para ponerme en marcha.

| * Una función Tridente de tormenta, similar a un rayo de tormenta, pero esto no es particularmente importante.


1
El decorador llama a super.execute, no el decorado. Tu clase principal ni siquiera debería darse cuenta de que está siendo decorada y solo debes tener cuidado con estas llamadas, ya que no pueden incluir el decorador (a diferencia de un enfoque con mixins o rasgos).
Frank

¿Está executeen BaseFunctionabstracto?
Visto el

@Spotted: Sí, executees abstracto en BaseFunction.
Make42

Respuestas:


13

En general, hay varias formas posibles de realizar el registro en torno a una determinada ejecución. En primer lugar, es bueno que desee separar esto, ya que el registro es un ejemplo típico de Separación de preocupaciones . Realmente no tiene mucho sentido agregar el registro directamente en la lógica de su negocio.

En cuanto a los posibles patrones de diseño, aquí hay una lista (no concluyente) desde la parte superior de mi cabeza:

  • Patrón de decorador : un patrón de diseño bien conocido, en el que básicamente envuelve la clase sin registro en otra clase de la misma interfaz y el contenedor realiza el registro antes y / o después de llamar a la clase ajustada.

  • Composición de funciones : en un lenguaje de programación funcional, simplemente puede componer su función con una función de registro. Esto es similar al patrón decorador en lenguajes OO, pero no es una forma preferida realmente, ya que la función de registro compuesta se basaría en una implementación de efectos secundarios.

  • Mónadas : las mónadas también son del mundo de la programación funcional y le permiten desacoplar su ejecución y registro. Básicamente, esto significa que agrega los mensajes de registro necesarios y los métodos de ejecución, pero aún no se ejecuta. Luego puede procesar la mónada para realizar la escritura de registros y / o la ejecución real de la lógica de negocios.

  • Programación orientada a aspectos : enfoque más sofisticado, que logra una separación completa, ya que la clase que realiza el comportamiento sin registro nunca interactúa directamente con el registro.

  • Retransmisión: otros patrones de diseño estándar también pueden ser adecuados, por ejemplo, una Fachada podría servir como punto de entrada registrado, antes de reenviar la llamada a otra clase que realiza la parte de lógica de negocios. Esto también se puede aplicar a diferentes niveles de abstracción. Por ejemplo, puede registrar las solicitudes realizadas en una URL de servicio externamente accesible, antes de retransmitirlas a una URL interna para el procesamiento real no registrado de las solicitudes.

En cuanto a su requisito adicional de que necesita realizar el registro "en el medio", eso probablemente apunta a un diseño extraño. ¿Por qué es que su ejecución está haciendo tanto, que algo como un "medio" de su ejecución es remotamente interesante? Si de hecho hay tantas cosas sucediendo, ¿por qué no agruparlas en etapas / fases / qué-tienes? Supongo que, en teoría, podría lograr iniciar sesión en el medio con AOP, pero aún argumentaría que un cambio de diseño parece ser mucho más apropiado.


3

Tengo la sensación de que absolutamente quieres usar un patrón. Recuerde que un mal uso de los patrones de diseño puede resultar en un código menos mantenible / legible.

Ese sonido dijo ...

Su caso es complicado, ya que parece que desea hacer 2 tipos de registro:

  1. Registrando algunos pasos de su funcionalidad lógica (dentro execute )
  2. Registro de llamadas a funciones (entrar / salir de una función) (fuera execute)

Para el primer caso, en el momento en que desea registrar algunas cosas dentro de una función, debe hacerlo ... dentro de la función. Podría usar el patrón de método de plantilla para hacer las cosas un poco más bonitas, pero creo que es excesivo en este caso.

public final class MyBusinessFunction extends BaseFunction {
    @Override
    public final void execute() {
        log.info("Initializing some variables");
        int i = 0;
        double d = 1.0;

        log.info("Starting algorithm");
        for(...) {
            ...
            log.info("One step forward");
            ...
        }
        log.info("Ending algorithm");

        ...
        log.info("Cleaning some mess");
        ...
    }
}

Para el segundo caso, el patrón decorador es el camino a seguir:

public final class LoggingFunction extends BaseFunction {
    private final BaseFunction origin;

    public LoggingFunction(BaseFunction origin) {
        this.origin = origin;
    }

    @Override
    public final void execute() {
        log.info("Entering execute");
        origin.execute();
        log.info("Exiting execute");
    }
}

Y podría usarlo así en BusinessClass:

public final BusinessClass {
    private final BaseFunction f1;

    public BusinessClass() {
        this.f1 = new LoggingFunction(new MyBusinessFunction());
    }

    public final void doFunction() {
        f1.execute();
    }
}

Una llamada a doFunctionregistrará esto:

Entering execute
Initializing some variables
Starting algorithm
One step forward
One step forward
...
Ending algorithm
Cleaning some mess
Exiting execute

¿Funcionaría omitir el atributo de origen y escribir en super.execute();lugar de origin.execute();?
Make42

Su solución no está completa, tal vez debido a mi mala explicación. Agregaré información en la pregunta.
Make42

@ user49283 ¿Por qué desea llamar super.execute()como este método es abstract?
Visto el

@ user49283 También he actualizado mi respuesta
visto el

No he entendido bien el patrón de plantilla, pero lo intentaré pronto. Mientras tanto: ¿Qué tal usar el patrón de comando? Di una respuesta usando un patrón de comando - ¡por favor comente!
Make42

1

Su enfoque parece válido y en realidad es una implementación del Patrón Decorador . Supongo que hay otras formas de hacerlo, pero Decorator es un patrón muy elegante y fácil de entender que es muy adecuado para resolver su problema.


0

¿Qué hay de usar el patrón de comando?

abstract class MyLoggingCommandPart {

  MyLoggingCommandPart() {
    log.info("Entering execute part");
    executeWithoutLogging();
    log.info("Exiting execute part");
  }

  abstract void executeWithoutLogging();

}

abstract class MyLoggingCommandWhole {

  MyLoggingCommandWhole() {
    log.info("Entering execute whole");
    executeWithoutLogging();
    log.info("Exiting execute whole");
  }

  abstract void executeWithoutLogging();

}

public final class MyBusinessFunction extends BaseFunction {

    @Override
    public final void execute() {

        new MyLoggingCommandWhole(){

            @Override
            executeWithoutLogging(){

                new MyLoggingCommandPart(){
                    @Override
                    executeWithoutLogging(){
                    // ... what I actually wanne do
                    }
                }

                new MyLoggingCommandPart(){
                    @Override
                    executeWithoutLogging(){
                    // ... some more stuff I actually wanne do
                    }
                }
            }
        }
    }
}

No tengo mucha experiencia con el patrón de comando. El único punto negativo obvio que veo es que apenas es legible (tenga en cuenta el nivel de sangría), por lo que no lo usaré porque me gusta cuidar de las personas que leerán mi código en el futuro.
Visto el

Tampoco execute()haga nada por el momento, ya que solo está instanciando MyLoggingCommandWhole.
Visto el

@Spotted: pensé que MyLoggingCommandWhole, executeWithoutLogging();desde la ejecución , se ejecuta MyLoggingCommandWhole. No es ese el caso?
Make42

No, no es el caso. Tendrías que escribir algo como:MyLoggingCommandWhole cmd = new MyLoggingCommandWhole() { ... }; cmd.executeWithoutLogging();
Visto el

@Spotted: Eso difícilmente tendría sentido. Si es como usted dice, prefiero poner el código del constructor en una función como executeWithLogging()y cmd.executeWithLogging(). Después de todo, eso es lo que debería ejecutarse.
Make42
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.