Singleton z argumentami w Javie


142

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ę!


1
Wzór fabryczny jest tym, czego chcesz. Idealnie byłoby, gdyby zadania gridowe były całkowicie niezależne od wszystkiego innego i otrzymywały wszystkie dane, których potrzebują do wykonania i zwrócenia wyników. Jednak nie zawsze jest to najbardziej wykonalne rozwiązanie, więc serializacja danych do pliku nie jest takim złym pomysłem. Myślę, że cała sprawa z singletonem jest trochę czerwona; nie chcesz singletona.
oxbow_lakes

2
Szkoda, że ​​użyłeś terminu Singleton, który towarzyszy takiemu bagażowi. Właściwy termin dla tego wzorca to właściwie internowanie. Internowanie to metoda zapewniająca, że ​​wartości abstrakcyjne są reprezentowane tylko przez jedną instancję. Najczęstszym zastosowaniem jest internowanie stringów: en.wikipedia.org/wiki/String_intern_pool.
notnoop

Możesz rzucić okiem na Terracotta. Utrzymuje tożsamość obiektu w całym klastrze. Gdy wysyłasz odwołanie do danych znajdujących się już w klastrze, nie są one ponownie serializowane.
Taylor Gautier

21
Pomijając kwestię tego, czy kiedykolwiek powinien być używany wzorzec singleton, chciałbym zauważyć, że prawie każda odpowiedź wydaje się zakładać, że celem podania argumentu jest umożliwienie utworzenia „wielu singletonów”, które są rozróżniane przez wartość tego parametru. Ale innym możliwym celem jest zapewnienie dostępu do obiektu zewnętrznego, który jest jedynym obiektem tego rodzaju, którego unikalna instancja klasy pojedynczej będzie kiedykolwiek potrzebować. Dlatego musimy odróżnić parametr zapewniany do takiego dostępu od parametru przeznaczonego do tworzenia „wielu pojedynczych instancji”.
Carl

2
Inny scenariusz dla „singletona z parametrami”: aplikacja internetowa, która zbuduje swój unikalny niezmienny singleton na podstawie informacji pochodzących z pierwszego nadchodzącego żądania (wątku). Domeną wniosek może określić zachowanie jakiegoś Singleton dla przykładu
fustaki

Odpowiedzi:


171

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:

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

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:

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

W każdym razie tworzenie instancji będzie zawsze bez parametrów. W przeciwnym razie Twój singleton nie jest singletonem.


1
+1 Tak prawdopodobnie zrobiłbym to podczas kodowania. Jednak w C # użyłbym tylko właściwości. Java, prawdopodobnie w ten sposób.
Zack

131
przepraszam, to nieprawda. są sytuacje, w których musisz przekazać dynamicznie utworzone parametry, które pozostają takie same przez cały czas wykonywania aplikacji otworu. więc nie możesz użyć stałej w singletonie, ale musisz przekazać tę stałą, gdy zostanie utworzona. po minięciu raz jest taka sama dla czasu dołka. ustawiający nie wykona zadania, jeśli potrzebujesz tej określonej stałej w konstruktorze.
masi

1
@masi, jak mówi autor - to nie jest singleton. Jeśli chcesz przekazywać stałą dynamicznie, może być konieczne utworzenie wielu takich klas z różnymi stałymi. Tak więc singleton nie ma sensu.
Dmitry Zaytsev

53
Jeśli potrzebujesz tylko jednego wystąpienia klasy na cały okres istnienia aplikacji, ale musisz podać temu wystąpieniu wartość w czasie uruchamiania, dlaczego nie jest to już singleton?
Oscar

4
„Jeśli próbujesz przesłać parametry do konstruktora, jaki jest sens singletona?” - Można by też powiedzieć: „Jeśli stworzysz jedną instancję dla całej aplikacji, jaki jest sens argumentów wiersza poleceń?”, A odpowiedź brzmi, że ma to sens. Można teraz powiedzieć, że różni się to znacznie od klasy singleton, z wyjątkiem sytuacji, gdy klasa jest w rzeczywistości klasą Main, która otrzymuje argumenty [] z metody main - to nawet to samo. Ostatni argument, który może się po prostu stać, jest taki, że jest to dość wyjątkowa sytuacja.
Prezydent Dreamspace

