Construtores Java vs Métodos estáticos de fábrica

Construtores Java vs Métodos estáticos de fábrica

1. Visão geral

Construtores Java são o mecanismo padrão para obter instâncias de classe totalmente inicializadas. Afinal, eles fornecem toda a infraestrutura necessária para injetar dependências, manual ou automaticamente.

Mesmo assim, em alguns casos de uso específicos, é preferível recorrer a métodos de fábrica estáticos para obter o mesmo resultado.

Neste tutorial, vamos destacar opros and cons of using static factory methods vs plain old Java constructors.

2. Vantagens de Métodos estáticos de fábrica sobre construtores

Em uma linguagem orientada a objetos como Java, o que poderia estar errado com os construtores? No geral, nada. Mesmo assim, o famosoJoshua Block’s Effective Java Item 1 afirma claramente:

“Considere métodos de fábrica estáticos em vez de construtores”

Embora isso não seja uma solução mágica, aqui estão os motivos mais convincentes que sustentam essa abordagem:

  1. Constructors don’t have meaningful names, então eles estão sempre restritos à convenção de nomenclatura padrão imposta pelo idioma. Static factory methods can have meaningful names, portanto, explicitamente transmitindo o que eles fazem

  2. Static factory methods can return the same type that implements the method(s), a subtype, and also primitives, então eles oferecem uma gama mais flexível de tipos de retorno

  3. Static factory methods can encapsulate all the logic required for pre-constructing fully initialized instances, então eles podem ser usados ​​para mover essa lógica adicional para fora dos construtores. Isso evita que os construtores deperforming further tasks, others than just initializing fields

  4. Static factory methods can be controlled-instanced methods, comSingleton pattern sendo o exemplo mais flagrante deste recurso

3. Métodos de fábrica estáticos no JDK

Existem muitos exemplos de métodos estáticos de fábrica no JDK que mostram muitas das vantagens descritas acima. Vamos explorar alguns deles.

3.1. A classeString

Por causa do conhecidoString interning, é muito improvável que usemos o construtor de classeString para criar um novo objetoString. Mesmo assim, isso é perfeitamente legal:

String value = new String("example");

Nesse caso, o construtor criará um novo objetoString, que é o comportamento esperado.

Alternativamente, se quisermoscreate a new String object using a static factory method, podemos usar algumas das seguintes implementações do métodovalueOf():

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

Existem várias implementações sobrecarregadas devalueOf(). Cada um retornará um novo objetoString, dependendo do tipo de argumento passado para o método (por exemplo int,long,boolean,char, e assim por diante).

O nome expressa claramente o que o método faz. Ele também segue um padrão bem estabelecido no ecossistema Java para nomear métodos de fábrica estáticos.

3.2. A classeOptional

Outro bom exemplo de métodos de fábrica estáticos no JDK é a classeOptional. Esta classeimplements a few factory methods with pretty meaningful names, incluindoempty(),of() eofNullable():

Optional value1 = Optional.empty();
Optional value2 = Optional.of("example");
Optional value3 = Optional.ofNullable(null);

3.3. A classeCollections

Muito possivelmentethe most representative example of static factory methods in the JDK is the Collections class. Esta é uma classe não instanciável que implementa apenas métodos estáticos.

Muitos desses são métodos de fábrica que também retornam coleções, depois de aplicar à coleção fornecida algum tipo de algoritmo.

Aqui estão alguns exemplos típicos dos métodos de fábrica da classe:

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List unmodifiableList = Collections.unmodifiableList(originalList);
Map unmodifiableMap = Collections.unmodifiableMap(originalMap);

O número de métodos de fábrica estáticos no JDK é muito extenso, então vamos manter a lista de exemplos curta para fins de brevidade.

No entanto, os exemplos acima devem nos dar uma idéia clara de como os métodos de fábrica estática onipresentes são em Java.

4. Métodos de fábrica estáticos personalizados

