Um conselho...


Codifique pensando que outras pessoas devem entender seu código, 
por mais que ninguém vá ver, 
pois isso aumenta consideravelmente as chances de no futuro 
você mesmo entender esse código.


Refatoração na prática


"Pensar é o trabalho mais pesado que existe. 
Talvez seja por isso que tão poucos o exercitem."
(Henry Ford)

Ser pragmático tem seu valor. Sempre me importei com o código, gosto de ser assim, pois conheço o real valor dessa ação, ou melhor, dessa conduta. Ser pragmático envolve muitos conceitos e acima de tudo, prática. Nesse post quero demonstrar uma prática muito conhecida de quem é pragmático que é a refatoração de código.

Refatorar código não é tão simples assim, exige conhecimento da lógica de negócio e pode ter resultados catastróficos se for feito por alguém que não possua uma visão macro do projeto e possa visualizar todas as dependências possíveis (lembrando que mesmo com os mecanismos de busca das IDEs muita informação está em locais inacessíveis para estas).



O código a seguir envolve um caso onde um controller é responsável por receber dados para o envio de um e-mail. Esse controller deve validar os dados de entrada, montar o e-mail e enviá-lo. Vou mostrar o código “cru” e então começarei a refatorá-lo. Passo a passo pretendo descrever conceitos que estão sendo atendidos e outros que possivelmente estão sendo violados. Será utilizada a linguagem Java para isso.

Case 1
import java.util.Date;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case1 {

    public void abstractController() throws Exception {
        
        try {
            // dados recebidos de uma fonte qualquer
            String de = "eu@email.com";
            String para = "qualquer@email.com";
            String assunto = "Refactoring na Prática";
            String corpo = "Corpo da Mensagem";
            
            // validacao de email
            Pattern p = Pattern.compile(".+@.+\\.[a-z]+");  
            
            Matcher m1 = p.matcher(de);  
            if (!m1.matches())  
                throw new IllegalArgumentException("Email address is invalid: \"" + de + "\"");
            
            Matcher m2 = p.matcher(para);  
            if (!m2.matches())  
                throw new IllegalArgumentException("Email address is invalid: \"" + para + "\"");
            
            Properties props = new Properties();
            props.put("mail.host", "127.0.0.1");
            Session session = Session.getInstance(props);
            MimeMessage msg = new MimeMessage(session);
            try {
                msg.setFrom(new InternetAddress(de));
                InternetAddress[] to = {new InternetAddress(para)};
                msg.setRecipients(Message.RecipientType.TO, to);
                msg.setSentDate(new Date());
                msg.setSubject(assunto);
                msg.setText(corpo);
                Transport.send(msg);
            } catch (MessagingException e) {
                // omitido
            }
        } catch (Exception e) {
            // omitido
        }
        
    }

}
A classe Case1 é o primeiro caso que quero mostrar. Em seu método abstractController ela executa a lógica de receber os atributos que identificam um e-mail, validar alguns deles, montar o e-mail utilizando a biblioteca javax.mail, então por fim, enviar o e-mail. Um método com muitas responsabilidades.

Isso funciona?

Sim (se você tiver o SMTP configurado em sua máquina como um servidor local). Acredito que nesse ponto se você não for alguém pragmático esse código é algo normal, mas para mim é um monstro. Quando estudei software a primeira coisa que aprendi lá em Algoritmos é que devo pegar um problema e fragmentá-lo, então resolver os problemas menores sendo que ao final terei resolvido o problema todo. O código acima para mim é um problema e que necessita de fragmentação.

