Singleton mit Argumenten in Java


142

Ich habe den Singleton-Artikel auf Wikipedia gelesen und bin auf dieses Beispiel gestoßen:

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;
    }
}

Obwohl mir das Verhalten dieses Singleton sehr gut gefällt, kann ich nicht sehen, wie ich es anpassen kann, um Argumente in den Konstruktor aufzunehmen. Was ist der bevorzugte Weg, dies in Java zu tun? Würde ich so etwas tun müssen?

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;
    }
}

Vielen Dank!


Bearbeiten: Ich glaube, ich habe mit meinem Wunsch, Singleton zu verwenden, einen Sturm der Kontroversen ausgelöst. Lassen Sie mich meine Motivation erklären und hoffentlich kann jemand eine bessere Idee vorschlagen. Ich verwende ein Grid-Computing-Framework, um Aufgaben parallel auszuführen. Im Allgemeinen habe ich so etwas:

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

Obwohl ich lediglich einen Verweis auf meine Daten an alle Aufgaben weitergebe, werden die Daten bei der Serialisierung der Aufgaben immer wieder kopiert. Ich möchte das Objekt auf alle Aufgaben verteilen. Natürlich könnte ich die Klasse so ändern:

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

Wie Sie sehen können, habe ich auch hier das Problem, dass das Übergeben eines anderen Dateipfads nichts bedeutet, nachdem der erste übergeben wurde. Aus diesem Grund gefällt mir die Idee für ein Geschäft, das in den Antworten veröffentlicht wurde. Anstatt die Logik zum Laden der Datei in die Ausführungsmethode aufzunehmen, wollte ich diese Logik in eine Singleton-Klasse abstrahieren. Ich werde kein weiteres Beispiel nennen, aber ich hoffe, Sie haben die Idee. Bitte lassen Sie mich Ihre Ideen für einen eleganteren Weg hören, um das zu erreichen, was ich versuche. Danke nochmal!


1
Das Fabrikmuster ist das, was Sie wollen. Im Idealfall sollten Grid-Aufgaben völlig unabhängig von allem anderen sein und alle Daten erhalten, die sie zur Ausführung und Rückgabe ihrer Ergebnisse benötigen. Dies ist jedoch nicht immer die praktikabelste Lösung, daher ist das Serialisieren der Daten in eine Datei keine so schlechte Idee. Ich denke, das ganze Singleton-Ding ist ein bisschen wie ein roter Hering; Du willst keinen Singleton.
oxbow_lakes

2
Es ist ziemlich bedauerlich, dass Sie den Begriff Singleton verwendet haben, der mit einem solchen Gepäck geliefert wird. Der richtige Begriff für dieses Muster ist Interning. Internierung ist eine Methode, um sicherzustellen, dass abstrakte Werte nur von einer Instanz dargestellt werden. String-Internierung ist die häufigste Verwendung: en.wikipedia.org/wiki/String_intern_pool.
Notnoop

Vielleicht möchten Sie einen Blick auf Terrakotta werfen. Es behält die Objektidentität im gesamten Cluster bei. Wenn Sie einen Verweis auf Daten senden, die sich bereits im Cluster befinden, wird dieser nicht erneut serialisiert.
Taylor Gautier

21
Abgesehen von der Frage, ob das Singleton-Muster jemals verwendet werden sollte, würde ich bemerken, dass fast jede Antwort hier davon auszugehen scheint, dass der Zweck der Bereitstellung eines Arguments darin besteht, die Erstellung von "mehreren Singletons" zu ermöglichen, die sich durch den Wert unterscheiden dieses Parameters. Ein weiterer möglicher Zweck besteht darin, den Zugriff auf ein externes Objekt zu ermöglichen, das das einzige Objekt seiner Art ist, das die eindeutige Instanz der Singleton-Klasse jemals benötigen wird. Wir müssen also einen Parameter, der für einen solchen Zugriff bereitgestellt wird, von einem Parameter unterscheiden, der "mehrere Singleton-Instanzen" erstellen soll.
Carl

2
Ein weiteres Szenario für einen "Singleton mit Parametern": Eine Webanwendung, die ihren einzigartigen unveränderlichen Singleton basierend auf den Informationen erstellt, die mit der allerersten anstehenden Anforderung (Thread) eingehen. Die Domäne der Anfrage könnte beispielsweise das Verhalten eines Singletons bestimmen
Fustaki,

Antworten:


171

Ich werde meinen Standpunkt sehr deutlich machen: Ein Singleton mit Parametern ist kein Singleton .

