DEV Community

Hugo Marques
Hugo Marques

Posted on

Template Method com composição

Eu acho que esse vai ser curto, vamos lá! Hoje mais cedo eu fiz o seguinte comentário sobre herança no finado Twitter.

Image description

Imediatamente, surgiram alguns comentários interessantes, mas um dele me chamou a atenção. O amigo @DevCorinthiano777 perguntou:

Image description

E com isso, automaticamente as engrenagens internas aqui na minha cabeça começaram a funcionar 🤔.

Mas o que danado é um Template Method?

Sendo super sucinto, o Template Method é um "design pattern" (ou padrão de projeto em PT-br), que define uma "receita de bolo". Essa receita é definida na super-classe e a partir daí as classes filhas podem implementar passos específicos dessa "receita". Isso fica mais claro no exemplo abaixo:

Disclaimer: Eu sou um dev cansado, usei o gepeto pra me ajudar a escrever o exemplo rápido.

abstract class ReviewTemplate {
    private String reviewText;

    public ReviewTemplate(String reviewText) {
        this.reviewText = reviewText;
    }

    // Método de template que orquestra o processo de validação e salvamento
    public void processReview() {
        if (validateReview()) {
            saveReview();
            System.out.println("Revisão salva com sucesso.");
        } else {
            System.out.println("A revisão não é válida e não foi salva.");
        }
    }

    // Método abstrato que deve ser implementado pelas subclasses para a validação
    protected abstract boolean validateReview();

    // Método comum para salvar a revisão (implementação padrão)
    private void saveReview() {
        System.out.println("Revisão salva no banco de dados: " + reviewText);
    }
}

class CriarReview extends ReviewTemplate {
    public CriarReview(String reviewText) {
        super(reviewText);
    }

    @Override
    protected boolean validateReview() {
        // Implementação específica de validação para criar uma revisão
        return reviewText != null && !reviewText.isEmpty();
    }
}

class AtualizarReview extends ReviewTemplate {
    public AtualizarReview(String reviewText) {
        super(reviewText);
    }

    @Override
    protected boolean validateReview() {
        // Implementação específica de validação para atualizar uma revisão
        return reviewText != null && !reviewText.isEmpty();
    }
}

public class Main {
    public static void main(String[] args) {
        // Criar uma nova revisão
        ReviewTemplate criarReview = new CriarReview("Esta é uma ótima revisão.");
        criarReview.processReview();

        // Atualizar uma revisão existente
        ReviewTemplate atualizarReview = new AtualizarReview("Versão atualizada da revisão.");
        atualizarReview.processReview();
    }
}
Enter fullscreen mode Exit fullscreen mode

Observe como o review template define um padrão de como todas as suas filhas são tratadas. A partir daí as classes filhas só seguem esse padrão, implementando pedaços do algoritmo e expondo apenas o método público processReview()

Entendi, mas se a implementação usa herança, como tu vai fazer pra usar composição?

Simples, aí que entra a mágica da injeção de dependências. Olha que bacana:

interface ReviewValidator {

    public boolean validateReview();

}

class CriarReviewValidator implements ReviewValidator {
    private String reviewText;

    public ReviewValidator(String reviewText) {
        this.reviewText = reviewText;
    }

    public boolean validateReview() {
        // Lógica de validação comum para todas as revisões
        return reviewText != null && !reviewText.isEmpty();
    }
}

class AtualizarReviewValidator implements ReviewValidator {
    private String reviewText;

    public ReviewValidator(String reviewText) {
        this.reviewText = reviewText;
    }

    public boolean validateReview() {
        // Lógica de validação comum para todas as revisões
        return reviewText != null && !reviewText.isEmpty();
    }
}

class ReviewWriter {
    private ReviewValidator validator;

    public ReviewWriter(ReviewValidator validator) {
        this.validator = validator;
    }

    public void writeReview() {
        if (validator.validateReview()) {
            saveReview();
            System.out.println("Revisão salva com sucesso.");
        } else {
            System.out.println("A revisão não é válida e não foi salva.");
        }
    }

    private void saveReview() {
        System.out.println("Revisão salva no banco de dados: " + validator.reviewText);
    }
}

public class Main {
    public static void main(String[] args) {
        // Criar uma nova revisão
        ReviewWriter criarReview = new ReviewWriter(new CriarReviewValidator("Essa não é uma boa review"));
        criarReview.writeReview();

        // Atualizar uma revisão existente
        ReviewWriter atualizarReview = new ReviewWriter(new AtualizarReviewValidator("Versão atualizada da revisão."));
        atualizarReview.writeReview();
    }
}
Enter fullscreen mode Exit fullscreen mode

Viu como dá pra fazer o mesmo comportamento usando apenas composição de objetos?

Tá blz, entendi. Mas e qual a vantagem disso?

Com herança, você tem um alto acoplamento entre a super-classe e suas filhas. Se você faz uma mudança na super-classe, todas as classes filhas são afetadas quer você queira ou não.

Com composição, os comportamentos ficam todos mais isolados. É possível criar novos comportamentos e inseri-los ou removê-los conforme você desejar e você não corre o risco de afetar os validators caso você altere o ReviewWriter.

Conclusão

Esse post foi escrito de forma super rápida, só porque eu acho o assunto interessante e fazia muito tempo que eu tinha postado algo com código. Eu espero que vocês tenham curtido o assunto.

Se foi o caso, deixa aí o like e comentário que isso me incentiva a escrever mais sobre esse tipo de assunto com código.

Top comments (4)

Collapse
 
murilocb123 profile image
MURILO COSTA BITTENCOURT

Opa, topzera o artigo, já até salvei aqui nos favoritos, só encontrei um pequeno detalhe, gepeto te pregou uma peça. No construtor do ReviewWriter você colocou uma String e no main voce passa um ReviewWriter criarReview = new ReviewWriter(new CriarReviewValidator("Essa não é uma boa review")); teria que ser a interface como o tipo de objeto esperado né ?

Collapse
 
hugaomarques profile image
Hugo Marques

aha, excelente! Obg pela contribuição Murilo, eu dei uma corrigida :)

Collapse
 
fernandocristan profile image
Fernando Rodrigues Cristan

Uso herança somente nos payloads de cruds com muitas propridades. Aí crio um validator para a classe base e no validator do insert e update fica as validações únicas.

O que acha desse cenário?

Collapse
 
hugaomarques profile image
Hugo Marques

Teve um mano que falou que gostava dessa abordagem, de usar herança pra payloads, etc...

Eu nunca usei, mas tlvz dê certo. Se for algo que o a herança fica lisa sem muitas camadas e que a base tenha estritamente o que é comum, me parece uma boa idéia.