Case 2

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case2 {
    
    /**
     * Refactoring:
     * - Criacao da classe EmailAddress
     */
    public void abstractController() {
        
        EmailAddress de = new EmailAddress("eu@email.com");
        EmailAddress para = new EmailAddress("qualquer@email.com");
        
        String assunto = "Refactoring na Prática";
        String corpo = "Corpo da Mensagem";
        
        Properties props = new Properties();
        props.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(props);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(de.toInternetAddress());
            InternetAddress[] to = {para.toInternetAddress()};
            msg.setRecipients(Message.RecipientType.TO, to);
            msg.setSentDate(new Date());
            msg.setSubject(assunto);
            msg.setText(corpo);
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}
Nesse ponto do código, uma classe extra foi criada com a intenção de centralizar o e-mail bem como sua validação. A classe EmailAddress tem a responsabilidade de fazer apenas isso, representar um e-mail. Vale lembrar que em um refactoring não se pode alterar o fluxo que a aplicação já possui, pois isso seria tarefa em uma correção ou manutenção. A classe EmailAddress segue o padrão Tiny Type, que são pequenos tipos de objeto que encapsulam pouca lógica.

EmailAddress

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

/**
 * Tiny Type Pattern
 *
 * @author Vinicius Martins Knob
 */
public final class EmailAddress {

    private final InternetAddress _emailAddress;

    /**
     * Cria um EmailAddress a partir de um email
     * @param emailAddress
     * @throws AddressException
     */
    public EmailAddress(String emailAddress) throws AddressException {
        validar(emailAddress);
        _emailAddress = new InternetAddress(emailAddress);
    }

    private void validar(String emailAddress) {
        Pattern p = Pattern.compile(".+@.+\\.[a-z]+");  
        Matcher m = p.matcher(emailAddress);  
        if (!m.matches())  
            throw new IllegalArgumentException("Email address is invalid: \"" + emailAddress + "\"");
    }
    
    @Override
    public String toString() {
        return _emailAddress.getAddress();
    }
    
    public InternetAddress toInternetAddress(){
        return _emailAddress;
    }
    
    /**
     * Retorna uma lista imutavel de EmailAddress a partir de um array de emails.
     * @param emails
     * @return
     * @throws AddressException
     */
    public static List<EmailAddress> asList(String... emails) throws AddressException {
        if (emails.length == 0)
            return Collections.emptyList();
        
        List<EmailAddress> foo = new ArrayList<EmailAddress>();
        for (String bar : emails) {
            foo.add(new EmailAddress(bar.trim()));
        }
        return Collections.unmodifiableList(foo);
    }
}
O que ganho com isso?

Em um projeto grande, ter aquele trecho de código responsável por validar o e-mail espalhado por toda parte seria inadmissível. Uma alternativa encontrada muitas vezes por mim em outros projetos é uma classe com métodos estáticos onde exista um método validarEmail(), não que seja errado, mas quando criei a classe EmailAddress estive pensando em conceitos muito discutidos pela comunidade de software como coesão, responsabilidade única, legibilidade e baixo acoplamento. EmailAddress é uma classe que quando você vê tem plena noção de seus limites e responsabilidades, pois ela é simples e clara.

Case 3
import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case3 {

    /**
     * Refactoring:
     * - Criacao da classe Email
     */
    public void abstractController() {

        EmailAddress de = new EmailAddress("eu@email.com");
        EmailAddress para = new EmailAddress("qualquer@email.com");

        Email email = Email.create()
                .from(de)
                .to(para)
                .subject("Refactoring na Prática")
                .body("Corpo da Mensagem")
            .build();

        Properties props = new Properties();
        props.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(props);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(de.toInternetAddress());
            InternetAddress[] to = {para.toInternetAddress()};
            msg.setRecipients(Message.RecipientType.TO, to);
            msg.setSentDate(new Date());
            msg.setSubject(email.getSubject());
            msg.setText(email.getBody());
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}
No caso 3 externalizei uma segunda parte da lógica, a montagem do e-mail. A montagem consiste na coleta dos dados e suas respectivas validações. Para isso criei a classe Email. Ela segue o padrão Builder.

Email

import java.text.MessageFormat;
import java.util.List;

/**
 * 
 * Esta classe segue o Builder Pattern
 *
 * @author Vinicius Martins Knob
 */
public class Email {
    
    private EmailAddress _from;
    private List<EmailAddress> _to;
    private String _subject;
    private String _body;
    
    private Email() {}
    
    public EmailAddress getFrom() {
        return _from;
    }
    public List<EmailAddress> getTo() {
        return _to;
    }
    public String getSubject() {
        return _subject;
    }
    public String getBody() {
        return _body;
    }
    
    @Override
    public String toString() {
        String pattern = "[from: {0}, to: {1}, subject: {2}, body: {3}]";
        Object[] data = {_from, _to.toString(), _subject, _body};
        return MessageFormat.format(pattern, data);
    }
    
    @Override
    public boolean equals(Object obj) {
        return this.toString().equals(obj.toString());
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result * (_from==null?0:_from.hashCode());
        result = 31 * result * (_to==null?0:_to.hashCode());
        result = 31 * result * (_subject==null?0:_subject.hashCode());
        result = 31 * result * (_body==null?0:_body.hashCode());
        return result;
    }
    
    /**
     * 
     * Builder Method - Builder Pattern
     * 
     */
    public static EmailBuilder create() {
         return new EmailBuilder();
    }
    
    /**
     * 
     * Builder Class - Builder Pattern
     * <br>Inner Class
     *
     */
    public static class EmailBuilder{
        private final Email _email = new Email();
        
        private EmailBuilder(){}
        
        public EmailBuilder from(EmailAddress from){
            _email._from = from;
            return this;
        }
        public EmailBuilder to(List<EmailAddress> to){
            _email._to = to;
            return this;
        }
        public EmailBuilder subject(String subject){
            _email._subject = subject;
            return this;
        }
        public EmailBuilder body(String body){
            _email._body = body;
            return this;
        }
        public Email build(){
            return _email;
        }
        
    }
    
}
A classe Email contempla o padrão Builder, padrão aplicado em virtude da quantidade de parâmetros utilizados, e a tendência dessa classe, dependendo do sistema, é aumentar a quantidade desses parâmetros. Cada método poderia conter a validação respectiva ao dado inserido, centralizando toda a lógica apenas em uma classe, para todo sistema. A classe Email faz a coleta e validação de dados de um e-mail, e só isso. Os atributos atendidos pela classe EmailAddress são também atendidos aqui: coesão, responsabilidade única, legibilidade e baixo acoplamento.

Case 4

import java.util.Arrays;

public class Case4 {
    
    /**
     * Refactoring: 
     * - Criacao da classe Sender
     */
    public void abstractController() {
        try {
            EmailAddress de = new EmailAddress("eu@email.com");
            EmailAddress para = new EmailAddress("qualquer@email.com");

            Email email = Email.create()
                    .from(de)
                    .to(Arrays.asList(para))
                    .subject("Refactoring na Prática")
                    .body("Corpo da mensagem")
                .build();

            Sender.send(email);
        } catch (Exception e) {
            // log
        }

    }
}

No caso 4, uma terceira lógica foi movida do controller para uma classe específica, a classe Sender. Essa classe passa a ser responsável pelo envio do e-mail, e somente isso. Ela encapsula boa parte da API de envio de e-mail do java (javax.mail) e possui apenas um método estático, send(email). O parâmetro de send é um objeto do tipo Email, a classe criada no caso 3. O contrutor de Sender lança uma UnsupportedOperationException, pois não existe motivos para instanciar essa classe, ela foi projetada para apenas enviar um e-mail, não armazenando dado algum. O seu método private getArrayAddress efetua uma conversão da lista de EmailAddress para um array de InternetAddress, exigência esta imposta pela API javax.mail.

Sender

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.naming.NamingException;

/**
 * <p>
 * Classe responsavel por enviar um email.
 * </p>
 * <p>
 * Classe nao-instanciavel, seu unico proposito eh enviar um email,
 * funcionalidade essa fornecidade pelo metodo {@link #send(Email)}.
 * </p>
 * <p>
 * <b>OBS:</b> Classe nao projetada para heranca.
 * </p>
 * 
 * @author Vinicius Martins Knob
 * 
 */
public final class Sender {
    
    private Sender() {
        throw new UnsupportedOperationException("classe nao-instanciavel");
    }
    
    public static void send(Email email) throws NamingException {
        Properties p = new Properties();
        p.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(p);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(email.getFrom().toInternetAddress());
            msg.setRecipients(Message.RecipientType.TO, getArrayAddress(email.getTo()));
            msg.setSentDate(new Date());
            msg.setSubject(email.getSubject());
            msg.setText(email.getBody());
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
    
    private static InternetAddress[] getArrayAddress(List<EmailAddress> emails){
        List<InternetAddress> internetAddresses = new ArrayList<InternetAddress>();
        for (EmailAddress address : emails) {
            internetAddresses.add(address.toInternetAddress());         
        }
        return internetAddresses.toArray(new InternetAddress[internetAddresses.size()]);
    }
}

Conclusão

Acredito que com essa refatoração o controller ficou muitas vezes mais legível, fácil de efetuar uma alteração já que temos as partes do processo bem definidas. As classes geradas por esta refatoração são totalmente reutilizáveis, flexíveis e coesas. Nem sempre é possível efetuar uma refatoração de código, pois esta ação exige conhecimento amplo. Um ambiente preparado para testes seria ideal para manter uma rotina de refatoração diminuindo significamente partes repetidas de um sistema que utiliza uma linguagem orientada a objetos. Conceitos aplicados aqui, e muitos deles posso ter passado por cima, deveria sempre ser aplicados, desde o inicio.

Use isEmpty em vez de equals


 "A perfeição é feita de pequenos detalhes - não é apenas um detalhe." (Michelangelo)

Gostaria de começar esse post ressaltando a famosa frase de Michelangelo ditada acima que fala sobre "detalhes". Em minha busca por conhecimento dedico meu tempo justamente a isso, detalhes. Não basta para mim ter a solução em mãos, é preciso mais, é preciso saber como chegar até esta solução, cada detalhe da implementação de um algoritmo é o que o faz ter usabilidade, flexibilidade e clareza, porém nesse post quero tratar de algo mais interessante, um detalhe que envolve desempenho.

Assim como qualquer linguagem, Java tem seus detalhes de implementação que gosto de explorar. Nesse post quero mostrar o porque deve ser usado isEmpty() em vez de equals(""). Primeiramente vamos a documentação de ambos os métodos:

isEmpty - "Returns true if, and only if, length() is 0".
equals - "Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object".

Pela documentação de cada método já temos uma simples noção de implementação onde que isEmpty utiliza um atributo privado para verificar se é igual a zero, apenas isso enquanto equals utiliza verificação de argumento nulo e sequencia de caracteres, o que com certeza é uma implementação mais penosa para um caso tão simples como a verificação de uma string vazia. Equals abrange verificação para casos mais complexos. Vamos ao código de cada método:

isEmpty
public boolean isEmpty() {
    return count == 0;
}
equals
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}

Como esperado, o método equals é dedicado a comparação de strings diversas, enquanto o método isEmpty ataca um problema específico onde posso apenas querer saber se essa string esta vazia ou não.

Desempenho


Para o teste de desempenho fiz dois testes com jUnit, cada um atacando um dos métodos. Cada teste possui um loop de 100 milhões de iterações mostrando uma diferença de tempo considerável. Acredito que grandes empresas com sites que superam acessos de 1 milhão de pessoas rodando diversas classes contendo diversas vezes uma comparação utilizando equals("") resulte em algo semelhante, senão pior.

public class StringEmptyTest {
    
    @Test
    public void isEmpty() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.isEmpty();
        }
    }
    
    @Test
    public void equals() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.equals("");
        }
    }

}


