Konstruktor Java vs Metode Pabrik Statis

1. Ikhtisar

Konstruktor Java adalah mekanisme default untuk mendapatkan instance kelas yang diinisialisasi sepenuhnya. Bagaimanapun, mereka menyediakan semua infrastruktur yang diperlukan untuk menyuntikkan dependensi, baik secara manual atau otomatis.

Meski begitu, dalam beberapa kasus penggunaan tertentu, lebih baik menggunakan metode pabrik statis untuk mencapai hasil yang sama.

Dalam tutorial ini, kami akan menyoroti pro dan kontra menggunakan metode pabrik statis vs konstruktor Java lama biasa .

2. Keuntungan Metode Pabrik Statis Dibandingkan Konstruktor

Dalam bahasa berorientasi objek seperti Java, apa yang salah dengan konstruktor? Secara keseluruhan, tidak ada. Meski begitu, Efektif Java Item 1 Block Joshua yang terkenal dengan jelas menyatakan:

"Pertimbangkan metode pabrik statis, bukan konstruktor"

Meskipun ini bukan peluru perak, berikut adalah alasan paling kuat yang mempertahankan pendekatan ini:

  1. Konstruktor tidak memiliki nama yang bermakna , jadi mereka selalu dibatasi pada konvensi penamaan standar yang diberlakukan oleh bahasa. Metode pabrik statis dapat memiliki nama yang bermakna , sehingga secara eksplisit menyampaikan apa yang mereka lakukan
  2. Metode pabrik statis dapat mengembalikan tipe yang sama yang mengimplementasikan metode, subtipe, dan juga primitif , sehingga menawarkan rentang tipe pengembalian yang lebih fleksibel
  3. Metode pabrik statis dapat merangkum semua logika yang diperlukan untuk pra-konstruksi instans yang diinisialisasi penuh , sehingga dapat digunakan untuk memindahkan logika tambahan ini dari konstruktor. Ini mencegah konstruktor melakukan tugas lebih lanjut, selain dari hanya menginisialisasi bidang
  4. Metode pabrik statis dapat menjadi metode instans terkontrol , dengan pola Singleton menjadi contoh paling mencolok dari fitur ini

3. Metode Pabrik Statis di JDK

Ada banyak contoh metode pabrik statis di JDK yang menunjukkan banyak keuntungan yang diuraikan di atas. Mari jelajahi beberapa di antaranya.

3.1. The String Kelas

Karena interning String yang terkenal , sangat tidak mungkin kita akan menggunakan konstruktor kelas String untuk membuat objek String baru . Meski begitu, ini sangat legal:

String value = new String("Baeldung");

Dalam kasus ini, konstruktor akan membuat objek String baru , yang merupakan perilaku yang diharapkan.

Alternatifnya, jika kita ingin membuat objek String baru menggunakan metode pabrik statis , kita dapat menggunakan beberapa implementasi metode valueOf () berikut :

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

Ada beberapa implementasi valueOf () yang kelebihan beban . Masing-masing akan mengembalikan objek String baru , bergantung pada tipe argumen yang diteruskan ke metode (misalnya int , long , boolean , char, dan sebagainya).

Nama tersebut mengungkapkan dengan cukup jelas apa yang dilakukan metode tersebut. Itu juga berpegang pada standar mapan dalam ekosistem Java untuk penamaan metode pabrik statis.

3.2. The Opsional Kelas

Contoh rapi lainnya dari metode pabrik statis di JDK adalah kelas Opsional . Kelas ini mengimplementasikan beberapa metode pabrik dengan nama yang cukup berarti , termasuk empty () , of () , dan ofNullable () :

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

3.3. The Collections Kelas

Sangat mungkin contoh yang paling representatif dari metode pabrik statis di JDK adalah Koleksi kelas. Ini adalah kelas non-instantiable yang hanya mengimplementasikan metode statis.

