Wie baut man einen Singleton in Dart?

202

Das Singleton-Muster stellt sicher, dass immer nur eine Instanz einer Klasse erstellt wird. Wie baue ich das in Dart?

Seth Ladd
quelle
Ich habe unten einige Antworten gesehen, die verschiedene Möglichkeiten beschreiben, einen Klassen-Singleton zu erstellen. Ich denke also darüber nach, warum uns dieses class_name-Objekt nicht gefällt. if (object == null) return object = neuer Klassenname; sonst Objekt zurückgeben
Avnish Kumar

Antworten:

320

Dank der Fabrikkonstrukteure von Dart ist es einfach, einen Singleton zu bauen:

class Singleton {
  static final Singleton _singleton = Singleton._internal();

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Sie können es so konstruieren

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Seth Ladd
quelle
2
Obwohl, was ist der Sinn, es zweimal zu instanziieren? Sollte es nicht besser sein, wenn beim zweiten Instanziieren ein Fehler aufgetreten ist?
Westoque
53
Ich instanziiere es nicht zweimal, sondern erhalte nur zweimal einen Verweis auf ein Singleton-Objekt. Sie würden es im wirklichen Leben wahrscheinlich nicht zweimal hintereinander tun :) Ich möchte nicht, dass eine Ausnahme ausgelöst wird. Ich möchte nur jedes Mal dieselbe Singleton-Instanz, wenn ich "new Singleton ()" sage. Ich gebe zu, es ist ein bisschen verwirrend ... newbedeutet hier nicht "einen neuen konstruieren", sondern nur "den Konstruktor ausführen".
Seth Ladd
1
Was genau dient das Factory-Schlüsselwort hier? Es handelt sich lediglich um eine Annotation der Implementierung. Warum ist es erforderlich?
Καrτhικ
4
Es ist etwas verwirrend, dass Sie einen Konstruktor verwenden, um die Instanz abzurufen. Das newSchlüsselwort legt nahe, dass die Klasse instanziiert ist, was nicht der Fall ist. Ich würde mich für eine statische Methode entscheiden get()oder getInstance()wie in Java.
Steven Roose
11
@ SethLadd das ist sehr schön, aber ich schlage vor, es braucht ein paar Erklärungen. Es gibt die seltsame Syntax Singleton._internal();, die wie ein Methodenaufruf aussieht, wenn es sich wirklich um eine Konstruktordefinition handelt. Da ist der _internalName. Und es gibt den raffinierten Sprachentwurfspunkt, mit dem Sie mit Dart mit einem normalen Konstruktor beginnen (Dart out?) Und ihn dann bei Bedarf in eine factoryMethode ändern können, ohne alle Aufrufer zu ändern.
Jerry101
169

Hier finden Sie einen Vergleich verschiedener Möglichkeiten zum Erstellen eines Singletons in Dart.

1. Fabrikkonstrukteur

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Statisches Feld mit Getter

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Statisches Feld

class SingletonThree {

  SingletonThree._privateConstructor();

  static final SingletonThree instance = SingletonThree._privateConstructor();
  
}

Wie man instanstiiert

Die obigen Singletons werden folgendermaßen instanziiert:

SingletonOne one = SingletonOne();
SingletonTwo two = SingletonTwo.instance;
SingletonThree three = SingletonThree.instance;

Hinweis:

Ich habe dies ursprünglich als Frage gestellt , aber festgestellt, dass alle oben genannten Methoden gültig sind und die Auswahl weitgehend von den persönlichen Vorlieben abhängt.

Suragch
quelle
3
Ich habe gerade Ihre Antwort positiv bewertet. Viel klarer als die akzeptierte Antwort. Nur noch eine Frage: Was ist der Sinn eines privaten Konstrukteurs für den zweiten und dritten Weg? Ich habe gesehen, dass viele Leute das getan haben, aber ich verstehe den Punkt nicht. Ich benutze immer einfach static final SingletonThree instance = SingletonThree(). Gleiches gilt für den zweiten Weg _instance. Ich weiß nicht, was der Nachteil ist, keinen privaten Konstruktor zu verwenden. Bisher finde ich keine Probleme im Weg. Die zweite und dritte Möglichkeit blockieren den Aufruf des Standardkonstruktors ohnehin nicht.
sgon00
3
@ sgon00, der private Konstruktor ist so, dass Sie keine weitere Instanz erstellen können. Sonst könnte jeder machen SingletonThree instance2 = SingletonThree(). Wenn Sie versuchen, dies zu tun, wenn es einen privaten Konstruktor gibt, erhalten Sie die Fehlermeldung:The class 'SingletonThree' doesn't have a default constructor.
Suragch
40