No resultado, isEmpty() foi 5,6 vezes menor em seu tempo de execução.

Conclusão


Quando Michelangelo cunhou sua frase, assim como tantas outras, o contexto era outro obviamente, mas programar não deixa de ser uma arte e assim como no contexto de Michelangelo, nós no nosso contexto precisamos entender o quanto um detalhe é importante. Com certeza utilizar isEmpty ou equals em um programa com poucos acessos não fará diferença, o que pretendo dizer com esse post é justamente o que a frase de Michelangelo diz "A perfeição é feita de pequenos detalhes...", junte esse detalhe, mais aquele outro e mais um e terá uma melhoria de desempenho. 

HashCode.java: uma alternativa

Em uma das minhas últimas aquisições obtive o livro Java Efetivo de Joshua Bloch, uma referência para quem conhece Java e quer melhorar seu entendimento sobre detalhes da linguagem. O item 9 do 3º capítulo é um tanto interessante, explicando regras de sobreposição do método hashCode. Não vou transcrever aqui o que fala o livro, mas quero deixar uma alternativa que acho interessante para geração de hashCodes quando ocorre a sobreposição do método levando em consideração o que fala no livro. Conceitos como flexibilidade e manutenibilidade e até o princípio da Responsabilidade Única foram levados em consideração, já no quesito desempenho será apresentado uma alternativa.