Banyak di antaranya adalah metode pabrik yang juga mengembalikan koleksi, setelah menerapkan beberapa jenis algoritme ke koleksi yang disediakan.

Berikut beberapa contoh umum metode pabrik kelas:

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

Jumlah metode pabrik statis di JDK sangat banyak, jadi kami akan mempersingkat daftar contoh agar singkatnya.

Namun demikian, contoh di atas seharusnya memberi kita gambaran yang jelas tentang bagaimana metode pabrik statis ada di mana-mana di Java.

4. Metode Pabrik Statis Kustom

Tentu saja, kami dapat menerapkan metode pabrik statis kami sendiri. Tetapi kapan itu benar-benar layak dilakukan, daripada membuat instance kelas melalui konstruktor biasa?

Mari kita lihat contoh sederhana.

Mari pertimbangkan kelas Pengguna yang naif ini :

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 }

Dalam kasus ini, tidak ada peringatan yang terlihat untuk menunjukkan bahwa metode pabrik statis bisa lebih baik daripada konstruktor standar.

Bagaimana jika kita ingin semua instance Pengguna mendapatkan nilai default untuk bidang negara ?

Jika kita menginisialisasi bidang dengan nilai default, kita harus memfaktorkan ulang konstruktor juga, sehingga membuat desain menjadi lebih kaku.

Sebagai gantinya, kita dapat menggunakan metode pabrik statis:

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

Inilah cara kami mendapatkan instance Pengguna dengan nilai default yang ditetapkan ke bidang negara :

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

5. Memindahkan Logika dari Konstruktor

Kelas User kita dapat dengan cepat berubah menjadi desain yang cacat jika kita memutuskan untuk mengimplementasikan fitur yang memerlukan penambahan logika lebih lanjut ke konstruktor (alarm bel harus berbunyi saat ini).

Misalkan kita ingin memberikan kelas dengan kemampuan untuk mencatat waktu di mana setiap objek User dibuat.

Jika kita memasukkan logika ini ke dalam konstruktor, kita akan melanggar Prinsip Tanggung Jawab Tunggal . Kami akan berakhir dengan konstruktor monolitik yang melakukan lebih dari sekadar menginisialisasi bidang.

Kami dapat menjaga desain kami tetap bersih dengan metode pabrik statis:

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) { LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now()); return new User(name, email, country); } } 

Inilah cara kami membuat instance Pengguna yang ditingkatkan :

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

6. Instance-Controlled Instantiation

Seperti yang ditunjukkan di atas, kita dapat merangkum potongan logika ke dalam metode pabrik statis sebelum mengembalikan objek Pengguna yang diinisialisasi sepenuhnya . Dan kita dapat melakukan ini tanpa mencemari konstruktor dengan tanggung jawab melakukan banyak tugas yang tidak terkait.

For instance, 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; } } 

The implementation of the getSingletonInstance() method is thread-safe, with a small performance penalty, due to the synchronized block.

In this case, we used lazy initialization to demonstrate the implementation of an instance-controlled static factory method.

It's worth mentioning, however, that the best way to implement a Singleton is with a Java enum type, as it's both serialization-safe and thread-safe. For the full details on how to implement Singletons using different approaches, please check this article.

As expected, getting a User object with this method looks very similar to the previous examples:

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

7. Conclusion

In this article, we explored a few use cases where static factory methods can be a better alternative to using plain Java constructors.

Moreover, this refactoring pattern is so tightly rooted to a typical workflow that most IDEs will do it for us.

Of course, Apache NetBeans, IntelliJ IDEA, and Eclipse will perform the refactoring in slightly different ways, so please make sure first to check your IDE documentation.

As with many other refactoring patterns, we should use static factory methods with due caution, and only when it's worth the trade-off between producing more flexible and clean designs and the cost of having to implement additional methods.

Seperti biasa, semua contoh kode yang ditampilkan dalam artikel ini tersedia di GitHub.