Ich finde es nicht sehr intuitiv zu lesen new Singleton(). Sie müssen die Dokumente lesen, um das zu wissennew keine neue Instanz erstellt wird, wie dies normalerweise der Fall ist.

Hier ist eine andere Möglichkeit, Singletons zu machen (im Grunde das, was Andrew oben gesagt hat).

lib / thing.dart

library thing;

final Thing thing = new Thing._private();

class Thing {
   Thing._private() { print('#2'); }
   foo() {
     print('#3');
   }
}

main.dart

import 'package:thing/thing.dart';

main() {
  print('#1');
  thing.foo();
}

Beachten Sie, dass der Singleton erst erstellt wird, wenn der Getter aufgrund der verzögerten Initialisierung von Dart zum ersten Mal aufgerufen wird.

Wenn Sie möchten, können Sie Singletons auch als statischen Getter für die Singleton-Klasse implementieren. dhThing.singleton anstelle eines Top-Level-Getters.

Lesen Sie auch Bob Nystroms Version von Singletons aus seinem Spielprogrammiermusterbuch .

Greg Lowe
quelle
1
Dies ist für mich dank Greg und der Eigenschaft der Top-Level-Eigenschaft von Dart sinnvoller.
Eason PI
Das ist keine Redewendung. Es ist eine Traumfunktion, ein Singleton-Muster in der Sprache zu erstellen, und Sie werfen es weg, weil Sie nicht daran gewöhnt sind.
Arash
1
Sowohl Seths Beispiel als auch dieses Beispiel sind Singleton-Muster. Es ist wirklich eine Frage der Syntax "new Singleton ()" vs "singleton". Letzteres finde ich klarer. Darts Fabrikkonstrukteure sind nützlich, aber ich denke nicht, dass dies ein guter Anwendungsfall für sie ist. Ich denke auch, dass Darts faule Initialisierung eine großartige Funktion ist, die nicht ausreichend genutzt wird. Lesen Sie auch Bobs Artikel oben - er empfiehlt in den meisten Fällen gegen Singletons.
Greg Lowe
Ich empfehle auch, diesen Thread auf der Mailingliste zu lesen. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe
Das ist viel besser. Das Schlüsselwort "new" impliziert ziemlich stark die Konstruktion eines neuen Objekts. Die akzeptierte Lösung fühlt sich wirklich falsch an.
Sava B.
16

Wie wäre es, wenn Sie einfach eine globale Variable in Ihrer Bibliothek verwenden?

single.dart::

library singleton;

var Singleton = new Impl();

class Impl {
  int i;
}

main.dart::

import 'single.dart';

void main() {
  var a = Singleton;
  var b = Singleton;
  a.i = 2;
  print(b.i);
}

Oder ist das verpönt?

Das Singleton-Muster ist in Java erforderlich, wo das Konzept der Globalen nicht existiert, aber es scheint, dass Sie in Dart nicht den weiten Weg gehen müssen.