O livro fala sobre como se deve implementar a conversão de atributos de uma classe em hashCode, o cálculo e o tratamento, mas sempre aprendi que a lógica de uma parte quando pode viver isolada deve ser isolada, isso aumenta a flexibilidade do sistema e consequentemente a manutenibilidade, com isso, a alternativa que apresento é uma classe HashCode contendo toda a lógica para anexar os atributos e obter um hashCode. Segue a classe:

public final class HashCode {
    
    private HashCode() {}
    
    public static HashCode create() {
        return new HashCode();
    }
    private int _result = 17;
    
    public HashCode append(int value) {
        _result = 31 * _result + value;
        return this;
    }
    public HashCode append(boolean value) {
        return append(value?1:0);
    }
    public HashCode append(byte value) {
        return append((int)value);
    }
    public HashCode append(char value) {
        return append((int)value);
    }
    public HashCode append(short value) {
        return append((int)value);
    }
    public HashCode append(long value) {
        return append((int)(value^(value>>>32)));
    }
    public HashCode append(float value) {
        return append(Float.floatToIntBits(value));
    }
    public HashCode append(double value) {
        return append(Double.doubleToLongBits(value));
    }
    public HashCode append(Object value) {
        return append(value==null?0:value.hashCode());
    }
    @Override
    public int hashCode() {
        return _result;
    }
}

