Panoramica
Il modo tradizionale per ottenere un'istanza di una classe è tramite un costruttore pubblico.
```javascript public class Member {
}
public enum MemberStatus {
}
Generalmente, un costruttore pubblico è sufficiente, ma a volte, fornendo un metodo statico di fabbrica (static factory method) oltre al costruttore, è più facile per l'utente creare un'istanza come previsto.
Un esempio tipico di metodo statico di fabbrica è il metodo valueOf() di Boolean.
```javascript public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
Il metodo sopra accetta un valore di tipo primitivo boolean e restituisce un oggetto Boolean.
Vantaggi dei metodi statici di fabbrica
Può avere un nome.
I parametri passati al costruttore e il costruttore stesso non sono in grado di descrivere adeguatamente le caratteristiche dell'oggetto restituito. Ad esempio, è difficile capire da solo quale tipo di Member sia dalla classe Member di cui sopra, solo guardando il costruttore principale (nome, età, hobby, stato membro).
Inoltre, è possibile creare un solo costruttore per una singola firma, mentre i metodi statici di fabbrica possono avere un nome, quindi è possibile creare più metodi statici di fabbrica con la stessa firma per restituire istanze.
```javascript public class Member {
}
Come sopra, creare più metodi statici di fabbrica con la stessa firma anziché usare il costruttore per distinguere il MemberStatus, consente all'utente di creare un'istanza di Member con specifiche capacità senza confusione.
Guardando le librerie definite nel JDK, esiste un metodo statico di fabbrica, probablePrime(), in BigInteger.
```javascript public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2");
}
Comparando il costruttore generale di BigInteger con il metodo statico di fabbrica probablePrime(), quest'ultimo descrive meglio il fatto che restituisce un BigInteger che è un numero primo.
Non è necessario creare una nuova istanza ogni volta che viene chiamato.
```javascript public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); }
Si può vedere che il metodo valueOf() di Boolean mette in cache le istanze e le restituisce. Questa caratteristica può migliorare significativamente le prestazioni, soprattutto quando vengono richieste frequentemente oggetti con elevati costi di creazione, ed è possibile vederla come una tecnica simile aFlyweight Pattern.
Le classi che usano il metodo statico di fabbrica per restituire lo stesso oggetto alle richieste ripetute sono chiamate classi di controllo delle istanze perché possono controllare il ciclo di vita delle istanze. Controllando le istanze, è possibile creare una classe singleton o una classe non istanziabile. Inoltre, è possibile garantire che ci sia una sola istanza nelle classi di valori immutabili.
Il controllo delle istanze è alla base del modello Flyweight e i tipi enumerati garantiscono che venga creata solo una singola istanza.
Esempio
In Minecraft, è necessario piantare alberi. Se si crea un nuovo oggetto per ogni albero, è probabile che si verifichi un overflow di memoria.
Pertanto, come sopra, è possibile memorizzare gli oggetti albero rosso e albero verde e restituire solo la posizione. Ovviamente, poiché il colore può essere più di due colori, sarebbe più efficiente memorizzare gli alberi in base al colore in una struttura dati come Map.
```javascript public class Tree {
}
public class TreeFactory { // Gestisce gli alberi creati utilizzando la struttura dati 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);
}
Differenza con il modello Singleton
Il modello Singleton consente di creare un solo albero nella classe albero. Pertanto, se si utilizza il modello Singleton, è necessario modificare il colore dell'unico oggetto creato. In altre parole, il modello Singleton consente di avere un solo oggetto indipendentemente dal tipo.
Casi d'uso
Il modello Flyweight viene utilizzato nel pool di costanti String di Java.
Può restituire un oggetto di un sottotipo del tipo di ritorno.
Se hai mai usato il metodo asList() della classe utility Arrays, puoi capire questo vantaggio.
```javascript
public static
Restituisce un valore avvolto in ArrayList, che è un'implementazione di sottotipo di List, ma l'utente non ha bisogno di conoscere questa implementazione. Vale a dire, la flessibilità di poter scegliere la classe dell'oggetto restituito consente allo sviluppatore di restituire l'implementazione senza esporla, il che consente di mantenere l'API di dimensioni ridotte.
Storia dei metodi statici dell'interfaccia Java
Prima di Java 8, non era possibile dichiarare metodi statici nelle interfacce, quindi se fosse stato necessario un metodo statico che restituisse un'interfaccia denominata "Type", sarebbe stato necessario creare una classe associata non istanziabile denominata "Types" per definire il metodo al suo interno.
Un esempio tipico sono le 45 implementazioni di utilità fornite da JCF, la maggior parte delle quali vengono ottenute tramite metodi statici di fabbrica nella classe associata java.util.Collections. In particolare, alcune di queste implementazioni non sono pubbliche e possono essere istanziate solo tramite metodi statici di fabbrica (naturalmente, queste implementazioni non possono essere ereditate).
Inoltre, poiché non vengono esposte 45 implementazioni, l'API può essere molto più piccola.
```javascript
// Esempio di interfaccia e classe associata
List
Tuttavia, a partire da Java 8, è possibile aggiungere metodi statici direttamente alle interfacce, quindi non è più necessario definire una classe associata separatamente.
È possibile restituire un oggetto di una classe diversa a seconda del parametro di input.
Oltre a restituire semplicemente un sottotipo, è possibile restituire un sottotipo diverso a seconda del valore del parametro. Ad esempio, se si desidera restituire un MemberStatus diverso a seconda del punteggio, è possibile creare un metodo statico di fabbrica come quello sottostante e inserire la logica di confronto al suo interno.
```javascript public enum MemberStatus {
}
@DisplayName("MemberStatus Test") class MemberStatusTest {
}
La classe dell'oggetto da restituire non deve esistere al momento della scrittura del metodo statico di fabbrica.
Nella frase sopra,classe dell'oggettosi riferisce al file di classe che scriviamo.
Per inciso, Class<?> si riferisce all'oggetto Class che l'oggetto ClassLoader alloca nell'area heap quando carica una classe. Questo oggetto Class contiene vari metadati della classe che abbiamo scritto.
```javascript package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
Nel codice precedente, puoi vedere che un oggetto Class viene creato in base al percorso dell'implementazione dell'interfaccia e la tecnica della riflessione viene utilizzata per inizializzare l'implementazione effettiva. In questo caso,al momento della scrittura del metodo statico di fabbrica, la classe StaticFactoryMethodTypeChild non deve esistere.
Se non esiste un'implementazione nel percorso algorithm.dataStructure.StaticFactoryMethodTypeChild al momento dell'utilizzo del metodo statico di fabbrica, si verificherà un errore, ma non ci saranno problemi al momento della scrittura del metodo statico di fabbrica, quindi è flessibile.
```javascript public interface Test {
}
public class Main {
}
È possibile ottenere la stessa flessibilità senza utilizzare la riflessione. Guardando il metodo statico di fabbrica create() di Test, non ci sono problemi a scriverlo anche se non ci sono implementazioni. Naturalmente, si verifica un NPE al momento dell'effettivo utilizzo, quindi è necessario restituire l'implementazione in un secondo momento.
Questa flessibilità è alla base della creazione di framework di provider di servizi, di cui JDBC è un esempio tipico. Il provider del framework di provider di servizi è l'implementazione del servizio e il framework controlla la fornitura di queste implementazioni al client, separando il client dall'implementazione (DIP).
- Componenti del framework di provider di servizi
- Interfaccia di servizio
- Definisce il comportamento dell'implementazione.
- JDBC Connection
- API di registrazione del provider
- Il provider registra l'implementazione.
- JDBC DriverManager.registerDriver()
- API di accesso al servizio
- Utilizzato dal client per ottenere un'istanza del servizio, e se non sono specificate condizioni, restituisce l'implementazione predefinita o l'implementazione supportata in modo rotatorio.
- Corrispondenza dei metodi statici di fabbrica
- JDBC DriverManager.getConnection()
- (Opzionale) Interfaccia di provider di servizi
- Se questo non esiste, è necessario utilizzare la riflessione per istanziare ogni implementazione.
- JDBC Driver
- Interfaccia di servizio
Il modello di framework di provider di servizi ha molte varianti, tra cui il modello Bridge, il framework di iniezione di dipendenze, ecc.
Tipico Esempio JDBC
```javascript Class.forName("oracle.jdbc.driver.OracleDriver"); Connection connection = null; connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// Logica SQL utilizzando vari Statement
Generalmente, JDBC viene scritto come sopra. Tramite Class.forName(), viene registrato uno degli implementatori di Driver, OracleDriver, e tramite DriverManager.getConnection(), viene ottenuto uno degli implementatori di Connection, Connection per OracleDriver.
Qui, Connection è l'interfaccia di servizio, DriverManager.getConnection() è l'API di accesso al servizio e Driver è l'interfaccia di provider di servizi. Tuttavia, l'API di registrazione del provider, DriverManager.registerDriver(), non viene utilizzata. Tuttavia, è possibile registrare OracleDriver, che è un'implementazione di Driver, usando solo Class.forName(). Come è possibile?
Meccanismo di funzionamento di Class.forName()
Questo metodo accetta il nome del file di classe fisico come argomento e richiede alla JVM di caricare la classe. Il caricatore di classi memorizza i metadati della classe nell'area metodo e alloca anche un oggetto Class nell'area heap. Inoltre, una volta completato il caricamento della classe,i campi statici e i blocchi statici vengono inizializzati e l'API di registrazione del provider viene utilizzata in questo momento.
```javascript public class OracleDriver implements Driver {
}
In realtà, puoi vedere che OracleDriver utilizza DriverManager.registerDriver() all'interno del blocco statico per registrare OracleDriver, che è un'implementazione di Driver.
Analisi della classe DriverManager
```javascript public class DriverManager {
}
La classe DriverManager è in realtà molto più complessa, ma come descritto sopra, è simile a questa. Come descritto sopra, OracleDriver viene registrato chiamando registerDriver() nel blocco statico di OracleDriver, e l'utente può ottenere un'implementazione di Connection chiamando getConnection().
Guardando più da vicino l'API di accesso dell'utente, getConnection(), puoi vedere che ottiene una Connection dall'interfaccia Driver. Se non esiste un'interfaccia Driver di provider di servizi, è possibile utilizzare la riflessione, come Class.forName(), per restituire l'implementazione di Connection desiderata. In questo caso,l'implementazione di Connection non deve esistere al momento della scrittura del metodo statico di fabbrica.
Invece, utilizziamo l'interfaccia Driver e possiamo facilmente ottenere un'implementazione di Connection corrispondente a quel Driver registrando dinamicamente l'implementazione di Driver.
Per inciso, ho analizzato il codice JDK effettivo del metodo getConnection() di DriverManager, ma puoi saltarlo se non sei molto interessato.
```javascript @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties();
}
Innanzitutto, viene chiamato il metodo statico pubblico getConnection() e url, Properties e CallerClass vengono passati come argomenti al metodo statico privato getConnection(). In questo caso, Reflection.getCallerClass() serve a ottenere la classe che ha chiamato il metodo statico pubblico getConnection(). Se la classe Car chiama getConnection(), è possibile ottenere un oggetto Class tramite 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 è un oggetto caricatore di classi e viene creato dal caricatore di classi di caller o dal thread corrente. Successivamente, viene estratto un aDriver dall'elenco di driver registrati nell'applicazione corrente, registeredDrivers. Se questo Driver restituisce true in isDriverAllowed(), viene ottenuta una connessione da questo Driver e restituita. isDriverAllowed() verifica se aDriver esiste in caller.
Vantaggi del framework JDBC
Il punto fondamentale del framework JDBC è che l'interfaccia Driver, l'interfaccia Connection e le classi di implementazione che implementano queste interfacce vengono fornite separatamente. Utilizzando l'interfaccia per creare una cornice, è possibile creare classi di implementazione separate che si adattano a quella cornice, il che è molto flessibile.
Pertanto, anche se viene rilasciato un altro DBMS, il fornitore può fornire Driver e Connection che implementano l'interfaccia, consentendo agli sviluppatori che utilizzano Java di utilizzare la stessa API per i driver di altri DBMS.
Svantaggi dei metodi statici di fabbrica
È necessario un costruttore pubblico o protetto per l'ereditarietà, quindi non è possibile creare sottoclassi se viene fornito solo un metodo statico di fabbrica.
Tuttavia, questo vincolo può essere considerato un vantaggio in quanto incoraggia la composizione piuttosto che l'ereditarietà e deve essere mantenuto per creare tipi immutabili.
I metodi statici di fabbrica sono difficili da trovare per i programmatori.
Poiché non sono esplicitamente evidenziati nell'API come i costruttori, gli sviluppatori devono mitigare il problema scrivendo una buona documentazione API e dando ai metodi nomi che seguono convenzioni ampiamente riconosciute.
Metodi di denominazione dei metodi statici di fabbrica
- from
- Accetta un parametro e restituisce un'istanza del tipo corrispondente.
- Date date = Date.from(instant);
- of
- Accetta più parametri e restituisce un'istanza del tipo appropriato.
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- Versione più dettagliata di from e of.
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance o getInstance
- Restituisce l'istanza specificata come parametro, ma non garantisce che sia la stessa istanza.
- StackWalker luke = StackWalker.getInstance(options);
- create o newInstance
- Simile a instance o getInstance, ma garantisce che venga creata e restituita una nuova istanza ogni volta.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- Simile a getInstance, ma utilizzato quando il metodo di fabbrica è definito in una classe diversa da quella da creare.
- FileStore fs = Files.getFileStore(path);
- newType
- Simile a newInstance, ma utilizzato quando il metodo di fabbrica è definito in una classe diversa da quella da creare.
- BufferedReader br = Files.newBufferedReader(path);
- type
- Versione abbreviata di getType e newType.
- List<Complaint> litany = Collections.list(legacyLitany);
Riepilogo
I metodi statici di fabbrica e i costruttori pubblici hanno ciascuno il proprio scopo, quindi usali in modo appropriato.
Commenti0