Ein Singleton ist per Definition ein Objekt, das Sie nur einmal instanziieren möchten. Wenn Sie versuchen, dem Konstruktor Parameter zuzuführen, wozu dient der Singleton?

Sie haben zwei Möglichkeiten. Wenn Sie möchten, dass Ihr Singleton mit einigen Daten initialisiert wird, können Sie ihn nach der Instanziierung wie folgt mit Daten laden :

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

Wenn die Operation, die Ihr Singleton ausführt, wiederkehrend ist und jedes Mal andere Parameter aufweist, können Sie die Parameter auch an die auszuführende Hauptmethode übergeben:

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

In jedem Fall ist die Instanziierung immer parameterlos. Andernfalls ist Ihr Singleton kein Singleton.


1
+1 So würde ich es wahrscheinlich beim Codieren machen. In C # würde ich jedoch nur Eigenschaften verwenden. Java, wahrscheinlich so.
Zack

131
Entschuldigung, das ist nicht wahr. Es gibt Situationen, in denen Sie dynamisch erstellte Parameter übergeben müssen, die für die Laufzeit der gesamten Anwendung gleich bleiben. Sie können also keine Konstante innerhalb des Singletons verwenden, sondern müssen diese Konstante beim Erstellen übergeben. nach einmal bestanden ist es die gleiche Konstante für die Lochzeit. Ein Setter erledigt den Job nicht, wenn Sie diese bestimmte Konstante im Konstruktor benötigen.
Masi

1
@masi, wie der Autor sagt - es ist kein Singleton. Wenn Sie eine konstante Dynamik übergeben müssen, müssen Sie möglicherweise viele solcher Klassen mit unterschiedlichen Konstanten erstellen. Es macht also keinen Sinn, Singleton zu spielen.
Dmitry Zaytsev

53
Wenn Sie nur eine Instanz einer Klasse für die gesamte Lebensdauer einer Anwendung benötigen, diese Instanz jedoch beim Start mit einem Wert versehen müssen, warum ist dies kein Singleton mehr?
Oscar

4
"Wenn Sie versuchen, dem Konstruktor Parameter zuzuführen, wozu dient der Singleton?" - Man könnte auch sagen: "Wenn Sie Ihre gesamte Anwendung zu einer einzigen Instanz machen, was ist der Sinn von Befehlszeilenargumenten?", Und die Antwort lautet, dass dies sehr sinnvoll ist. Man könnte jetzt sagen, dass dies ziemlich anders ist als eine Singleton-Klasse, außer wenn die Klasse tatsächlich die Hauptklasse ist, die die Argumente [] von der Hauptmethode empfängt - dann ist es sogar dasselbe. Das letzte Argument, das gerade stehen könnte, ist, dass dies eine ziemlich außergewöhnliche Situation ist.
Dreamspace Präsident

41

Ich denke, Sie brauchen so etwas wie eine Fabrik , um Objekte mit verschiedenen Parametern instanziieren und wiederverwenden zu lassen. Es könnte implementiert werden, indem ein synchronisierter HashMapoder ConcurrentHashMapein Parameter ( Integerzum Beispiel ein) Ihrer parametrierbaren 'Singleton'-Klasse zugeordnet wird.

Obwohl Sie möglicherweise an den Punkt gelangen, an dem Sie stattdessen reguläre Nicht-Singleton-Klassen verwenden sollten (z. B. 10.000 unterschiedlich parametrisierte Singleton-Klassen).

Hier ist ein Beispiel für ein solches Geschäft:

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;
        }
    }
}

Um es noch weiter enumvoranzutreiben, können die Java s auch als parametrisierte Singletons betrachtet (oder als solche verwendet) werden, obwohl nur statische Varianten mit einer festen Anzahl zulässig sind.

Wenn Sie jedoch eine verteilte 1- Lösung benötigen, sollten Sie eine laterale Caching-Lösung in Betracht ziehen. Zum Beispiel: EHCache, Terrakotta usw.

1 im Sinne einer Spannweite mehrerer VMs auf wahrscheinlich mehreren Computern.


Ja, genau das brauche ich. Vielen Dank! Ich stimme zu, dass der Umgang mit Argumenten in meinem Beispiel nicht viel Sinn machte, aber ich habe nicht daran gedacht. Siehe meine Erklärung in den Kommentaren der Antwort von oxbow_lakes.

1
Dies ist KEIN Singleton; Sie haben jetzt mehr als einen von ihnen. LOL
oxbow_lakes