Perceba que utilizei alguns outros conceitos como um construtor estático, sobrecarga e fluent interfaces. A classe é final, pois acredito que ela não deva ser estendida e o próprio método hashCode de Object é sobrescrito aqui para retornar o resultado final.

Usabilidade


No caso de atributos onde não exista uma coleção o código ficaria como mostrado no exemplo abaixo.

@Override 
public int hashCode() {
    return HashCode.create()
            .append(_name) // String
            .append(_url) // URL
            .append(_descriptiveStatus) // String
            .append(_timer) // long
            .append(_effect) // boolean
            .hashCode();
}

Já no caso de uma coleção, preferi não implementar nada, pois o que deve ser levado em conta numa coleção somente cada programador saberá, lembrando que no caso de arrays a classe Arrays possui vários métodos para geração de hashCodes, vale a pena conferir. Quando existir uma coleção, prefiro iterar sobre ela e obter os atributos que considero melhor. Abaixo um exemplo:

@Override
public int hashCode() {
    HashCode result = HashCode.create();
    result.append(_nameBedroom); // String
    Enumeration<String> keys = _bedroom.keys();
    while (keys.hasMoreElements()) {
        String key = (String) keys.nextElement();
        result.append(_bedroom.get(key).hashCode()); // int
    }
    return result.hashCode();
}

