Visão geral
O meio tradicional de obter instâncias de classe é por meio de construtores públicos.
```javascript public class Member {
}
public enum MemberStatus {
}
Normalmente, construtores públicos são suficientes, mas fornecer métodos estáticos de fábrica (static factory method) além dos construtores frequentemente torna mais fácil para os usuários criar instâncias como desejado.
Um exemplo típico de método de fábrica estático é o método valueOf() de Boolean.
```javascript public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
O método acima recebe um valor booleano do tipo primitivo e retorna um objeto Boolean.
Vantagens do método de fábrica estático
Pode ter um nome
Os parâmetros passados para o construtor e o próprio construtor não conseguem descrever adequadamente as características do objeto que será retornado. Por exemplo, é difícil determinar quais características um membro tem apenas olhando para o construtor principal da classe Member (name, age, hobby, memberStatus).
Além disso, um construtor pode ser criado com uma assinatura, mas métodos de fábrica estáticos podem ter nomes, então uma assinatura pode ser usada para criar vários métodos de fábrica estáticos para retornar instâncias.
```javascript public class Member {
}
Como acima, criar vários métodos de fábrica estáticos com a mesma assinatura em vez de usar MemberStatus no construtor permite que os usuários criem instâncias Member com habilidades específicas sem confusão.
Se olharmos para as bibliotecas definidas no JDK, veremos o método de fábrica estático probablePrime() de BigInteger.
```javascript public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2");
}
Ao comparar o construtor geral de BigInteger com o método de fábrica estático probablePrime(), o último descreve melhor a frase que retorna um BigInteger que é um número primo.
Não precisa criar uma nova instância a cada chamada
```javascript public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); }
O método valueOf() de Boolean coloca em cache as instâncias e as retorna. Este recurso pode melhorar significativamente o desempenho em cenários onde objetos caros são solicitados com frequência.Flyweight Patterné uma técnica semelhante.
As classes que usam métodos de fábrica estáticos para retornar a mesma instância para solicitações repetidas são chamadas de classes de controle de instância, pois podem controlar o ciclo de vida da instância. Ao controlar instâncias, você pode criar uma classe Singleton ou uma classe que não pode ser instanciada. Além disso, você pode garantir que uma classe de valor imutável tenha apenas uma instância.
O controle de instâncias é a base do padrão Flyweight, e os tipos de enumeração garantem que apenas uma instância é criada.
Exemplo
Na Minecraft, você precisa plantar árvores. Se cada objeto de árvore for criado novamente, isso pode levar a um estouro de memória.
Portanto, você pode salvar objetos de árvores vermelhas e verdes e apenas retornar suas localizações. É claro que a cor pode ser mais do que essas duas, então seria mais eficiente salvar árvores em uma estrutura de dados como um mapa de acordo com sua cor.
```javascript public class Tree {
}
public class TreeFactory { // Gerenciar árvores criadas usando a estrutura de dados HashMap. public static final Map<String, Tree> treeMap = new HashMap<>();
}
public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in);
}
Diferença com o padrão Singleton
O padrão Singleton permite que apenas uma árvore seja criada na classe Tree. Portanto, se você usar o padrão Singleton, você terá que mudar a cor do único objeto criado. Ou seja, o padrão Singleton permite apenas uma, independentemente do tipo.
Casos de uso
O Flyweight Pattern é usado no String Constant Pool do Java.
Você pode retornar um objeto do tipo de subtipo
Se você já usou o método asList() da classe de utilitários Arrays, você pode entender essa vantagem.
```javascript
public static
Ele envolve o valor em um ArrayList, que é uma implementação da subclasse de List, mas o usuário não precisa saber sobre essa implementação. Em outras palavras, a flexibilidade de poder escolher a classe do objeto retornado permite que os desenvolvedores mantenham uma API pequena sem expor a implementação.
Histórias relacionadas a métodos estáticos de interface Java
Antes do Java 8, métodos estáticos não podiam ser declarados em interfaces, então uma classe companheira que não podia ser instanciada chamada "Types" era criada para definir métodos dentro dela, se necessário, um método estático que retornasse uma interface chamada "Type".
Um exemplo clássico é o Java Collection Framework (JCF), que possui 45 implementações de utilitários, a maioria dos quais é obtida por meio de métodos de fábrica estáticos na classe companheira java.util.Collections. Em particular, algumas dessas implementações não são públicas e só podem ser instanciadas por meio de métodos de fábrica estáticos. (Obviamente, essas implementações não podem ser herdadas).
Além disso, a API pode ser mantida muito menor, pois as 45 implementações não são expostas.
```javascript
// Interface e classe companheira
List
No entanto, a partir do Java 8, os métodos estáticos podem ser adicionados diretamente às interfaces, então não há necessidade de definir classes companheiras separadamente.
Você pode retornar objetos de classes diferentes dependendo dos parâmetros de entrada.
Além de simplesmente retornar subtipos, você pode retornar subtipos diferentes dependendo do valor do parâmetro. Por exemplo, se você quiser retornar MemberStatus diferente dependendo da pontuação, você pode criar um método de fábrica estático e colocar lógica de comparação nele, como mostrado abaixo.
```javascript public enum MemberStatus {
}
@DisplayName("Teste MemberStatus") class MemberStatusTest {
}
A classe do objeto a ser retornado não precisa existir no momento em que o método de fábrica estático é escrito.
Na frase acima,classe de objetoé o nosso arquivo de classe.
Observe que Class<?> é um objeto Class alocado na área de heap quando o carregador de classes carrega uma classe. Este objeto Class contém vários metadados da classe que escrevemos.
```javascript package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
O código acima mostra como um objeto Class é criado com base no local da implementação da interface e a implementação real é inicializada usando a técnica de reflexão. Neste momento,quando o método de fábrica estático é escritoa classe StaticFactoryMethodTypeChild não precisa existir.
Se a implementação não existir no caminho algorithm.dataStructure.StaticFactoryMethodTypeChild quando o método de fábrica estático for usado, um erro ocorrerá, mas não haverá problema quando o método de fábrica estático for escrito, então é flexível.
```javascript public interface Test {
}
public class Main {
}
Você pode obter a mesma flexibilidade sem usar a reflexão. O método create() do método de fábrica estático de Test mostra que não há problema na hora de escrever, mesmo que não haja implementação. Claro, um NPE ocorrerá quando você realmente o usar, então você precisa fornecer a implementação mais tarde.
Essa flexibilidade é a base para a criação de estruturas de provedores de serviços, como o JDBC. Em uma estrutura de provedor de serviços, o provedor é a implementação do serviço e a estrutura controla a entrega dessas implementações ao cliente, separando o cliente da implementação. (DIP)
- Componentes da estrutura do provedor de serviços
- Interface de serviço
- Define o comportamento da implementação.
- JDBC Connection
- API de registro de provedor
- O provedor registra a implementação.
- JDBC DriverManager.registerDriver()
- API de acesso ao serviço
- Usado quando o cliente obtém a instância do serviço, e se nenhuma condição for especificada, a implementação padrão ou a implementação compatível será retornada alternadamente.
- Aplica-se a métodos de fábrica estáticos.
- JDBC DriverManager.getConnection()
- (Opcional) Interface do provedor de serviços
- Se este não existir, a reflexão deve ser usada para instanciar cada implementação.
- JDBC Driver
- Interface de serviço
O padrão de estrutura de provedor de serviços tem muitas variações, incluindo o padrão de ponte, estrutura de injeção de dependência, etc.
Típico Exemplo JDBC
```javascript Class.forName("oracle.jdbc.driver.OracleDriver"); Connection connection = null; connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// Lógica sql usando várias instruções
O JDBC é normalmente escrito como acima. OracleDriver, que é uma das implementações do Driver, é registrado por meio de Class.forName(), e uma das implementações de Connection, Connection para OracleDriver, é obtida por meio de DriverManager.getConnection().
Aqui, Connection é a interface de serviço, DriverManager.getConnection() é a API de acesso ao serviço e Driver é a interface do provedor de serviço. No entanto, DriverManager.registerDriver(), que é a API de registro do provedor, não é usada. No entanto, podemos registrar OracleDriver, que é a implementação do Driver, usando apenas Class.forName(). Como isso é possível?
Como Class.forName() funciona
Este método solicita ao JVM para carregar essa classe passando o nome do arquivo de classe físico como argumento. Então, o carregador de classes armazena os metadados da classe na área de método e, ao mesmo tempo, aloca um objeto Class na área de heap. Além disso, quando o carregamento da classe termina,campos estáticos e blocos estáticos são inicializados, e a API de registro do provedor é usada aqui.
```javascript public class OracleDriver implements Driver {
}
Na verdade, ao olhar para o OracleDriver, podemos ver que o OracleDriver é registrado usando DriverManager.registerDriver() no bloco estático.
Analisando a classe DriverManager
```javascript public class DriverManager {
}
A classe DriverManager é realmente muito mais complexa, mas se você resumir apenas o essencial, será semelhante ao acima. Como explicado anteriormente, OracleDriver é registrado chamando registerDriver() no bloco estático de OracleDriver, e os usuários podem obter a implementação de Connection chamando getConnection().
Se você olhar mais de perto para o método getConnetion() da API de acesso do usuário, você verá que ele obtém Connection da interface Driver. Se a interface do provedor de serviços Driver não existir, você pode usar a reflexão, como Class.forName(), para retornar a implementação de Connection desejada. Neste caso,a implementação de Connection não precisa existir quando o método de fábrica estático é escrito.
Em vez disso, usamos a interface Driver e podemos facilmente obter a implementação de Connection que corresponde a ela, registrando dinamicamente a implementação de Driver.
Por falar nisso, analisei o código real do JDK do método getConnection() de DriverManager, mas se você não estiver interessado, pode ignorá-lo.
```javascript @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties();
}
Primeiro, o método estático público getConnection() é chamado, e url, Properties e CallerClass são passados como argumentos para o método estático privado getConnection(). Neste momento, Reflection.getCallerClass() obtém a classe que chamou o método estático público getConnection(). Se a classe Car chamar getConnection(), um objeto Class pode ser obtido por Reflection.getCallerClass().
```javascript private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } }
}
callerCL é um objeto carregador de classes e é criado pelo carregador de classes do caller ou da thread atual. Em seguida, ele percorre um a um aDriver na lista de drivers registrados registeredDrivers no aplicativo atual. E se esse Driver for true por isDriverAllowed(), ele obtém um objeto Connection desse Driver e retorna. isDriverAllowed() é responsável por verificar se aDriver existe no caller.
Vantagens da estrutura JDBC
O ponto principal da estrutura JDBC é que o Driver, a interface Connection e as classes de implementação que realmente implementam essas interfaces são fornecidas completamente separadamente. Criar um modelo usando interfaces e depois criar classes de implementação que se encaixam nesse modelo é muito flexível.
Portanto, mesmo que um novo DBMS seja lançado, o fornecedor pode fornecer implementações de Driver e Connection, permitindo que desenvolvedores que usam Java usem a mesma API que outros drivers de DBMS.
Desvantagens do método de fábrica estático
Se você precisar herdar, precisa de um construtor público ou protegido, então você não pode criar uma subclasse se fornecer apenas um método de fábrica estático.
No entanto, essa restrição pode ser uma vantagem, pois incentiva a composição em vez da herança e essa restrição deve ser mantida para criar um tipo imutável.
Os métodos de fábrica estáticos são difíceis para os programadores encontrarem.
Como não é exposto claramente na descrição da API como um construtor, os desenvolvedores devem minimizar o problema escrevendo documentação de API bem e usando nomes de métodos que sigam convenções amplamente conhecidas.
Convenções de nomenclatura do método de fábrica estático
- from
- Recebe um parâmetro e retorna uma instância desse tipo.
- Date date = Date.from(instant);
- of
- Recebe vários parâmetros e retorna uma instância do tipo apropriado.
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- Uma versão mais detalhada do from e of
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance ou getInstance
- Retorna a instância especificada pelo parâmetro, mas não garante que seja a mesma instância.
- StackWalker luke = StackWalker.getInstance(options);
- create ou newInstance
- Semelhante ao instance ou getInstance, mas garante que uma nova instância é criada e retornada.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- Semelhante ao getInstance, mas usado quando o método de fábrica é definido em uma classe diferente da classe a ser criada.
- FileStore fs = Files.getFileStore(path);
- newType
- Semelhante ao newInstance, mas usado quando o método de fábrica é definido em uma classe diferente da classe a ser criada.
- BufferedReader br = Files.newBufferedReader(path);
- type
- Versão concisa de getType e newType
- List<Complaint> litany = Collections.list(legacyLitany);
Resumo
Os métodos de fábrica estáticos e os construtores públicos têm seus próprios usos, então vamos usá-los adequadamente.
Comentários0