@ Scott: Ich würde so etwas vorschlagen, wie Yuval es unten vorgeschlagen hat. Es macht etwas mehr Sinn und Sie haben einen "wahren" Singleton. bearbeiten
Zack

Ich hoffe, es macht niemandem etwas aus, die Namen im Code zu bearbeiten. Ich kann mir vorstellen, dass dies für Neulinge wirklich verwirrend ist. Rollback, wenn Sie nicht einverstanden sind
oxbow_lakes

Ja, wir könnten sie Multitron nennen und trotzdem das gleiche Ziel erreichen, das das OP meiner Meinung nach überhaupt wollte.
Akarnokd

22

Sie können eine konfigurierbare Initialisierungsmethode hinzufügen, um die Instanziierung vom Abrufen zu trennen.

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;
    }

}

Dann können Sie Singleton.init(123)einmal anrufen , um es zu konfigurieren, zum Beispiel beim Start Ihrer App.


13

Sie können auch das Builder-Muster verwenden, wenn Sie zeigen möchten, dass einige Parameter obligatorisch sind.

    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);
        }

    }


}

Dann können Sie es wie folgt erstellen / instanziieren / parametrisieren :

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

6

Die Anweisung " Ein Singleton mit Parametern ist kein Singleton " ist nicht vollständig korrekt . Wir müssen dies eher aus der Anwendungsperspektive als aus der Codeperspektive analysieren.

Wir erstellen eine Singleton-Klasse, um eine einzelne Instanz eines Objekts in einem Anwendungslauf zu erstellen. Mit einem Konstruktor mit Parametern können Sie Ihrem Code Flexibilität hinzufügen, um einige Attribute Ihres Singleton-Objekts bei jeder Ausführung Ihrer Anwendung zu ändern. Dies ist keine Verletzung des Singleton-Musters. Es sieht nach einer Verletzung aus, wenn Sie dies aus Code-Sicht sehen.

Design Patterns sollen uns helfen, flexiblen und erweiterbaren Code zu schreiben, und uns nicht daran hindern, guten Code zu schreiben.


12
Dies ist keine Antwort auf die OP-Frage, dies sollte ein Kommentar sein.
Thierry J.

5

Verwenden Sie Getter und Setter, um die Variable festzulegen und den Standardkonstruktor privat zu machen. Dann benutze:

Singleton.getInstance().setX(value);

1
Verstehe nicht, warum dies abgelehnt wurde. Es ist eine gültige Antwort, tbh. : /
Zack

13
Weil es eine Müllantwort ist. Stellen Sie sich beispielsweise ein System vor, bei dem der ursprüngliche Benutzername und das Kennwort für den anfänglichen Administrator Konstruktorargumente sind. Wenn ich dies zu einem Singleton mache und tue, was Sie sagen, bekomme ich Getter und Setter für den Administrator, was nicht ganz das ist, was Sie wollen. Obwohl Ihre Option in einigen Fällen gültig ist, beantwortet sie den allgemeinen Fall, der die Frage war, nicht wirklich. (Ja, ich arbeite an dem System, das ich beschrieben habe, und nein, ich hätte kein Singleton-Muster verwendet, wenn nicht die Aufgabe "hier ein Singleton-Muster verwenden" lautet.)
Jasper

5

Überrascht, dass niemand erwähnt hat, wie ein Logger erstellt / abgerufen wird. Im Folgenden wird beispielsweise gezeigt, wie der Log4J-Logger abgerufen wird.

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

Es gibt einige Ebenen von Indirektionen, aber der Schlüsselteil befindet sich unter der Methode, die so ziemlich alles darüber erzählt, wie es funktioniert. Es verwendet eine Hash-Tabelle, um die vorhandenen Logger zu speichern, und der Schlüssel wird vom Namen abgeleitet. Wenn der Logger für einen Vornamen nicht vorhanden ist, verwendet er eine Factory, um den Logger zu erstellen, und fügt ihn dann der Hash-Tabelle hinzu.

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

Änderung des Singleton-Musters unter Verwendung der Red Pugh-Initialisierung bei Bedarf . Dies ist threadsicher ohne den Aufwand spezialisierter Sprachkonstrukte (dh flüchtig oder synchronisiert):

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);
    }
}

Ich denke , es wäre es eine gute Idee sein finally { RInterfaceHL.rloopHandler = null; }in getInstance, weil die statischen Referenz einen Speicherverlust verursachen kann , wenn wir nicht aufpassen. In Ihrem Fall sieht es so aus, als wäre es kein Problem, aber ich könnte mir ein Szenario vorstellen, in dem das übergebene Objekt groß ist und nur von RInterfaceHLctor verwendet wird , um einige Werte abzurufen und keinen Verweis darauf zu behalten.
TWiStErRob