Seth Ladd
quelle
5
Variablen der obersten Ebene sind cool. Jeder, der single.dart importieren kann, kann jedoch ein "neues Impl ()" erstellen. Sie könnten Impl einen Unterstrichkonstruktor geben, aber dann könnte Code in der Singleton-Bibliothek diesen Konstruktor aufrufen.
Seth Ladd
Und der Code in Ihrer Implementierung kann nicht? Können Sie in Ihrer Antwort erklären, warum es besser ist als eine Variable der obersten Ebene?
Januar
2
Hallo @Jan, es ist nicht besser oder schlechter, es ist einfach anders. In Andrews Beispiel ist Impl keine Singleton-Klasse. Er hat eine Variable der obersten Ebene korrekt verwendet, um den SingletonZugriff auf die Instanz zu vereinfachen. In meinem obigen Beispiel ist die SingletonKlasse ein echter Singleton, von dem nur eine Instanz Singletonim Isolat existieren kann.
Seth Ladd
1
Seth, du hast nicht recht. In Dart gibt es keine Möglichkeit, einen echten Singleton zu erstellen, da es keine Möglichkeit gibt, die Instantiierbarkeit einer Klasse in der deklarierenden Bibliothek einzuschränken . Es erfordert immer Disziplin vom Bibliotheksautor. In Ihrem Beispiel kann die deklarierende Bibliothek new Singleton._internal()so oft aufrufen, wie sie möchte, und so viele Objekte der SingletonKlasse erstellen . Wenn die ImplKlasse in Andrews Beispiel privat ( _Impl) wäre, wäre sie dieselbe wie Ihr Beispiel. Auf der anderen Seite ist Singleton ein Antimuster und niemand sollte es trotzdem benutzen.
Ladicek
@Ladicek, vertrauen Sie nicht dem Entwickler einer Bibliothek nicht neu zu nennen Singelton._internal(). Sie können argumentieren, dass die Entwickler der Singelton-Klasse die Klasse auch mehrmals initiieren könnten. Sicher gibt es das Enum Singelton, aber für mich ist es nur von theoretischem Nutzen. Eine Aufzählung ist eine Aufzählung, kein Singelton ... Was die Verwendung von Variablen der obersten Ebene (@Andrew und @Seth) betrifft: Könnte niemand in die Variable der obersten Ebene schreiben? Es ist keineswegs geschützt, oder fehlt mir etwas?
Tobias Ritzau
12

Hier ist ein anderer möglicher Weg:

void main() {
  var s1 = Singleton.instance;
  s1.somedata = 123;
  var s2 = Singleton.instance;
  print(s2.somedata); // 123
  print(identical(s1, s2));  // true
  print(s1 == s2); // true
  //var s3 = new Singleton(); //produces a warning re missing default constructor and breaks on execution
}

class Singleton {
  static final Singleton _singleton = new Singleton._internal();
  Singleton._internal();
  static Singleton get instance => _singleton;
  var somedata;
}
iBob101
quelle
11

Dart Singleton von Const Konstruktor & Fabrik

class Singleton {
  factory Singleton() =>
    const Singleton._internal_();
  const Singleton._internal_();
}


void main() {
  print(new Singleton() == new Singleton());
  print(identical(new Singleton() , new Singleton()));
}
Ticore Shih
quelle
5

Singleton, der das Objekt nach der Instanz nicht ändern kann

class User {
  final int age;
  final String name;

  User({
    this.name,
    this.age
    });

  static User _instance;

  static User getInstance({name, age}) {
     if(_instance == null) {
       _instance = User(name: name, idade: age);
       return _instance;
     }
    return _instance;
  }
}

  print(User.getInstance(name: "baidu", age: 24).age); //24

  print(User.getInstance(name: "baidu 2").name); // is not changed //baidu

  print(User.getInstance()); // {name: "baidu": age 24}
Lucas Breitembach
quelle
4

Modifizierte @ Seth Ladd-Antwort für diejenigen, die den Swift-Singleton-Stil bevorzugen, wie .shared:

class Auth {
  // singleton
  static final Auth _singleton = Auth._internal();
  factory Auth() => _singleton;
  Auth._internal();
  static Auth get shared => _singleton;

  // variables
  String username;
  String password;
}

Stichprobe:

Auth.shared.username = 'abc';
DazChong
quelle
4

Nachdem ich alle Alternativen gelesen hatte, kam ich auf diese Idee, die mich an einen "klassischen Singleton" erinnert:

class AccountService {
  static final _instance = AccountService._internal();

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
Daveoncode
quelle
3
Ich würde die getInstanceMethode in einer instanceEigenschaft wie dieser ändern :static AccountService get instance => _instance;
Gianlucaparadise
Ich mag das. da ich etwas hinzufügen möchte, bevor die Instanz zurückgegeben wird und andere Methoden verwendet werden.
Chitgoks
4

Hier ist eine einfache Antwort:

class Singleton {
  static Singleton _instance;

  Singleton._();

  static Singleton get getInstance => _instance = _instance ?? Singleton._();
}
Hamed
quelle
3

Hier ist ein kurzes Beispiel, das die anderen Lösungen kombiniert. Der Zugriff auf den Singleton kann erfolgen über:

