Secara tradisional, singleton biasanya diimplementasikan sebagai
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
Dengan Java enum, kita dapat mengimplementasikan singleton as
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
Sehebat versi ke-2, apakah ada kerugian untuk itu?
(Saya memberikan beberapa pemikiran dan saya akan menjawab pertanyaan saya sendiri; semoga Anda memiliki jawaban yang lebih baik)
Jawaban:
Beberapa masalah dengan lajang enum:
Berkomitmen pada strategi implementasi
Biasanya, "singleton" mengacu pada strategi implementasi, bukan spesifikasi API. Sangat jarang untuk
Foo1.getInstance()
secara terbuka menyatakan bahwa itu akan selalu mengembalikan contoh yang sama. Jika diperlukan, implementasiFoo1.getInstance()
dapat berkembang, misalnya, untuk mengembalikan satu instance per utas.Dengan
Foo2.INSTANCE
kita secara terbuka menyatakan bahwa hal ini adalah dengan contoh, dan tidak ada kesempatan untuk mengubah itu. Strategi implementasi memiliki satu contoh terbuka dan berkomitmen untuk.Masalah ini tidak melumpuhkan. Misalnya,
Foo2.INSTANCE.doo()
dapat mengandalkan objek pembantu lokal utas, untuk secara efektif memiliki instance per-utas.Memperluas kelas Enum
Foo2
memperluas kelas superEnum<Foo2>
. Kami biasanya ingin menghindari kelas super; terutama dalam kasus ini, kelas super yang dipaksakanFoo2
tidak ada hubungannya dengan apaFoo2
yang seharusnya. Itu adalah polusi pada jenis hierarki aplikasi kita. Jika kita benar-benar menginginkan kelas super, biasanya kelas aplikasi, tetapi kita tidak bisa,Foo2
kelas supernya sudah diperbaiki.Foo2
mewarisi beberapa metode contoh lucu sepertiname(), cardinal(), compareTo(Foo2)
, yang hanya membingungkanFoo2
pengguna.Foo2
tidak dapat memilikiname()
metode sendiri bahkan jika metode itu diinginkan dalamFoo2
antarmuka.Foo2
juga mengandung beberapa metode statis lucuyang tampaknya tidak masuk akal bagi pengguna. Singleton biasanya tidak seharusnya memiliki metode statis pulbic (selain dari
getInstance()
)Serializability
Sangat umum bagi lajang untuk menyatakan diri. Lajang ini umumnya tidak boleh serial. Saya tidak bisa memikirkan contoh realistis di mana masuk akal untuk mengangkut singleton stateful dari satu VM ke VM lain; singleton berarti "unik di dalam VM", bukan "unik di alam semesta".
Jika serialisasi benar-benar masuk akal untuk singleton stateful, singleton harus secara eksplisit dan tepat menentukan apa artinya deserialisasi singleton di VM lain di mana singleton dari tipe yang sama mungkin sudah ada.
Foo2
secara otomatis berkomitmen pada strategi serialisasi / deserialisasi sederhana. Itu hanya kecelakaan yang menunggu untuk terjadi. Jika kita memiliki pohon data yang secara konseptual merujuk variabel keadaanFoo2
dalam VM1 di t1, melalui serialisasi / deserialisasi nilai menjadi nilai yang berbeda - nilai variabel yang samaFoo2
di dalam VM2 di t2, membuat sulit untuk mendeteksi bug. Bug ini tidak akan terjadi pada yang tidak dapat digunakanFoo1
diam-diam.Batasan pengkodean
Ada hal yang bisa dilakukan di kelas normal, tetapi dilarang di
enum
kelas. Misalnya, mengakses bidang statis di konstruktor. Programmer harus lebih berhati-hati karena dia bekerja di kelas khusus.Kesimpulan
Dengan membonceng enum, kami menyimpan 2 baris kode; tetapi harga terlalu tinggi, kita harus membawa semua kantong dan pembatasan enum, kita secara tidak sengaja mewarisi "fitur" enum yang memiliki konsekuensi yang tidak diinginkan. Satu-satunya keuntungan yang diduga - serializability otomatis - ternyata menjadi kerugian.
sumber
Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);
misalnya contoh yang sangat bagus adalah :;Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);
^), diuji di bawah Java 7 dan Java 8 ...Instance enum tergantung pada loader kelas. yaitu jika Anda memiliki pemuat kelas kedua yang tidak memiliki pemuat kelas pertama sebagai induk yang memuat kelas enum yang sama, Anda bisa mendapatkan beberapa instance dalam memori.
Contoh kode
Buat enum berikut, dan masukkan file .class ke dalam toples dengan sendirinya. (tentu saja toples akan memiliki struktur paket / folder yang benar)
Sekarang jalankan tes ini, pastikan tidak ada salinan enum di atas di jalur kelas:
Dan kita sekarang memiliki dua objek yang mewakili nilai enum "sama".
Saya setuju bahwa ini adalah kasus sudut yang langka dan dibuat-buat, dan hampir selalu enum dapat digunakan untuk java singleton. Saya melakukannya sendiri. Tetapi pertanyaan yang diajukan tentang potensi kerugian dan catatan kehati-hatian ini layak untuk diketahui.
sumber
enum pattern tidak dapat digunakan untuk Kelas apa pun yang akan melempar pengecualian pada konstruktor. Jika ini diperlukan, gunakan pabrik:
sumber