Idee: return SingletonHolder.INSTANCEwürde genauso gut funktionieren in getInstance. Ich glaube nicht, dass hier eine Kapselung erforderlich ist, da die äußere Klasse die Innereien der inneren Klasse bereits kennt, sie sind eng miteinander verbunden: Sie weiß, dass sie rloopHandlervor dem Anruf init benötigt. Auch der private Konstruktor hat keine Auswirkung, da das private Material der inneren Klasse einfach der äußeren Klasse zur Verfügung steht.
TWiStErRob


3

Der Grund, warum Sie nicht verstehen können, wie Sie das erreichen, was Sie versuchen, ist wahrscheinlich, dass das, was Sie versuchen, keinen wirklichen Sinn ergibt. Sie möchten getInstance(x)mit unterschiedlichen Argumenten aufrufen , aber immer das gleiche Objekt zurückgeben? Welches Verhalten möchten Sie, wenn Sie anrufen getInstance(2)und dann getInstance(5)?

Wenn Sie möchten, dass dasselbe Objekt unterschiedlich ist, der interne Wert jedoch unterschiedlich ist. Nur so ist es immer noch ein Singleton. Dann müssen Sie sich überhaupt nicht um den Konstruktor kümmern. Sie setzen den Wert einfach getInstance()auf dem Weg des Objekts ein. Natürlich verstehen Sie, dass alle anderen Verweise auf den Singleton jetzt einen anderen internen Wert haben.

Wenn Sie möchten , getInstance(2)und getInstance(5)verschiedene Objekte zurückzukehren, auf der anderen Seite, die Sie verwenden nicht die Singleton - Muster, sind Sie die Fabrik Muster.


3

In Ihrem Beispiel verwenden Sie keinen Singleton. Beachten Sie Folgendes: Wenn Sie Folgendes tun (vorausgesetzt, Singleton.getInstance war tatsächlich statisch):

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

Dann ist der Wert von obj2.x 3, nicht 4. Wenn Sie dies tun müssen, machen Sie es zu einer einfachen Klasse. Wenn die Anzahl der Werte klein und fest ist, können Sie eine verwenden enum. Wenn Sie Probleme mit einer übermäßigen Objektgenerierung haben (was normalerweise nicht der Fall ist), können Sie Caching-Werte in Betracht ziehen (und Quellen überprüfen oder Hilfe dazu erhalten, da es offensichtlich ist, wie Caches ohne die Gefahr von Speicherlecks erstellt werden).

Vielleicht möchten Sie diesen Artikel auch lesen, da Singletons sehr leicht überbeansprucht werden können.


3

Ein weiterer Grund, warum Singletons ein Anti-Pattern sind, besteht darin, dass sie, wenn sie gemäß den Empfehlungen mit einem privaten Konstruktor geschrieben wurden, sehr schwer zu klassifizieren und für die Verwendung in bestimmten Komponententests zu konfigurieren sind. Wäre zum Beispiel für die Pflege von Legacy-Code erforderlich.


3

Wenn Sie eine Singleton-Klasse erstellen möchten, die als Kontext dient, sollten Sie eine Konfigurationsdatei haben und die Parameter aus der Datei in instance () lesen.

Wenn die Parameter, die die Singleton-Klasse speisen, während der Ausführung Ihres Programms dynamisch abgerufen werden, verwenden Sie einfach eine statische HashMap, in der verschiedene Instanzen in Ihrer Singleton-Klasse gespeichert sind, um sicherzustellen, dass für jeden Parameter nur eine Instanz erstellt wird.


1

Dies ist kein Einzelfall, kann aber möglicherweise Ihr Problem beheben.

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

Wenn wir das Problem als "wie man Singleton mit state macht" betrachten, ist es nicht notwendig, den state als Konstruktorparameter zu übergeben. Ich bin mit den Posts einverstanden, die die Zustände initialisieren oder die set-Methode verwenden, nachdem die Singleton-Instanz abgerufen wurde.

Eine andere Frage ist: Ist es gut, Singleton mit Staat zu haben?


1

Könnten wir so etwas nicht machen:

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

Trotz allem, was einige behaupten mögen, ist hier ein Singleton mit Parametern im Konstruktor

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
     */
}

Das Singleton-Muster sagt:

  • Stellen Sie sicher, dass immer nur eine Instanz der Singleton-Klasse vorhanden ist
  • Bieten Sie globalen Zugriff auf diese Instanz.