41

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 HashMaplub ConcurrentHashMapzmapowanego parametru ( Integerna 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:

public final class UsefulObjFactory {

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj {
        private UsefulObj(int parameter) {
            // init
        }
        public void someUsefulMethod() {
            // some useful operation
        }
    }

    public static UsefulObj get(int parameter) {
        synchronized (store) {
            UsefulObj result = store.get(parameter);
            if (result == null) {
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            }
            return result;
        }
    }
}

Aby pchnąć to jeszcze dalej, Java enums 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.


Tak, właśnie tego potrzebuję. Dziękuję Ci bardzo! Zgadzam się, że sposób, w jaki radziłem sobie z argumentami w moim przykładzie, nie miał większego sensu, ale nie pomyślałem o tym. Zobacz moje wyjaśnienie w komentarzach do odpowiedzi oxbow_lakes.

1
To NIE jest singleton; masz teraz więcej niż jeden z nich. LOL
oxbow_lakes

@Scott: Proponuję coś podobnego do sugestii Yuval poniżej. To ma trochę więcej sensu i masz „prawdziwego” singletona. edycja
Zack

Mam nadzieję, że nikt nie będzie miał nic przeciwko edycji nazw w kodzie; Mogę sobie wyobrazić, że jest to naprawdę mylące dla początkujących.
Wycofaj,

Tak, moglibyśmy nazwać je Multitronami i nadal osiągnąć ten sam cel, którego OP chciał przede wszystkim IMHO.
akarnokd

22

Możesz dodać konfigurowalną metodę inicjalizacji, aby oddzielić wystąpienie od pobierania.

public class Singleton {
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public static Singleton getInstance() {
        if(singleton == null) {
            throw new AssertionError("You have to call init first");
        }

        return singleton;
    }

    public synchronized static Singleton init(int x) {
        if (singleton != null)
        {
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        }

        singleton = new Singleton(x);
        return singleton;
    }

}

Następnie możesz zadzwonić Singleton.init(123)raz, aby go skonfigurować, na przykład podczas uruchamiania aplikacji.


13

Możesz również użyć wzorca Builder, jeśli chcesz pokazać, że niektóre parametry są obowiązkowe.

    public enum EnumSingleton {

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // Static getter
    public static EnumSingleton getSingleton() {
        return INSTANCE;
    }

    public void print() {
        System.out.println("Name "+name + ", age: "+age);
    }


    public static class SingletonBuilder {

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder(){
          name = null;
        }

        SingletonBuilder(String name) {
            this.name = name;
        }

        public SingletonBuilder age(double age) {
            this.age = age;
            return this;
        }

        public void build(){
            EnumSingleton.INSTANCE.build(this);
        }

    }


}

Następnie możesz utworzyć / utworzyć wystąpienie / sparametryzować go w następujący sposób:

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}

6

Singleton z parametrami nie jest singletonemnie 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.


12
To nie jest odpowiedź na pytanie PO, to powinien być komentarz.
Thierry J.

5

Użyj metod pobierających i ustawiających, aby ustawić zmienną i ustawić domyślny konstruktor jako prywatny. Następnie użyj:

Singleton.getInstance().setX(value);

1
Nie rozumiem, dlaczego ten głos został odrzucony. To ważna odpowiedź. : /
Zack

13
Ponieważ jest to bzdura odpowiedź. Na przykład wyobraź sobie system, w którym początkowa nazwa użytkownika i hasło pierwszego administratora są argumentami konstruktora. Teraz, jeśli zrobię to jako singleton i zrobię tak, jak mówisz, otrzymam metody pobierające i ustawiające dla administratora, co nie jest tym, czego chcesz. Więc chociaż twoja opcja może być ważna w niektórych przypadkach, tak naprawdę nie odpowiada ona na ogólny przypadek, o który było pytanie. (tak, ja pracuję w systemie opisałem i nie, nie miałbym użyć wzorca singleton, gdyby nie fakt, że przypisanie mówi „użyć wzorca singleton tutaj”)
Jasper

5

Zaskoczony, że nikt nie wspomniał, w jaki sposób jest tworzony / odzyskiwany rejestrator. Na przykład poniżej pokazano, jak jest pobierany rejestrator Log4J .

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

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.

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) {
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) {
268      Object o = ht.get(key);
269      if(o == null) {
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275      } else if(o instanceof Logger) {
276        return (Logger) o;
277      } 
...

4

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):

public final class RInterfaceHL {

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL() { }

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder {

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() {
            try {
                return new RInterfaceHL(rloopHandler);
            } catch (REngineException e) {
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            }
        }

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() {
        }

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() {
            return SingletonHolder.INSTANCE;
        }

    }

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException {
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try {
            instance = SingletonHolder.getInstance();
        } catch (ExceptionInInitializerError e) {

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        }

        return instance;
    }

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * {@link SingletonHolder.initRInterfaceHL}.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException {

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler);
    }
}

Myślę, że byłby to dobry pomysł, aby finally { RInterfaceHL.rloopHandler = null; }w getInstance, 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 przez RInterfaceHLctora tylko do uzyskania pewnych wartości, a nie do zachowania odniesienia do niego.
TWiStErRob

Pomysł: return SingletonHolder.INSTANCEdziałałby równie dobrze w getInstance. 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, że rloopHandlerpotrzebuje 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.
TWiStErRob

1
Link jest uszkodzony. Czy odnosiłeś się do en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
Jorge Lavín

3

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 potem getInstance(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)i getInstance(5)wrócić różnych przedmiotów, z drugiej strony, nie używasz wzorca Singleton, używasz wzorca Factory.