  • Verwendung einer singleton globalen Variablen, die auf die Instanz verweist.
  • Das Gemeinsame Singleton.instance Muster.
  • Verwenden des Standardkonstruktors, einer Factory, die die Instanz zurückgibt.

Hinweis: Sie sollten nur eine der drei Optionen implementieren, damit der Code mit dem Singleton konsistent ist.

Singleton get singleton => Singleton.instance;
ComplexSingleton get complexSingleton => ComplexSingleton._instance;

class Singleton {
  static final Singleton instance = Singleton._private();
  Singleton._private();
  factory Singleton() => instance;
}

class ComplexSingleton {
  static ComplexSingleton _instance;
  static ComplexSingleton get instance => _instance;
  static void init(arg) => _instance ??= ComplexSingleton._init(arg);

  final property;
  ComplexSingleton._init(this.property);
  factory ComplexSingleton() => _instance;
}

Wenn Sie eine komplexe Initialisierung durchführen müssen, müssen Sie dies nur tun, bevor Sie die Instanz später im Programm verwenden.

Beispiel

void main() {
  print(identical(singleton, Singleton.instance));        // true
  print(identical(singleton, Singleton()));               // true
  print(complexSingleton == null);                        // true
  ComplexSingleton.init(0); 
  print(complexSingleton == null);                        // false
  print(identical(complexSingleton, ComplexSingleton())); // true
}
Jacob Phillips
quelle
1

Hallo was ist mit so etwas? Sehr einfache Implementierung, Injector selbst ist Singleton und hat auch Klassen hinzugefügt. Natürlich kann sehr einfach erweitert werden. Wenn Sie nach etwas Anspruchsvollerem suchen, überprüfen Sie dieses Paket: https://pub.dartlang.org/packages/flutter_simple_dependency_injection

void main() {  
  Injector injector = Injector();
  injector.add(() => Person('Filip'));
  injector.add(() => City('New York'));

  Person person =  injector.get<Person>(); 
  City city =  injector.get<City>();

  print(person.name);
  print(city.name);
}

class Person {
  String name;

  Person(this.name);
}

class City {
  String name;

  City(this.name);
}


typedef T CreateInstanceFn<T>();

class Injector {
  static final Injector _singleton =  Injector._internal();
  final _factories = Map<String, dynamic>();

  factory Injector() {
    return _singleton;
  }

  Injector._internal();

  String _generateKey<T>(T type) {
    return '${type.toString()}_instance';
  }

  void add<T>(CreateInstanceFn<T> createInstance) {
    final typeKey = _generateKey(T);
    _factories[typeKey] = createInstance();
  }

  T get<T>() {
    final typeKey = _generateKey(T);
    T instance = _factories[typeKey];
    if (instance == null) {
      print('Cannot find instance for type $typeKey');
    }

    return instance;
  }
}
Filip Jerga
quelle
0

Das sollte funktionieren.

class GlobalStore {
    static GlobalStore _instance;
    static GlobalStore get instance {
       if(_instance == null)
           _instance = new GlobalStore()._();
       return _instance;
    }

    _(){

    }
    factory GlobalStore()=> instance;


}
Vilsad PP
quelle
Bitte posten Sie keine Folgefragen als Antworten. Das Problem mit diesem Code ist, dass er etwas ausführlich ist. static GlobalStore get instance => _instance ??= new GlobalStore._();würdest du. Was _(){}soll tun? Dies scheint überflüssig.
Günter Zöchbauer
Entschuldigung, das war ein Vorschlag, keine Folgefrage. _ () {} wird einen privaten Konstruktor erstellen, oder?
Vilsad PP
Konstruktoren beginnen mit dem Klassennamen. Dies ist nur eine normale private Instanzmethode ohne Angabe eines Rückgabetyps.
Günter Zöchbauer
1
Entschuldigen Sie die Ablehnung, aber ich denke, es ist von schlechter Qualität und bietet keinen Mehrwert zusätzlich zu den vorhandenen Antworten.
Günter Zöchbauer
2
Während dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Karl Richter
0

Da ich das newSchlüsselwort oder einen anderen Konstruktor wie Aufrufe von Singletons nicht sehr gern verwende , würde ich lieber einen statischen Getter verwenden, der instzum Beispiel aufgerufen wird :

// the singleton class
class Dao {
    // singleton boilerplate
        Dao._internal() {}
        static final Dao _singleton = new Dao._internal();
        static get inst => _singleton;

    // business logic
        void greet() => print("Hello from singleton");
}

Anwendungsbeispiel:

Dao.inst.greet();       // call a method

// Dao x = new Dao();   // compiler error: Method not found: 'Dao'

// verify that there only exists one and only one instance
assert(identical(Dao.inst, Dao.inst));
Sprestel
quelle