die mit diesem Beispiel respektiert werden.

Warum nicht direkt die Eigenschaft einstellen? Es ist ein Lehrbuchfall, um zu zeigen, wie wir einen Singleton mit einem Konstruktor mit Parameter erhalten können, aber es kann in einigen Situationen nützlich sein. Zum Beispiel in Vererbungsfällen, um den Singleton zu zwingen, einige Eigenschaften der Oberklasse festzulegen.


0

Ich habe Angst, dies als Antwort zu posten, aber ich verstehe nicht, warum niemand darüber nachdenkt. Vielleicht wurde diese Antwort auch schon gegeben, ich habe sie einfach nicht verstanden.

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");
    }
  }
}

Da die getInstance()Instanz jedes Mal dieselbe Instanz zurückgibt, denke ich, dass dies funktionieren könnte. Wenn dies zu viel falsch ist, werde ich es löschen, ich bin nur an diesem Thema interessiert.


-1

Ich denke, das ist ein häufiges Problem. Das Trennen der "Initialisierung" des Singletons vom "Get" des Singletons funktioniert möglicherweise (in diesem Beispiel wird eine Variante der doppelt aktivierten Sperre verwendet).

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;
    }

    ...
}

Könnte auch in der Initialisierungsmethode immer "result" zurückgeben, nehme ich an.
Michael Andrews

-2

Singleton ist natürlich ein "Anti-Pattern" (unter der Annahme einer Definition einer Statik mit variablem Zustand).

Wenn Sie einen festen Satz unveränderlicher Wertobjekte wünschen, sind Aufzählungen der richtige Weg. Für einen großen, möglicherweise offenen Satz von Werten können Sie ein Repository in irgendeiner Form verwenden - normalerweise basierend auf einer MapImplementierung. Wenn Sie sich mit Statik beschäftigen, sollten Sie natürlich vorsichtig mit dem Threading umgehen (entweder ausreichend breit synchronisieren oder ConcurrentMapentweder überprüfen, ob ein anderer Thread Sie nicht geschlagen hat, oder irgendeine Form von Futures verwenden).


4
Nur ein Anti-Muster, wenn es falsch verwendet wird, obwohl dies die Definition eines Anti-Musters ist. Nur weil Sie sie dort gesehen haben, wo sie in der Vergangenheit nicht hingehörten, heißt das nicht, dass sie keinen Platz haben.
Geowa4

Die korrekte Verwendung eines Singletons besteht darin, inkompetenten Code zu demonstrieren.
Tom Hawtin - Tackline

-6

Singletons gelten allgemein als Anti-Patterns und sollten nicht verwendet werden. Sie machen es nicht einfach, Code zu testen.

Ein Singleton mit einem Argument macht sowieso keinen Sinn - was würde passieren, wenn Sie schreiben würden:

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

Ihr Singleton ist auch nicht threadsicher, da mehrere Threads gleichzeitig aufrufen können, getInstancewas dazu führt, dass mehr als eine Instanz erstellt wird (möglicherweise mit unterschiedlichen Werten von x).


Das ist ziemlich umstritten.
AlbertoPL

1
Ja, es ist umstritten. daher meine Verwendung des Wortes "allgemein". Ich denke, es ist fair zu sagen, dass sie im Allgemeinen als schlechte Idee angesehen werden
oxbow_lakes

Es ist umstritten - einige Leute behaupten, dass sogenannte "Anti-Muster" zur Definition von Mustern passen, es ist nur so, dass sie schlechte Muster sind.
Tom Hawtin - Tackline

Ich verstehe, dass sie schlecht sind. Ich mache verteiltes Rechnen und muss ein Objekt auf mehrere Aufgaben verteilen. Anstatt eine statische Variable deterministisch zu initialisieren, möchte ich die Logik in einen Singleton abstrahieren. Ich stelle mir vor, ich könnte getInstance synchronisieren. Würde das funktionieren? Ich muss eine Datei einmal für viele Aufgaben laden und erst, nachdem die erste Aufgabe gesendet wurde. (Ich möchte nicht, dass meine Daten serialisiert werden.) Ich dachte, ich würde meinen AbstractFileReader zu einem Argument für die getInstance-Methode machen, um den Singleton flexibler zu machen. Ich schätze Ihre Eingabe.

Ich denke, Sie können falsch verstehen, was "verteilt" bedeutet? Es gibt andere Möglichkeiten, um das zu erreichen, was Sie wollen: Haben Sie über eine Abhängigkeitsinjektion nachgedacht? Oder JNDI?
oxbow_lakes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.