Claro,we can implement our own static factory methods. Mas quando realmente vale a pena fazer isso, em vez de criar instâncias de classe por meio de construtores simples?

Vamos ver um exemplo simples.

Vamos considerar esta aula ingênua deUser:

public class User {

    private final String name;
    private final String email;
    private final String country;

    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }

    // standard getters / toString
}

Nesse caso, não há avisos visíveis para indicar que um método de fábrica estático pode ser melhor do que o construtor padrão.

E se quisermos que todas as instânciasUser obtenham um valor padrão para o campocountry?

Se inicializarmos o campo com um valor padrão, teremos que refatorar o construtor também, tornando o design mais rígido.

Em vez disso, podemos usar um método estático de fábrica:

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

Veja como obteríamos uma instânciaUser com um valor padrão atribuído ao campocountry:

User user = User.createWithDefaultCountry("John", "[email protected]");

5. Movendo a lógica para fora dos construtores

Nossa classeUser pode rapidamente apodrecer em um design defeituoso se decidirmos implementar recursos que exigiriam adicionar mais lógica ao construtor (os alarmes devem estar soando neste momento).

Vamos supor que queremos fornecer à classe a capacidade de registrar o momento em que cada objetoUser é criado.

If we just put this logic into the constructor, we’d be breaking the Single Responsibility Principle. Terminaríamos com um construtor monolítico que faz muito mais do que inicializar campos.

Podemos manter nosso design limpo com um método de fábrica estático:

public class User {

    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;

    // standard constructors / getters

    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        setLoggerProperties();
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }

    private static void setLoggerProperties() {
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.INFO);
        handler.setFormatter(new SimpleFormatter());
        LOGGER.addHandler(handler);
    }
}

Veja como criaríamos nossa instânciaUser aprimorada:

User user
  = User.createWithLoggedInstantiationTime("John", "[email protected]", "Argentina");

6. Instanciação controlada por instância

Como mostrado acima, podemos encapsular pedaços de lógica em métodos de fábrica estáticos antes de retornar objetosUser totalmente inicializados. E podemos fazer isso sem poluir o construtor com a responsabilidade de executar várias tarefas não relacionadas.

Por exemplo,suppose we want to make our User class a Singleton. We can achieve this by implementing an instance-controlled static factory method:

public class User {

    private static volatile User instance = null;

    // other fields / standard constructors / getters

    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

A implementação do métodogetSingletonInstance() éthread-safe, with a small performance penalty, due to the synchronized block.

Nesse caso, usamos a inicialização lenta para demonstrar a implementação de um método de fábrica estático controlado por instância.

Vale a pena mencionar, no entanto, quethe best way to implement a Singleton is with a Java enum type, as it’s both serialization-safe and thread-safe. Para obter os detalhes completos sobre como implementar Singletons usando diferentes abordagens, verifiquethis article.

Como esperado, obter um objetoUser com este método é muito semelhante aos exemplos anteriores:

User user = User.getSingletonInstance("John", "[email protected]", "Argentina");

7. Conclusão

Neste artigo, exploramos alguns casos de uso em que métodos estáticos de fábrica podem ser uma alternativa melhor ao uso de construtores Java simples.

Além disso, esse padrão de refatoração está tão fortemente enraizado em um fluxo de trabalho típico que a maioria dos IDEs fará isso por nós.

Claro,Apache NetBeans,IntelliJ IDEA eEclipse farão a refatoração de maneiras ligeiramente diferentes, portanto, certifique-se primeiro de verificar a documentação do IDE.

Tal como acontece com muitos outros padrões de refatoração, devemos usar métodos de fábrica estáticos com o devido cuidado e apenas quando vale a pena a compensação entre a produção de designs mais flexíveis e limpos e o custo de ter que implementar métodos adicionais.

Como de costume, todos os exemplos de código mostrados neste artigo estão disponíveisover on GitHub.