Omówienie
Tradycyjna metoda konstruowania instancji klasy to publiczny konstruktor.
```javascript public class Member {
}
public enum MemberStatus {
}
Publiczny konstruktor jest zazwyczaj wystarczający, ale często zapewnia użytkownikowi wygodniejszy sposób tworzenia instancji w zamian za konstruktora, zapewniając statyczną metodę fabryczną.
Typowy przykład statycznej metody fabrycznej to metoda Boolean.valueOf().
```javascript public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
Powyższa metoda przyjmuje wartość boolean typu boolean i zwraca ją jako obiekt Boolean.
Zalety statycznej metody fabrycznej
Może mieć nazwę.
Same parametry przekazywane do konstruktora i sam konstruktor nie opisują wystarczająco dobrze specyfiki zwracanego obiektu. Na przykład, trudno określić, jakie cechy ma Member, patrząc tylko na główny konstruktor klasy Member (name, age, hobby, memberStatus).
Ponadto dla jednego podpisu można utworzyć tylko jeden konstruktor, podczas gdy statyczna metoda fabryczna może mieć nazwę, co oznacza, że możesz utworzyć wiele statycznych metod fabrycznych z jednym podpisem w celu zwrócenia instancji.
```javascript public class Member {
}
Jak widać, tworzenie wielu statycznych metod fabrycznych z różnymi sygnaturami zamiast konstruktora do rozróżniania MemberStatus umożliwia użytkownikowi tworzenie instancji Member z konkretnymi umiejętnościami bez ryzyka pomyłki.
Rozważ bibliotekę zdefiniowaną w JDK, statyczną metodę fabryczną probablePrime() typu BigInteger.
```javascript public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2");
}
W porównaniu do ogólnego konstruktora BigInteger i statycznej metody fabrycznej probablePrime(), ta ostatnia wyraźnie mówi, że zwraca BigInteger, który jest liczbą pierwszą.
Nie trzeba tworzyć nowych instancji za każdym razem, gdy są wywoływane.
```javascript public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); }
Można zauważyć, że metoda valueOf() Booleana wstępnie buforuje instancje i zwraca je. Ta funkcja może znacznie poprawić wydajność w scenariuszach, w których obiekt jest często tworzony,wzorca Flyweightktóry można postrzegać jako podobną technikę.
Klasa, która używa tej statycznej metody fabrycznej, aby zwracać ten sam obiekt dla powtarzających się żądań, może kontrolować cykl życia instancji, dlatego nazywana jest klasą kontroli instancji. Kontrola instancji umożliwia tworzenie klas singletonów lub klas, których nie można tworzyć instancji. Ponadto zapewnia, że tylko jedna instancja jest taka sama w klasach wartości niezmiennych.
Kontroler instancji jest podstawą wzorca Flyweight, a typy wyliczeniowe zapewniają utworzenie tylko jednej instancji.
Przykład
Musisz zasadzić drzewo w Minecrafcie. Jeśli tworzysz nowy obiekt dla każdego drzewa, może to prowadzić do переполнения pamięci.
Dlatego lepiej jest przechowywać obiekty drzew i zmieniać tylko ich lokalizacje. Oczywiście kolor może być większy niż dwa, więc użycie struktury danych, takiej jak mapa, aby przechowywać drzewa według kolorów, byłoby skuteczne.
```javascript public class Tree {
}
public class TreeFactory { // Zarządzaj stworzonymi drzewami za pomocą struktury danych 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);
}
Różnica ze wzorcem Singleton
Wzorzec Singleton umożliwia utworzenie tylko jednego drzewa dla klasy. Tak więc, jeśli używany jest wzorzec Singleton, należy zmienić kolor utworzonego pojedynczego obiektu. Oznacza to, że wzorzec Singleton może mieć tylko jeden rodzaj, niezależnie od ilości.
Użyj przypadku
Wzorzec Flyweight jest używany w puli stałych ciągów Java.
Może zwracać obiekty podtypu typu zwracanego.
Zrozumiesz tę zaletę, jeśli kiedykolwiek korzystałeś z metody asList() klasy narzędziowej Arrays.
```javascript
public static
Zwraca ArrayList, który jest podtypem Listy, owijając wartość, ale użytkownik nie musi znać tego podtypu. Oznacza to, że możesz swobodnie wybierać klasę obiektu zwracanego, co zapewnia elastyczność deweloperom, ponieważ nie muszą ujawniać implementacji, więc API może być małe.
O statycznych metodach interfejsu Java
Przed Javą 8 nie można było deklarować statycznych metod w interfejsie, więc jeśli potrzebna była statyczna metoda zwracająca interfejs o nazwie “Typ”, musiałeś utworzyć dołączoną klasę o nazwie “Typy”, w której definiowano metody.
Typowy przykład to 45 implementacji narzędziowych provided JCF, z których prawie wszystkie uzyskuje się za pomocą statycznej metody fabrycznej z jednej dołączonej klasy java.util.Collections. Zwłaszcza wśród tych implementacji znajdują się te, które nie są publiczne, więc instancje można tworzyć tylko za pomocą statycznej metody fabrycznej i nie można odziedziczyć tej implementacji (oczywiście).
Nie ujawniają również 45 implementacji, co znacznie zmniejsza rozmiar API.
```javascript
// Przykład interfejsu i towarzyszącej mu klasy
List
Jednak od Java 8 można dodawać statyczne metody bezpośrednio do interfejsów, więc nie trzeba definiować oddzielnej klasy towarzyszącej.
Może zwracać różne obiekty podklas w zależności od parametrów wejściowych.
Poza prostym zwracaniem podtypów, możesz zwracać różne podtypy w zależności od wartości parametru. Na przykład, jeśli chcesz zwrócić MemberStatus w oparciu o wynik, możesz utworzyć statyczną metodę fabryczną w następujący sposób i umieścić w niej logikę porównania.
```javascript public enum MemberStatus {
}
@DisplayName("Test MemberStatus") class MemberStatusTest {
}
Statyczna metoda fabryczna nie musi istnieć w momencie tworzenia obiektu zwracanego.
W zdaniu obiekt zwracanyjest plikiem klas, który piszemy.
Należy zauważyć, że Class<?> reprezentuje obiekt Class, który ładowarka klas przypisuje do obszaru sterty podczas ładowania klasy. Ten obiekt klasy zawiera różne metadane o klasie, którą napisaliśmy.
```javascript package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
Jak widać, możesz utworzyć obiekt Class za pomocą lokalizacji implemented interfejsu i zainicjalizować rzeczywistą implementację za pomocą techniki refleksji. W tym momencie statyczna metoda fabryczna nie musi istniećKlase. StaticFactoryMethodTypeChild.
Jeśli implementacja nie istnieje pod ścieżką algorithm.dataStructure.StaticFactoryMethodTypeChild w momencie użycia statycznej metody fabrycznej, wystąpi błąd, ale ponieważ nie ma problemu w momencie utworzenia statycznej metody fabrycznej, mówi się, że jest elastyczna.
```javascript public interface Test {
}
public class Main {
}
Nawet jeśli nie używasz refleksji, możesz uzyskać tę samą elastyczność. Patrząc na metodę create() statycznej metody fabrycznej Test, nie ma problemu w momencie tworzenia, nawet jeśli nie ma implementacji. Oczywiście w rzeczywistym momencie użycia wystąpi NPE, więc musisz wtedy zwrócić implementację.
Ta elastyczność stanowi podstawę tworzenia frameworka providera usług, a jego typowym przykładem jest JDBC. Framework providera usług JDBC provider jest implementacją usługi, a framework kontroluje providerów w celu dostarczenia implementacji klientom (DIP).
- Komponenty frameworka providera usług
- Interfejs usługi
- Definiuje zachowanie implementacji
- Połączenie JDBC
- API rejestracji providera
- Register provider implementację
- JDBC DriverManager.registerDriver()
- API dostępu do usługi
- Używany, gdy klient uzyskuje instancję usługi; jeśli nie zostanie określony żaden warunek, zwraca domyślną implementację lub iteruje po dostępnych implementacjach.
- Odpowiednik statycznej metody fabrycznej
- JDBC DriverManager.getConnection()
- (opcjonalnie) Interfejs providera usługi
- Bez niej refleksja byłaby używana do tworzenia instancji każdej implementacji
- JDBC Driver
- Interfejs usługi
Wzór frameworka providera usług ma wiele wariantów, takich jak wzorzec mostu i framework wstrzykiwania zależności.
Typowy przykład JDBC
```javascript Class.forName("oracle.jdbc.driver.OracleDriver"); Connection connection = null; connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// Logika sql z różnymi instrukcjami
Ogólnie JDBC jest napisane w powyższy sposób. Driver jest rejestrowany za pomocą Class.forName(), a OracleDriver, który jest jedną z implementacji Driver, a Connection jest pobierane za pomocą DriverManager.getConnection(), które jest jedną z implementacji Connection, OracleDriver.
Tutaj Connection jest interfejsem usługi, DriverManager.getConnection() jest API dostępu do usługi, a Driver jest interfejsem providera usługi. Jednak API rejestracji providera DriverManager.registerDriver() nie zostało użyte. Mimo to możemy zarejestrować OracleDriver, implementację Driver, za pomocą Class.forName(). Jak to jest możliwe?
Zasada działania Class.forName()
Ta metoda pobiera fizyczną nazwę pliku klasy jako argument i prosi JVM o załadowanie tej klasy. Następnie ładowarka klas zapisuje metadane klasy w obszarze metod, a także przydziela obiekt Class do obszaru sterty. Ponadto po zakończeniu ładowania klasy,inicjowane są statyczne pola i bloki statyczne, a tutaj używane jest API rejestracji providera.
```javascript public class OracleDriver implements Driver {
}
Rzeczywiście, patrząc na OracleDriver, możesz stwierdzić, że rejestruje OracleDriver za pomocą DriverManager.registerDriver() w bloku statycznym.
Analiza klasy DriverManager
```javascript public class DriverManager {
}
Klasa DriverManager jest w rzeczywistości znacznie bardziej skomplikowana, ale jeśli podsumujemy kluczowe punkty i ułatwimy je, będzie to wyglądać podobnie jak powyżej. Jak wspomniano powyżej, OracleDriver jest rejestrowany, wywołując DriverManager.registerDriver() w bloku statycznym OracleDriver, a użytkownik może uzyskać implementację Connection wywołując getConnection().
Patrząc dokładniej na metodę API dostępu do użytkownika getConnection(), możesz zobaczyć, że uzyskuje Connection z interfejsu Driver. Jeśli nie ma interfejsu Driver, który jest interfejsem providera usługi, możesz użyć Class.forName() lub podobnej refleksji, aby zwrócić żądaną implementację Connection. Tutaj Implementacja Connection nie musi istnieć w momencie tworzenia statycznej metody fabrycznej.
Zamiast tego używamy interfejsu Driver, dynamicznie rejestrujemy implementację Driver, a następnie możemy łatwo uzyskać implementację Connection odpowiadającą temu Driverowi.
Nawiasem mówiąc, spojrzałem na rzeczywisty kod metody JDK getConnection() klasy DriverManager, ale jeśli nie jesteś szczególnie zainteresowany, możesz go pominąć.
```javascript @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties();
}
Najpierw wywoływana jest publiczna statyczna metoda getConnection(), a url, Properties i CallerClass są przekazywane do prywatnej statycznej metody getConnection(). W tym czasie Reflection.getCallerClass() służy do uzyskania klasy, która wywołała publiczną statyczną metodę getConnection(). Na przykład, jeśli klasa Car wywołała getConnection(), Reflection.getCallerClass() zwróci obiekt Class.
```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(); } }
}
Najpierw callerCL staje się obiektem klasy ładowarki klas, a jest tworzony przez klasę ładującą lub bieżący wątek. Następnie iteruje po liście zarejestrowanych sterowników w bieżącej aplikacji, aDriver. Jeśli aDriver jest uznany przez isDriverAllowed() za prawdziwe, uzyskuje obiekt Connection za pomocą tego sterownika i zwraca go. isDriverAllowed() sprawdza, czy aDriver istnieje w klasie ładującej.
Zalety frameworka JDBC
Kluczową zaletą JDBC jest to, że interfejs Driver, interfejs Connection i jego rzeczywiste klasy implementacji są całkowicie oddzielone i dostarczane. Tworząc ramkę za pomocą interfejsu i tworząc każdą klasę implementacji pasującą do tej ramki, jest to bardzo elastyczne.
Dlatego nawet jeśli pojawi się inny DBMS, ten dostawca może zapewnić sterownik i implementację interfejsu Connection, aby programiści Java mogli używać tego samego API sterownika JDBC.
Wady statycznej metody fabrycznej
Nie można tworzyć podklas, ponieważ publiczny lub chroniony konstruktor jest wymagany podczas dziedziczenia.
Jednak to ograniczenie może być również zaletą, ponieważ zachęca do kompozycji zamiast dziedziczenia i można tego wymagać do tworzenia typów niezmiennych.
Statycznych metod fabrycznych trudno zaleźć programistom.
Ponieważ nie są one wyraźnie wyświetlane w opisie API, jak konstruktorzy, programiści muszą dobrze napisać dokumentację API i nadać metodom nazwy, które są powszechnie znane w tej konwencji.
Konwencja nazewnictwa statycznych metod fabrycznych
- z
- Przyjmuje jeden argument i zwraca instancję tego typu.
- Date date = Date.from(instant);
- z
- Przyjmuje wiele argumentów i zwraca instancję odpowiedniego typu.
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- Bardziej szczegółowa wersja z i of.
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance lub getInstance
- Zwraca wskazaną instancję, ale nie gwarantuje, że jest to ta sama instancja.
- StackWalker luke = StackWalker.getInstance(options);
- create lub newInstance
- Podobnie jak instance lub getInstance, ale gwarantuje, że każdorazowo zwracana jest nowa instancja.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- Podobie jak getInstance, ale zdefiniować statyczną metodę fabryczną w innej klasie niż tworzona klasa.
- FileStore fs = Files.getFileStore(path);
- newType
- Podobie jak newInstance, ale zdefiniować statyczną metodę fabryczną w innej klasie niż tworzona klasa.
- BufferedReader br = Files.newBufferedReader(path);
- typ
- Prosta wersja getType i newType.
- List<Complaint> litany = Collections.list(legacyLitany);
Wniosek
Statyczne metody fabryczne i publiczne konstruktory mają swoje zastosowania, więc używaj ich mądrze.
Komentarze0