Ringkasan
Cara tradisional untuk mendapatkan instance dari suatu kelas adalah melalui constructor public.
```javascript public class Member {
}
public enum MemberStatus {
}
Secara umum, constructor public sudah cukup. Namun, terkadang menyediakan static factory method selain constructor akan lebih mudah bagi pengguna untuk membuat instance sesuai keinginan.
Contoh umum dari static factory method adalah metode valueOf() di Boolean.
```javascript public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
Metode di atas menerima nilai tipe dasar boolean dan mengubahnya menjadi objek Boolean lalu mengembalikannya.
Keuntungan Static Factory Method
Dapat memiliki nama
Parameter yang diteruskan ke constructor dan constructor itu sendiri tidak dapat sepenuhnya menjelaskan karakteristik objek yang akan dikembalikan. Misalnya, sulit untuk mengetahui karakteristik Member mana yang ingin dibuat hanya dengan melihat constructor utama (nama, usia, hobi, status anggota) di kelas Member di atas.
Selain itu, satu signature hanya dapat membuat satu constructor, tetapi static factory method dapat memiliki nama, sehingga satu signature dapat membuat beberapa static factory method untuk mengembalikan instance.
```javascript public class Member {
}
Dengan membuat beberapa static factory method dengan signature yang sama seperti di atas, daripada membedakan MemberStatus dengan constructor, pengguna dapat membuat instance Member dengan kemampuan tertentu tanpa ambiguitas.
Mari kita lihat perpustakaan yang ditentukan di JDK, ada static factory method probablePrime() di BigInteger.
```javascript public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2");
}
Jika kita membandingkan constructor biasa BigInteger dengan static factory method probablePrime(), jelas bahwa yang terakhir lebih baik dalam menjelaskan bahwa objek yang dikembalikan adalah BigInteger yang nilainya adalah bilangan prima.
Tidak perlu membuat instance baru setiap kali dipanggil
```javascript public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); }
Metode valueOf() di Boolean dapat dilihat bahwa instance tersebut disimpan dalam cache terlebih dahulu lalu dikembalikan. Karakteristik ini dapat secara signifikan meningkatkan kinerja jika objek dengan biaya pembuatan yang tinggi sering diminta, dan dapat dilihat sebagai teknik yang mirip dengan pola Flyweight.
Kelas yang menggunakan pendekatan static factory method untuk mengembalikan objek yang sama untuk permintaan berulang disebut kelas kontrol instance. Dengan mengontrol instance, Anda dapat membuat kelas singleton atau kelas yang tidak dapat diinstansiasi. Anda juga dapat memastikan bahwa kelas nilai tidak berubah memiliki instance tunggal.
Kontrol instance adalah dasar dari pola Flyweight, dan tipe enumerasi memastikan bahwa hanya satu instance yang dibuat.
Contoh
Anda harus menanam pohon di Minecraft. Jika setiap objek pohon dibuat ulang, ada potensi kebocoran memori.
Oleh karena itu, Anda dapat menyimpan objek pohon merah dan pohon hijau dan hanya mengembalikan lokasi yang berbeda. Tentu saja, warna bisa lebih dari 2 warna, jadi menyimpan pohon berdasarkan warna di struktur data seperti Map akan lebih efisien.
```javascript public class Tree {
}
public class TreeFactory { // Kelola pohon yang dibuat menggunakan struktur data 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);
}
Perbedaan dengan pola Singleton
Pola Singleton hanya memungkinkan satu pohon dibuat di kelas pohon. Oleh karena itu, Anda perlu mengubah warna satu objek yang dibuat jika Anda menggunakan pola Singleton. Artinya, pola Singleton hanya dapat memiliki satu jenis.
Kasus Penggunaan
Pola Flyweight digunakan di Java String Constant Pool.
Dapat mengembalikan objek tipe bawah dari tipe pengembalian
Jika Anda pernah menggunakan metode asList() dari kelas utilitas Arrays, Anda dapat memahami keuntungan ini.
```javascript
public static
Metode ini membungkus nilai dalam ArrayList, yang merupakan implementasi bawah List, dan mengembalikannya. Pengguna tidak perlu mengetahui implementasi ini. Artinya, fleksibilitas untuk memilih kelas objek yang dikembalikan memungkinkan pengembang untuk mengembalikan implementasi tanpa mengungkapkan implementasi tersebut, sehingga API dapat dijaga tetap kecil.
Cerita terkait metode statis antarmuka Java
Sebelum Java 8, metode statis tidak dapat dideklarasikan di antarmuka, sehingga jika diperlukan static factory method yang mengembalikan antarmuka bernama “Type”, kelas pendamping yang tidak dapat diinstansiasi bernama “Types” dibuat untuk mendefinisikan metode di dalamnya.
Contoh yang paling umum adalah 45 implementasi utilitas yang disediakan oleh JCF. Sebagian besar implementasi ini diperoleh melalui static factory method di satu kelas pendamping, java.util.Collections. Khususnya, beberapa implementasi ini bukan public, sehingga instance hanya dapat dibuat melalui static factory method (implementasi ini tentu saja tidak dapat diwariskan).
Selain itu, 45 implementasi tidak dipublikasikan, sehingga API dapat dijaga tetap kecil.
```javascript
// Contoh antarmuka dan kelas pendamping
List
Namun, mulai Java 8, metode statis dapat langsung ditambahkan ke antarmuka, sehingga tidak perlu mendefinisikan kelas pendamping secara terpisah.
Dapat mengembalikan objek kelas yang berbeda setiap kali berdasarkan parameter input.
Selain hanya mengembalikan tipe bawah, Anda dapat mengembalikan tipe bawah yang berbeda berdasarkan nilai parameter. Misalnya, jika Anda ingin mengembalikan MemberStatus yang berbeda berdasarkan skor, Anda dapat membuat static factory method seperti di bawah ini dan menetapkan logika perbandingan di dalamnya.
```javascript public enum MemberStatus {
}
@DisplayName("Uji MemberStatus") class MemberStatusTest {
}
Kelas objek yang akan dikembalikan tidak perlu ada saat menulis static factory method.
Dalam kalimat di atas, kelas objekadalah file kelas yang kita tulis.
Sebagai informasi, Class<?> mengacu pada objek Class yang dialokasikan di area heap ketika class loader memuat kelas. Objek Class ini berisi berbagai metadata dari kelas yang kita tulis.
```javascript package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
Dalam kode di atas, Anda dapat melihat bahwa objek Class dibuat melalui lokasi implementasi antarmuka, dan implementasi sebenarnya diinisialisasi menggunakan teknik refleksi. Saat ini, saat menulis static factory method, kelas StaticFactoryMethodTypeChild tidak perlu ada.
Jika tidak ada implementasi di jalur algorithm.dataStructure.StaticFactoryMethodTypeChild saat menggunakan static factory method, akan terjadi kesalahan. Namun, tidak ada masalah saat menulis static factory method, sehingga fleksibel.
```javascript public interface Test {
}
public class Main {
}
Anda dapat memperoleh fleksibilitas yang sama tanpa menggunakan refleksi. Dalam metode create() static factory di Test, tidak ada masalah saat menulis meskipun tidak ada implementasi. Tentu saja, NPE akan terjadi pada saat penggunaan sebenarnya, jadi Anda perlu mengembalikan implementasi nanti.
Fleksibilitas ini adalah dasar dari pembuatan kerangka kerja penyedia layanan, yang paling terkenal adalah JDBC. Penyedia dalam kerangka kerja penyedia layanan adalah implementasi layanan, dan kerangka kerja mengontrol untuk menyediakan implementasi ini kepada klien, memisahkan klien dari implementasi (DIP).
- Komponen kerangka kerja penyedia layanan
- Antarmuka layanan
- Mendefinisikan perilaku implementasi
- JDBC Connection
- API pendaftaran penyedia
- Penyedia mendaftarkan implementasinya
- JDBC DriverManager.registerDriver()
- API akses layanan
- Digunakan oleh klien untuk mendapatkan instance layanan. Jika tidak ada kondisi yang ditentukan, implementasi default atau implementasi yang didukung akan dikembalikan secara bergantian.
- Ini sesuai dengan static factory method
- JDBC DriverManager.getConnection()
- (Opsional) Antarmuka penyedia layanan
- Jika tidak, Anda harus menggunakan refleksi untuk membuat instance setiap implementasi.
- JDBC Driver
- Antarmuka layanan
Pola kerangka kerja penyedia layanan memiliki banyak variasi, termasuk pola Bridge, kerangka kerja injeksi dependensi, dll.
Contoh JDBC yang khas
```javascript Class.forName("oracle.jdbc.driver.OracleDriver"); Connection connection = null; connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// Logika sql menggunakan berbagai Statement
Secara umum, JDBC ditulis seperti di atas. OracleDriver, yang merupakan salah satu implementasi Driver, didaftarkan melalui Class.forName(), dan Connection, yang merupakan salah satu implementasi Connection untuk OracleDriver, diperoleh melalui DriverManager.getConnection().
Di sini, Connection adalah antarmuka layanan, DriverManager.getConnection() adalah API akses layanan, dan Driver adalah antarmuka penyedia layanan. Namun, API pendaftaran penyedia, DriverManager.registerDriver(), tidak digunakan. Namun, kami dapat mendaftarkan OracleDriver, yang merupakan implementasi Driver, hanya dengan Class.forName(). Bagaimana hal itu mungkin?
Prinsip Kerja Class.forName()
Metode ini meminta JVM untuk memuat kelas dengan memasukkan nama file kelas fisik sebagai argumen. Kemudian, class loader menyimpan metadata kelas di area metode dan mengalokasikan objek Class di area heap. Selain itu, setelah pemuatan kelas selesai, static field dan static block diinisialisasi, dan API pendaftaran penyedia digunakan pada saat ini.
```javascript public class OracleDriver implements Driver {
}
Sebenarnya, Anda dapat melihat bahwa OracleDriver menggunakan DriverManager.registerDriver() untuk mendaftarkan OracleDriver, yang merupakan implementasi Driver, di blok statis.
Analisis Kelas DriverManager
```javascript public class DriverManager {
}
Kelas DriverManager sebenarnya jauh lebih kompleks, tetapi ringkasannya serupa dengan di atas. Seperti yang dijelaskan di atas, OracleDriver didaftarkan melalui panggilan registerDriver() di blok statis OracleDriver, dan pengguna dapat memperoleh implementasi Connection melalui panggilan getConnection().
Jika Anda melihat lebih dekat pada getConnetion(), API akses pengguna, Anda dapat melihat bahwa Connection diperoleh dari antarmuka Driver. Jika tidak ada antarmuka Driver, yang merupakan antarmuka penyedia layanan, Anda dapat menggunakan refleksi seperti Class.forName() untuk mengembalikan implementasi Connection yang diinginkan. Saat ini, implementasi Connection tidak perlu ada saat menulis static factory.
Sebaliknya, kita menggunakan antarmuka Driver dan dapat dengan mudah memperoleh implementasi Connection yang sesuai dengan secara dinamis mendaftarkan implementasi Driver.
Sebagai informasi, saya telah menganalisis kode JDK sebenarnya dari metode getConnection() di DriverManager, tetapi Anda dapat melewatinya jika Anda tidak terlalu tertarik.
```javascript @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties();
}
Pertama, metode statis public getConnection() dipanggil, dan url, Properties, dan CallerClass diteruskan sebagai argumen ke metode statis privat getConnection(). Reflection.getCallerClass() berfungsi untuk mendapatkan kelas yang memanggil metode statis public getConnection() ini. Jika kelas Car memanggil getConnection(), objek Class dapat diperoleh melalui 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 adalah objek class loader dan dibuat oleh class loader caller atau thread saat ini. Kemudian, aDriver diambil satu per satu dari registeredDrivers, yang merupakan daftar Driver yang terdaftar dalam aplikasi saat ini. Kemudian, jika Driver ini menghasilkan true melalui isDriverAllowed(), objek Connection diperoleh dari Driver tersebut dan dikembalikan. isDriverAllowed() berfungsi untuk memverifikasi apakah aDriver ada di caller.
Keuntungan kerangka kerja JDBC
Intinya dari kerangka kerja JDBC adalah bahwa Driver, antarmuka Connection, dan kelas implementasi yang benar-benar mengimplementasikan antarmuka ini disediakan secara terpisah. Dengan membuat kerangka menggunakan antarmuka, dan kemudian membuat kelas implementasi yang sesuai dengan kerangka tersebut, sangat fleksibel.
Oleh karena itu, bahkan jika DBMS lain muncul, vendor dapat menyediakan Driver dan Connection yang mengimplementasikan antarmuka, sehingga pengembang yang menggunakan Java dapat menggunakan API yang sama dengan driver DBMS lainnya.
Kerugian Static Factory Method
Constructor public diperlukan untuk warisan, sehingga kelas anak tidak dapat dibuat jika hanya static factory method yang disediakan.
Namun, kendala ini dapat menjadi keuntungan karena lebih mendorong komposisi daripada warisan dan karena kendala ini harus dipenuhi untuk membuat tipe yang tidak berubah.
Static factory method sulit ditemukan oleh programmer.
Karena tidak muncul dengan jelas di deskripsi API seperti constructor, pengembang harus meminimalkan masalah dengan menulis dokumentasi API dengan baik dan menggunakan konvensi yang dikenal luas untuk menamai metode.
Cara Penamaan Static Factory Method
- from
- Menerima satu parameter dan mengembalikan instance dari tipe tersebut
- Date date = Date.from(instant);
- of
- Menerima beberapa parameter dan mengembalikan instance dari tipe yang sesuai
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- Versi lebih rinci dari from dan of
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance atau getInstance
- Mengembalikan instance yang ditentukan oleh parameter, tetapi tidak menjamin bahwa itu adalah instance yang sama.
- StackWalker luke = StackWalker.getInstance(options);
- create atau newInstance
- Sama seperti instance atau getInstance, tetapi menjamin bahwa instance baru dibuat dan dikembalikan setiap kali.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- Sama seperti getInstance, tetapi digunakan ketika static factory method didefinisikan di kelas lain, bukan kelas yang akan dibuat.
- FileStore fs = Files.getFileStore(path);
- newType
- Sama seperti newInstance, tetapi digunakan ketika static factory method didefinisikan di kelas lain, bukan kelas yang akan dibuat.
- BufferedReader br = Files.newBufferedReader(path);
- type
- Versi yang lebih ringkas dari getType dan newType
- List<Complaint> litany = Collections.list(legacyLitany);
Ringkasan
Static factory method dan constructor public masing-masing memiliki kegunaannya, jadi gunakan secara tepat.
Sumber
- Effective Java
- https://catsbi.oopy.io/d7f3a636-b613-453b-91c7-655d71fda2b1
- https://velog.io/@hoit_98/디자인-패턴-Flyweight-패턴
- https://velog.io/@shinmj1207/Effective-Java-객체-생성과-파괴1
- https://a1010100z.tistory.com/entry/아이템-1-생성자-대신-정적-팩터리-메서드를-고려하라
- https://plposer.tistory.com/61
- https://honbabzone.com/java/effective-java-static-factory-method/#장점-5--정적-팩터리-메서드를-작성하는-시점에서-반환할-객체의-클래스가-존재하지-않아도-된다
- https://ktaes.tistory.com/2
Komentar0