Czytałem artykuł Singletona na Wikipedii i trafiłem na taki przykład:
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() {}
/**
* SingletonHolder is loaded on the first execution of Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Chociaż bardzo podoba mi się sposób, w jaki zachowuje się ten Singleton, nie widzę, jak dostosować go do włączenia argumentów do konstruktora. Jaki jest preferowany sposób zrobienia tego w Javie? Czy musiałbym zrobić coś takiego?
public class Singleton
{
private static Singleton singleton = null;
private final int x;
private Singleton(int x) {
this.x = x;
}
public synchronized static Singleton getInstance(int x) {
if(singleton == null) singleton = new Singleton(x);
return singleton;
}
}
Dzięki!
Edycja: Myślę, że zacząłem burzę kontrowersji z moim pragnieniem użycia Singletona. Pozwólcie, że wyjaśnię moją motywację i mam nadzieję, że ktoś zasugeruje lepszy pomysł. Do równoległego wykonywania zadań używam struktury grid computing. Ogólnie mam coś takiego:
// AbstractTask implements Serializable
public class Task extends AbstractTask
{
private final ReferenceToReallyBigObject object;
public Task(ReferenceToReallyBigObject object)
{
this.object = object;
}
public void run()
{
// Do some stuff with the object (which is immutable).
}
}
Dzieje się tak, że nawet jeśli przekazuję tylko odniesienie do moich danych do wszystkich zadań, kiedy zadania są serializowane, dane są kopiowane w kółko. Chcę udostępnić obiekt wszystkim zadaniom. Oczywiście mógłbym zmodyfikować klasę w następujący sposób:
// AbstractTask implements Serializable
public class Task extends AbstractTask
{
private static ReferenceToReallyBigObject object = null;
private final String filePath;
public Task(String filePath)
{
this.filePath = filePath;
}
public void run()
{
synchronized(this)
{
if(object == null)
{
ObjectReader reader = new ObjectReader(filePath);
object = reader.read();
}
}
// Do some stuff with the object (which is immutable).
}
}
Jak widać, nawet tutaj mam problem z tym, że przekazanie innej ścieżki do pliku nic nie znaczy po przejściu pierwszego. Dlatego podoba mi się pomysł na sklep, który został zamieszczony w odpowiedziach. W każdym razie zamiast włączać logikę do ładowania pliku w metodzie run, chciałem wyabstrahować tę logikę do klasy Singleton. Nie podam kolejnego przykładu, ale mam nadzieję, że zrozumiesz pomysł. Proszę, pozwól mi wysłuchać twoich pomysłów na bardziej elegancki sposób realizacji tego, co próbuję zrobić. Jeszcze raz dziękuję!
Odpowiedzi:
Powiem bardzo jasno: singleton z parametrami nie jest singletonem .
Singleton z definicji to obiekt, którego instancję chcesz utworzyć nie więcej niż raz. Jeśli próbujesz przesłać parametry do konstruktora, jaki jest sens singletona?
Masz dwie możliwości. Jeśli chcesz, aby Twój singleton został zainicjowany jakimiś danymi, możesz załadować go z danymi po instancji , na przykład:
Jeśli operacja wykonywana przez singleton jest cykliczna i za każdym razem z różnymi parametrami, równie dobrze możesz przekazać parametry do głównej wykonywanej metody:
W każdym razie tworzenie instancji będzie zawsze bez parametrów. W przeciwnym razie Twój singleton nie jest singletonem.
źródło
Myślę, że potrzebujesz czegoś takiego jak fabryka, aby obiekty o różnych parametrach były tworzone i ponownie używane. Można to zaimplementować za pomocą zsynchronizowanego
HashMap
lubConcurrentHashMap
zmapowanego parametru (Integer
na przykład) do swojej „pojedynczej” parametryzowanej klasy.Chociaż możesz dojść do punktu, w którym powinieneś zamiast tego używać zwykłych, nie-singletonowych klas (na przykład potrzebujących 10.000 różnie sparametryzowanych singletonów).
Oto przykład takiego sklepu:
Aby pchnąć to jeszcze dalej, Java
enum
s można również traktować (lub używać jako) sparametryzowanych singletonów, chociaż dopuszcza tylko statyczne warianty o stałej liczbie.Jeśli jednak potrzebujesz rozwiązania rozproszonego 1 , rozważ zastosowanie rozwiązania bocznego buforowania. Na przykład: EHCache, Terracotta itp.
1 w sensie obejmowania wielu maszyn wirtualnych na prawdopodobnie wielu komputerach.
źródło
Możesz dodać konfigurowalną metodę inicjalizacji, aby oddzielić wystąpienie od pobierania.
Następnie możesz zadzwonić
Singleton.init(123)
raz, aby go skonfigurować, na przykład podczas uruchamiania aplikacji.źródło
Możesz również użyć wzorca Builder, jeśli chcesz pokazać, że niektóre parametry są obowiązkowe.
Następnie możesz utworzyć / utworzyć wystąpienie / sparametryzować go w następujący sposób:
źródło
„ Singleton z parametrami nie jest singletonem ” nie jest całkowicie poprawny . Musimy to przeanalizować z perspektywy aplikacji, a nie z perspektywy kodu.
Tworzymy klasę pojedynczą, aby utworzyć pojedyncze wystąpienie obiektu w jednym uruchomieniu aplikacji. Mając konstruktor z parametrem, możesz wbudować elastyczność w swój kod, aby zmieniać niektóre atrybuty pojedynczego obiektu za każdym razem, gdy uruchamiasz aplikację. To nie jest naruszenie wzorca Singleton. Wygląda to na naruszenie, jeśli patrzysz na to z perspektywy kodu.
Wzorce projektowe mają nam pomóc w pisaniu elastycznego i rozszerzalnego kodu, a nie przeszkadzać w pisaniu dobrego kodu.
źródło
Użyj metod pobierających i ustawiających, aby ustawić zmienną i ustawić domyślny konstruktor jako prywatny. Następnie użyj:
źródło
Zaskoczony, że nikt nie wspomniał, w jaki sposób jest tworzony / odzyskiwany rejestrator. Na przykład poniżej pokazano, jak jest pobierany rejestrator Log4J .
Istnieje kilka poziomów pośrednich, ale kluczowa część znajduje się poniżej metody, która prawie wszystko mówi o tym, jak działa. Używa tablicy skrótów do przechowywania wychodzących rejestratorów, a klucz pochodzi z nazwy. Jeśli rejestrator nie istnieje dla podanej nazwy, używa fabryki do utworzenia rejestratora, a następnie dodaje go do tabeli skrótów.
źródło
Modyfikacja wzorca Singleton, który używa inicjalizacji Billa Pugha w idiomie posiadacza żądania . Jest to bezpieczne wątkowo bez narzutu wyspecjalizowanych konstrukcji językowych (tj. Ulotnych lub synchronizowanych):
źródło
finally { RInterfaceHL.rloopHandler = null; }
wgetInstance
, ponieważ odniesienie statyczna może spowodować przeciek pamięci, jeśli nie jesteś ostrożny. W twoim przypadku wygląda na to, że nie stanowi to problemu, ale mógłbym sobie wyobrazić scenariusz, w którym przekazywany obiekt jest duży i używany przezRInterfaceHL
ctora tylko do uzyskania pewnych wartości, a nie do zachowania odniesienia do niego.return SingletonHolder.INSTANCE
działałby równie dobrze wgetInstance
. Nie sądzę, żeby była tu potrzeba hermetyzacji, ponieważ klasa zewnętrzna zna już wnętrze klasy wewnętrznej, są one ściśle powiązane: wie, żerloopHandler
potrzebuje init przed wywołaniem. Również prywatny konstruktor nie ma żadnego efektu, ponieważ prywatne rzeczy klasy wewnętrznej są po prostu dostępne dla klasy zewnętrznej.Powodem, dla którego nie możesz zrozumieć, jak osiągnąć to, co próbujesz zrobić, jest prawdopodobnie to, że to, co próbujesz zrobić, nie ma sensu. Chcesz wywołać
getInstance(x)
z różnymi argumentami, ale zawsze zwracać ten sam obiekt? Jakiego zachowania chcesz, kiedy dzwonisz,getInstance(2)
a potemgetInstance(5)
?Jeśli chcesz, aby ten sam obiekt, ale jego wewnętrzna wartość była inna, co jest jedynym powodem, dla którego nadal jest singletonem, nie musisz w ogóle przejmować się konstruktorem; po prostu ustawiasz wartość
getInstance()
na wyjściu obiektu. Oczywiście rozumiesz, że wszystkie inne odwołania do singletona mają teraz inną wartość wewnętrzną.Jeśli chcesz
getInstance(2)
igetInstance(5)
wrócić różnych przedmiotów, z drugiej strony, nie używasz wzorca Singleton, używasz wzorca Factory.źródło
W swoim przykładzie nie używasz singletona. Zwróć uwagę, że jeśli wykonasz następujące czynności (zakładając, że Singleton.getInstance był faktycznie statyczny):
Wtedy wartość obj2.x to 3, a nie 4. Jeśli musisz to zrobić, zrób z tego zwykłą klasę. Jeśli liczba wartości jest mała i stała, możesz rozważyć użycie pliku
enum
. Jeśli masz problem z nadmiernym generowaniem obiektów (co zwykle nie ma miejsca), możesz rozważyć buforowanie wartości (i sprawdzić źródła lub uzyskać pomoc, ponieważ jest oczywiste, jak budować pamięci podręczne bez ryzyka wycieków pamięci).Warto również przeczytać ten artykuł, ponieważ singletony mogą być bardzo łatwo nadużywane.
źródło
Innym powodem, dla którego Singletony są anty-wzorcem, jest to, że jeśli są napisane zgodnie z zaleceniami, z prywatnym konstruktorem, bardzo trudno jest je podklasować i konfigurować do użycia w niektórych testach jednostkowych. Byłoby to wymagane na przykład przy utrzymywaniu starego kodu.
źródło
Jeśli chcesz stworzyć klasę Singleton służącą jako Context, dobrym sposobem jest posiadanie pliku konfiguracyjnego i odczytanie parametrów z pliku wewnątrz instancji ().
Jeśli parametry zasilające klasę Singleton są pobierane dynamicznie podczas działania programu, po prostu użyj statycznej HashMap przechowującej różne instancje w klasie Singleton, aby mieć pewność, że dla każdego parametru zostanie utworzona tylko jedna instancja.
źródło
To nie jest singleton, ale może być czymś, co może rozwiązać twój problem.
źródło
Jeśli przyjmiemy problem jako "jak zrobić singleton ze stanem", to nie jest konieczne przekazywanie stanu jako parametru konstruktora. Zgadzam się z postami inicjalizującymi stany lub używając metody set po pobraniu instancji singletona.
Kolejne pytanie brzmi: czy dobrze jest mieć singleton ze stanem?
źródło
Nie moglibyśmy zrobić czegoś takiego:
źródło
Wbrew temu, co niektórzy twierdzą, oto singleton z parametrami w konstruktorze
Wzór singletona mówi:
które są szanowane w tym przykładzie.
Dlaczego nie ustawić bezpośrednio właściwości? To podręcznikowy przypadek, aby pokazać, jak możemy uzyskać singleton mający konstruktor z parametrem, ale może to być przydatne w niektórych sytuacjach. Na przykład w przypadkach dziedziczenia, aby zmusić singletona do ustawienia niektórych właściwości nadklasy.
źródło
Boję się opublikować to jako odpowiedź, ale nie rozumiem, dlaczego nikt o tym nie myśli, może ta odpowiedź też została już udzielona, po prostu jej nie rozumiem.
Ponieważ
getInstance()
za każdym razem zwraca tę samą instancję, myślę, że to może zadziałać. Jeśli to jest za dużo, to usunę, interesuje mnie tylko ten temat.źródło
Myślę, że to powszechny problem. Oddzielenie „inicjalizacji” singletona od „get” singletona może działać (w tym przykładzie zastosowano odmianę podwójnie sprawdzanego blokowania).
źródło
Singleton jest oczywiście „anty-wzorcem” (przy założeniu definicji statycznej o zmiennym stanie).
Jeśli chcesz mieć stały zestaw niezmiennych obiektów wartości, najlepszym rozwiązaniem są wyliczenia. W przypadku dużego, prawdopodobnie otwartego zestawu wartości można użyć repozytorium w jakiejś formie - zwykle opartej na
Map
implementacji. Oczywiście, kiedy masz do czynienia ze statyką, bądź ostrożny z wątkami (albo zsynchronizuj wystarczająco szeroko, albo użyjConcurrentMap
albo sprawdzenia, czy inny wątek cię nie pokonał, albo użyj jakiejś formy futures).źródło
Singletony są ogólnie uważane za anty-wzorce i nie powinny być używane. Nie ułatwiają testowania kodu.
Singleton z argumentem i tak nie ma sensu - co by się stało, gdybyś napisał:
Twój singleton również nie jest bezpieczny dla wątków, ponieważ wiele wątków może wykonywać równoczesne wywołania
getInstance
powodujące utworzenie więcej niż jednej instancji (prawdopodobnie z różnymi wartościamix
).źródło