Java enum ile bir singleton uygulamanın dezavantajları nelerdir?


14

Geleneksel olarak, bir singleton genellikle

public class Foo1
{
    private static final Foo1 INSTANCE = new Foo1();

    public static Foo1 getInstance(){ return INSTANCE; }

    private Foo1(){}

    public void doo(){ ... }
}

Java enum ile, bir singleton uygulayabiliriz

public enum Foo2
{
    INSTANCE;

    public void doo(){ ... }
}

2. versiyon kadar harika, bunun bir dezavantajı var mı?

(Bazı düşünceler verdim ve kendi soruma cevap vereceğim; umarım daha iyi cevaplarınız vardır)


16
Dezavantajı, bir singleton olmasıdır. Tamamen abartılı ( öksürük ) bir “desen”
Thomas Eding

Yanıtlar:


32

Enum singleton ile ilgili bazı sorunlar:

Bir uygulama stratejisi taahhüdü

Tipik olarak, "singleton" bir API spesifikasyonu değil, bir uygulama stratejisini belirtir. Foo1.getInstance()Her zaman aynı örneği döndüreceğini açıkça bildirmek çok nadirdir . Gerekirse, Foo1.getInstance()örneğin iş parçacığı başına bir örnek döndürmek için evrimi gelişebilir.

İle Foo2.INSTANCEbiz bunu genel olarak örnek olduğunu beyan örneği ve bunu değiştirmek için hiçbir şansı yoktur. Tek bir örneğe sahip olma uygulama stratejisi açık ve taahhüt edilmiştir.

Bu sorun sakat değil. Örneğin, etkili bir iş parçacığı örneği için sahip bir Foo2.INSTANCE.doo()iş parçacığı yerel yardımcı nesnesine güvenebilirsiniz .

Enum sınıfını genişletme

Foo2bir süper sınıfı genişletir Enum<Foo2>. Genellikle süper sınıflardan kaçınmak isteriz; özellikle bu durumda, zorlanan süper sınıfın Foo2olması gerekenle hiçbir ilgisi yoktur Foo2. Bu, uygulamamızın tür hiyerarşisine bir kirlilik. Gerçekten bir süper sınıf istiyorsak, genellikle bir uygulama sınıfıdır, ancak yapamayız, Foo2'süper sınıfı sabittir.

Foo2kullanıcılarına name(), cardinal(), compareTo(Foo2)kafa karıştırıcı olan bazı komik örnek yöntemlerini devralır Foo2. bu yöntem arayüzünde istense bile Foo2kendi name()yöntemine sahip olamaz Foo2.

Foo2 ayrıca bazı komik statik yöntemler içerir

    public static Foo2[] values() { ... }
    public static Foo2 valueOf(String name) { ... }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

kullanıcılara saçma geliyor. Bir tekil genellikle zaten pulbic statik yöntemlerini olmamalıdır (dışındaki getInstance())

serilerştirilebirlik

Singletonların durum bilgisi olması çok yaygındır. Bu singletons genellikle gerektiğini değil seri hale edilebilir. Durumsal bir singletonu bir VM'den başka bir VM'ye taşımanın mantıklı olduğu gerçekçi bir örnek düşünemiyorum; singleton, "evrende benzersiz" değil, "sanal makine içinde benzersiz" anlamına gelir.

Serileştirme durumsal bir singleton için gerçekten anlamlıysa, singleton, aynı türden bir singletonun zaten mevcut olabileceği başka bir VM'de bir singletonun serileştirilmesinin ne anlama geldiğini açıkça ve kesin olarak belirtmelidir.

Foo2otomatik olarak basit bir serileştirme / serileştirme stratejisi uygular. Bu sadece gerçekleşmeyi bekleyen bir kazadır. Kavramsal Foo2olarak t1'de VM1'de bir durum değişkenine başvuran bir veri ağacımız varsa , serileştirme / serileştirme yoluyla değer farklı bir değer olur - Foo2t2'deki VM2'de aynı değişkenin değeri, algılanması zor bir hata oluşturur. Bu hata, serileştirilemez olana Foo1sessizce gerçekleşmeyecek.

Kodlama kısıtlamaları

Normal sınıflarda yapılabilecek, ancak enumsınıflarda yasak olan şeyler vardır . Örneğin, yapıcıdaki statik bir alana erişim. Özel bir sınıfta çalıştığı için programcı daha dikkatli olmalıdır.

Sonuç

Numaralandırmada piggybacking yaparak 2 satır kod kaydediyoruz; ama fiyat çok yüksek, biz tüm bavullar ve numaralandırma kısıtlamaları taşımak zorunda, biz yanlışlıkla sonuçların enum "özellikleri" miras. İddia edilen tek avantaj - otomatik serileştirilebilirlik - bir dezavantaj olduğu ortaya çıkıyor.


