Padrão de design para agrupar o log em torno da execução

11

Introdução

Estou implementando uma classe Java abstrata de uma estrutura de processamento *. Ao implementar a função execute, sou capaz de adicionar funcionalidade lógica de negócios. Gostaria de adicionar o registro no início e no final de cada implementação de todas as minhas funções de execução. Também entre alguns logs, se coisas específicas forem feitas. No entanto, eu gostaria de fazer isso de maneira uniforme. Meu pensamento era herdar a classe de estrutura para uma nova classe que implementa o log e fornece algumas novas executeWithLoggingfunções, que estão envolvendo o log em partes específicas. Não tenho certeza se essa é a melhor ideia e se posso usar um padrão de design que tornaria todo o empreendimento elegante. Como eu continuaria com a coisa toda?

Desafios (considere estes!)

  1. Um desafio no meu caso é que a classe original tem apenas uma executefunção; no entanto, eu precisaria registrar várias partes.
  2. A segunda coisa é: Usar um padrão decorador (também foi minha primeira ideia) não funciona muito bem, se eu estiver usando também um execute, porque a super.execute()função-teria que ser chamada na primeira linha do meu novo execute(), não é? ?
  3. Haveria pelo menos 4 classes envolvidas: BaseFunctionda estrutura, LoggingFunction extends BaseFunctionde mim, MyBusinessFunction extends LoggingFunctionde mim, MyBusinessClassque instancia MyBusinessFunction.
  4. Não só preciso fazer logon no início e no final de execute, mas também no meio.
  5. O "registro" não é apenas o registro Java simples, mas o registro em um banco de dados. Isso não muda nada sobre os princípios, mas demonstra que o log pode ser mais do que apenas uma linha de código.

Talvez um exemplo de como eu faria a coisa toda seria legal, para eu continuar.

| * Uma função Storm Trident, semelhante a um parafuso Storm, mas isso não é particularmente importante.

Make42
fonte
1
super.execute é chamado pelo decorador, não pelo decorado. Sua classe principal nem deve perceber que está sendo decorada e você só precisa ter cuidado com essas chamadas, pois elas não podem incluir o decorador (em oposição a uma abordagem com mixins ou características).
Frank
É executeem BaseFunctionabstrato?
Visto
@ Spotted: Sim, executeé abstrato em BaseFunction.
precisa saber é o seguinte

Respostas:

13

Geralmente, existem várias maneiras possíveis de executar o log em torno de uma determinada execução. Antes de tudo, é bom que você queira separar isso, pois o log é um exemplo típico de Separação de preocupações . Realmente não faz muito sentido adicionar logs diretamente à sua lógica de negócios.

Quanto aos possíveis padrões de design, aqui está uma lista (não conclusiva) do topo da minha cabeça:

  • Padrão do decorador : um padrão de design conhecido, no qual você basicamente agrupa a classe não registrada em outra classe da mesma interface e o wrapper faz o log antes e / ou depois de chamar a classe agrupada.

  • Composição da função : Em uma linguagem de programação funcional, você pode simplesmente compor sua função com uma função de registro. Isso é semelhante ao padrão decorador nas linguagens OO, mas na verdade não é uma maneira preferida, uma vez que a função de log composta seria baseada em uma implementação de efeito colateral.

  • Mônadas : as mônadas também são do mundo da programação funcional e permitem desacoplar sua execução e registro. Isso basicamente significa que você agrega as mensagens de log e os métodos de execução necessários, mas ainda não os executa. Em seguida, você pode processar a mônada para executar a gravação de log e / ou a execução real da lógica de negócios.

  • Programação orientada a aspectos : abordagem mais sofisticada, que obtém separação completa, pois a classe que faz o comportamento de não registrar nunca interage diretamente com o registro.

  • Retransmissão: Outros padrões de design padrão também podem ser adequados, por exemplo, uma Fachada pode servir como ponto de entrada registrado, antes de encaminhar a chamada para outra classe que executa a parte da lógica de negócios. Isso também pode ser aplicado a diferentes níveis de abstração. Por exemplo, você pode registrar solicitações feitas em um URL de serviço alcançável externamente, antes de retransmitir para um URL interno para processamento real não solicitado de solicitações.