Desempenho


Para testar o desempenho do código com e sem a classe HashCode fiz dois testes com jUnit, ambos efetuam o mesmo calculo, cada um sobre um for de 100 milhões de loops que proporciona uma visão mais humana, um dos testes não utiliza a classe HashCode, o outro sim.


Percebe-se a diferença de tempo, quase o dobro do tempo para executar o mesmo cálculo quando se utiliza  a classe HashCode. This is bad!!!

public class HashCodeTest {
    
    String nome = "Vinicius Knob";
    int anoNascimento = 1989;
    long dataHoje = Calendar.getInstance().getTimeInMillis();
    boolean java = true;

    @Test
    public void testSemClasse() {
        for (int i = 0; i <= 100000000; i++) {
            int result = 17;
            result = 31 * result + nome.hashCode();
            result = 31 * result + anoNascimento;
            result = 31 * result + (int)(dataHoje^(dataHoje>>>32));
            result = 31 * result + (java?1:0);
        }
    }
    
    @Test
    public void testComClasse() {
        for (int i = 0; i <= 100000000; i++) {
            HashCode.create().append(nome)
                .append(anoNascimento).append(dataHoje)
                .append(java).hashCode();
        }
    }

}

Em um sistema onde o método hashCode é muito utilizado e/ou possui um cálculo custoso é necessário fazer uma escolha: não utilizar a classe HashCode ou utilizar o conceito de cache e armazenar na própria classe consumidora de HashCode um atributo contendo o hashCode gerado na primeira execução. Isso ajudaria muito, pois somente na primeira vez o hashCode seria gerado, enquanto nas próximas apenas consumido. Cuidado, caches desse tipo não são uma boa alternativa quando se trata de uma classe mutável, já que o hashCode pode estar baseado em atributos que são alterados entre uma solicitação e outra do método hashCode. Desempenho sempre é uma questão delicada.


Considerável ganho de desempenho quando utilizado cache e uma diferença de 3ms quando utilizado a classe HashCode.

public class HashCodeTest {
    
    String nome = "Vinicius Knob";
    int anoNascimento = 1989;
    long dataHoje = Calendar.getInstance().getTimeInMillis();
    boolean java = true;

    @Test
    public void testSemClasse() {
        int cacheResult = 0;
        for (int i = 0; i <= 100000000; i++) {
            if (cacheResult == 0) {
                int result = 17;
                result = 31 * result + nome.hashCode();
                result = 31 * result + anoNascimento;
                result = 31 * result + (int)(dataHoje^(dataHoje>>>32));
                result = 31 * result + (java?1:0);
                cacheResult = result;
            }
        }
    }
    
    @Test
    public void testComClasse() {
        int cacheResult = 0;
        for (int i = 0; i <= 100000000; i++) {
            if (cacheResult == 0) {
                cacheResult = HashCode.create().append(nome)
                    .append(anoNascimento).append(dataHoje)
                    .append(java).hashCode();
            }
        }
    }

}

Conclusão


Essa alternativa atende alguns requisitos para proporcionar flexibilidade e manutenibilidade ao código, porém seria interessante fornecer um atributo para servir como cache em casos onde cálculos exigem muito processamento, mesmo sem utilizar a classe. Alguns críticos podem considerar ela totalmente desnecessária, eu vejo como uma classe encapsuladora de um padrão documentado e que tecnicamente poderia ser seguido dessa forma. Alternativas para essa classe poderia proporcionar melhoria a flexibilidade ou até ao desempenho, uma estrutura diferente com nomes diferentes também é aceitável, mas tudo depende sempre de conhecimento, o que busco incansavelmente.