2
-1: Serileştirme konusundaki tartışmanız yanlış. Serumun serileştirme sırasında sıradan örneklerden çok farklı muamele görmesi nedeniyle mekanizma basit değildir. Sorun etmez tarif değil gerçek seri kaldırma mekanizması bir "durum değişkeni" değiştirmez olarak ortaya çıkar.
eşarp

şu karışıklık örneğine bakın: coderanch.com/t/498782/java/java/…
vazgeçilmez

2
Aslında bağlantılı tartışma benim fikrimi vurguluyor. Var olduğunu iddia ettiğiniz sorun olarak neyi anladığımı açıklayayım. Bazı A nesnesi, ikinci bir B nesnesine referansta bulunur. Şimdi, enum tabanlı singletonun daha önce serileştirilmiş bir örneğini serileştiriyoruz (serileştirme sırasında B '! = B'ye atıfta bulunuluyor). Aslında olan, A ve S B referansıdır, çünkü B 'serileştirilmez. A ve S'nin artık aynı nesneye başvurmadığını ifade etmek istediğinizi düşündüm.
scarfridge

1
Belki de aynı sorundan gerçekten bahsetmiyoruz?
scarridge

1
@Kevin Krumwiede: Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);örneğin, gerçekten güzel bir örnek şöyle olurdu Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);:; ^), Java 7 ve Java 8 altında test edilmiştir…
Holger

6

Bir enum örneği sınıf yükleyicisine bağlıdır. örn., aynı enum sınıfını yükleyen bir üst öğe olarak birinci sınıf yükleyiciye sahip olmayan bir ikinci sınıf yükleyiciniz varsa, bellekte birden çok örnek alabilirsiniz.


Kod Örneği

Aşağıdaki numaralandırmayı oluşturun ve .class dosyasını tek başına bir kavanoza koyun. (tabii ki kavanoz doğru paket / klasör yapısına sahip olacaktır)

package mad;
public enum Side {
  RIGHT, LEFT;
}

Şimdi, sınıf yolunda yukarıdaki numaralandırmanın hiçbir kopyasının olmadığından emin olarak bu testi çalıştırın:

@Test
public void testEnums() throws Exception
{
    final ClassLoader root = MadTest.class.getClassLoader();

    final File jar = new File("path to jar"); // Edit path
    assertTrue(jar.exists());
    assertTrue(jar.isFile());

    final URL[] urls = new URL[] { jar.toURI().toURL() };
    final ClassLoader cl1 = new URLClassLoader(urls, root);
    final ClassLoader cl2 = new URLClassLoader(urls, root);

    final Class<?> sideClass1 = cl1.loadClass("mad.Side");
    final Class<?> sideClass2 = cl2.loadClass("mad.Side");

    assertNotSame(sideClass1, sideClass2);

    assertTrue(sideClass1.isEnum());
    assertTrue(sideClass2.isEnum());
    final Field f1 = sideClass1.getField("RIGHT");
    final Field f2 = sideClass2.getField("RIGHT");
    assertTrue(f1.isEnumConstant());
    assertTrue(f2.isEnumConstant());

    final Object right1 = f1.get(null);
    final Object right2 = f2.get(null);
    assertNotSame(right1, right2);
}

Ve şimdi "aynı" enum değerini temsil eden iki nesnemiz var.

Bunun nadir ve anlaşılır bir köşe vakası olduğunu kabul ediyorum ve neredeyse her zaman bir enum bir java singleton için kullanılabilir. Kendim yaparım. Ancak soru potansiyel olumsuzlukları sordu ve bu dikkat notu bilmeye değer.


Bu konuda herhangi bir referans bulabilir misiniz?

Şimdi örnek kodla ilk cevabımı düzenledim ve geliştirdim. Umarım bu konuyu açıklamaya ve MichaelT'nin sorgusuna cevap vermeye yardımcı olurlar.
Mad G

@MichaelT: Umarım sorunuzu cevaplar :-)
Mad G

Yani, single için enum (sınıf yerine) kullanım nedeni sadece onun güvenliği ise, şimdi bunun için bir sebep yok ... Mükemmel, +1
Gangnus

1
"Geleneksel" singleton uygulaması iki farklı sınıf yükleyicide bile beklendiği gibi davranacak mı?
Ron Klein

3

enum paterni yapıcıda istisna atacak herhangi bir Sınıf için kullanılamaz. Bu gerekirse bir fabrika kullanın:

class Elvis {
    private static Elvis self = null;
    private int age;

    private Elvis() throws Exception {
        ...
    }

    public synchronized Elvis getInstance() throws Exception {
        return self != null ? self : (self = new Elvis());
    }

    public int getAge() {
        return age;
    }        
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.