Quanto ao requisito adicional de que você precisa executar o registro "no meio" - isso provavelmente está apontando para um design estranho. Por que sua execução está fazendo tanto, que algo como um "meio" de sua execução é remotamente interessante? Se realmente existem tantas coisas acontecendo, por que não agrupá-las em etapas / fases / o que você tem? Em teoria, você poderia logar no meio com a AOP, mas ainda assim argumentaria que uma alteração no design parece ser muito mais apropriada.

Frank
fonte
3

Tenho a sensação de que você deseja absolutamente usar um padrão. Lembre-se de que um mau uso de padrões de design pode resultar em um código menos sustentável / legível.

Isso sendo dito ...

Seu caso é complicado, pois parece que você deseja fazer dois tipos de registro:

  1. Registrando algumas etapas da sua funcionalidade lógica (interna execute)
  2. Registrando chamadas de função (entrando / saindo de uma função) (fora execute)

Para o primeiro caso, no momento em que você deseja registrar algumas coisas dentro de uma função, você deve fazê-lo ... dentro da função. Você pode usar o padrão do método template para tornar as coisas um pouco mais bonitas, mas acho que é um exagero nesse 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 o segundo caso, o padrão do decorador é o caminho 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");
    }
}

E você pode usá-lo assim em BusinessClass:

public final BusinessClass {
    private final BaseFunction f1;

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

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

Uma chamada para doFunctionregistrará isso:

Entering execute
Initializing some variables
Starting algorithm
One step forward
One step forward
...
Ending algorithm
Cleaning some mess
Exiting execute
Visto
fonte
Funcionaria para omitir o atributo de origem e escrever em super.execute();vez de origin.execute();?
precisa saber é o seguinte
Sua solução não está completa - talvez devido à minha explicação incorreta. Vou adicionar informações na pergunta.
precisa saber é o seguinte
@ user49283 Por que você deseja chamar super.execute()como é esse método abstract?
manchado
@ user49283 Eu também atualizei a minha resposta
manchado
Ainda não entendi bem o padrão do modelo, mas em breve vou tentar outro. Enquanto isso: Que tal usar o Padrão de Comando? Eu dei uma resposta usando um padrão de comando - por favor, comente!
Make42
1

Sua abordagem parece válida e é realmente uma implementação do Padrão Decorador . Suponho que há outras maneiras de fazer isso, mas o Decorator é um padrão muito elegante e fácil de entender que é muito adequado para resolver seu problema.

JDT
fonte
0

Que tal usar o Padrão 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
                    }
                }
            }
        }
    }
}
Make42
fonte
Não tenho muita experiência com o padrão de comando. O único ponto negativo óbvio que vejo é que é dificilmente legível (observe o nível de indentação), então não o usarei porque gosto de cuidar das pessoas que lerão meu código no futuro.
Visto
Também execute()não faça nada no momento, pois você está apenas instanciando MyLoggingCommandWhole.
Visto
@ Spotted: Eu pensei que instaciar MyLoggingCommandWhole, executa a executeWithoutLogging();partir de MyLoggingCommandWhole. Não é esse o caso?
Make42
Não, não é o caso. Você teria que escrever algo como:MyLoggingCommandWhole cmd = new MyLoggingCommandWhole() { ... }; cmd.executeWithoutLogging();
Manchou
@ Spotted: Isso dificilmente faria algum sentido. Se é como você diz, eu prefiro colocar o código do construtor em uma função como executeWithLogging()e cmd.executeWithLogging(). Afinal, é isso que deve ser executado.
Make42