3

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):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

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 plikuenum . 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.


3

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.


3

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.


1

To nie jest singleton, ale może być czymś, co może rozwiązać twój problem.

public class KamilManager {

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() {
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;
}

  public KamilManager(Context context, KamilConfig KamilConfig) {
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  }
}

1

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?


1

Nie moglibyśmy zrobić czegoś takiego:

public class Singleton {

    private int x;

    // 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(int x) {
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    }
}

1

Wbrew temu, co niektórzy twierdzą, oto singleton z parametrami w konstruktorze

public class Singleton {

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() {
        // do nothing
    }

    private Singleton(String param) {
        aParameterStored = param;
    }

    public static Singleton getInstance() {
        return instance;
    }

    /*
     * ... stuff you would like the singleton do
     */
}

Wzór singletona mówi:

  • upewnij się, że kiedykolwiek istnieje tylko jedno wystąpienie klasy pojedynczej
  • zapewniają globalny dostęp do tej instancji.

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.


0

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.

public class example  {
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() {

    //In case someone uses the private method to create a new Instance
    if (instance != null){
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
  }

  public synchronized static example getIsntance(){
    if(instance == null){
      instance = new example();
    }
    return instance;
  }

public void methodDoingWork(){
    if(checkInit()){
      //DoSome
    }
  }

  private boolean checkInit(){
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  }

  public void setString(String string) {
    if(this.string == null){
      this.string = string;
    }else{
      throw new RuntimeException("You try to override an already setValue"); 
    }
  }

  public void setiInt(int iInt) {
    if(this.iInt == -1){
      this.iInt = iInt;
    }else{
      throw new RuntimeException("You try to override an already setValue");
    }
  }
}

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.


-1

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).

public class MySingleton {

    private static volatile MySingleton INSTANCE;

    @SuppressWarnings("UnusedAssignment")
    public static void initialize(
            final SomeDependency someDependency) {

        MySingleton result = INSTANCE;

        if (result != null) {
            throw new IllegalStateException("The singleton has already "
                    + "been initialized.");
        }

        synchronized (MySingleton.class) {
            result = INSTANCE;

            if (result == null) {
                INSTANCE = result = new MySingleton(someDependency);
            } 
        }
    }

    public static MySingleton get() {
        MySingleton  result = INSTANCE;

        if (result == null) {
            throw new IllegalStateException("The singleton has not been "
                    + "initialized. You must call initialize(...) before "
                    + "calling get()");
        }

       return result;
    }

    ...
}

Przypuszczam, że zawsze może zwrócić „wynik” również w metodzie inicjalizacji.
Michael Andrews

-2

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 Mapimplementacji. Oczywiście, kiedy masz do czynienia ze statyką, bądź ostrożny z wątkami (albo zsynchronizuj wystarczająco szeroko, albo użyj ConcurrentMapalbo sprawdzenia, czy inny wątek cię nie pokonał, albo użyj jakiejś formy futures).


4
Tylko anty-wzór, jeśli jest używany nieprawidłowo, chociaż taka jest definicja anty-wzorca. To, że widziałeś ich tam, gdzie nie pasowały w przeszłości, nie oznacza, że ​​nie mają dla nich miejsca.
geowa4

Prawidłowe użycie singletona polega na zademonstrowaniu niekompetentnego kodu.
Tom Hawtin - tackline

-6

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ł:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

Twój singleton również nie jest bezpieczny dla wątków, ponieważ wiele wątków może wykonywać równoczesne wywołania getInstancepowodujące utworzenie więcej niż jednej instancji (prawdopodobnie z różnymi wartościami x).


To dość dyskusyjne.
AlbertoPL

1
Tak, jest to dyskusyjne; stąd moje użycie słowa „ogólnie”. Myślę, że można uczciwie powiedzieć, że są one ogólnie uważane za zły pomysł
oxbow_lakes

Jest to dyskusyjne - niektórzy twierdzą, że tak zwane „anty-wzorce” pasują do definicji wzorców, po prostu są to złe wzorce.
Tom Hawtin - tackline

Rozumiem, że są złe. Zajmuję się przetwarzaniem rozproszonym i muszę udostępniać obiekt wielu zadaniom. Zamiast deterministycznie inicjować zmienną statyczną, chciałbym wyabstrahować logikę do postaci Singletona. Wyobrażam sobie, że mógłbym zsynchronizować getInstance. Czy to zadziała? Muszę tylko raz załadować plik dla wielu zadań i dopiero po wysłaniu pierwszego zadania. (Nie chcę serializować moich danych.) Pomyślałem, że uczynię z mojego AbstractFileReader argument do metody getInstance, aby uczynić Singleton bardziej elastycznym. Cenię Twój wkład.

Myślę, że możesz źle zrozumieć, co oznacza „rozproszone”? Istnieją inne sposoby osiągnięcia tego, czego chcesz: czy rozważałeś zastrzyk zależności? Albo JNDI?
oxbow_lakes
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.