Come si costruisce un Singleton in Dart?


205

Il modello singleton garantisce che venga creata solo un'istanza di una classe. Come lo costruisco in Dart?


Ho visto diverse risposte di seguito che descrivono diversi modi per creare un singleton di classe. Quindi sto pensando al perché non ci piace questo oggetto class_name; if (object == null) restituisce object = new class_name; else return object
Avnish kumar

Risposte:


327

Grazie ai costruttori di fabbrica di Dart , è facile costruire un singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Puoi costruirlo in questo modo

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}

2
Sebbene a che serve istanziarlo due volte? Non dovrebbe essere meglio se ha generato un errore quando lo si istanzia per la seconda volta?
Westoque,

53
Non lo sto istanziando due volte, sto solo ottenendo due volte un riferimento a un oggetto Singleton. Probabilmente non lo faresti due volte di seguito nella vita reale :) Non vorrei che venisse generata un'eccezione, voglio solo la stessa istanza singleton ogni volta che dico "new Singleton ()". Lo ammetto, è un po 'confuso ... newnon significa "costruirne uno nuovo" qui, dice solo "esegui il costruttore".
Seth Ladd,

1
Cosa serve esattamente la parola chiave factory qui? Sta semplicemente annotando l'implementazione. Perché è richiesto?
Καrτhικ,

4
È un po 'confuso che stai usando un costruttore per ottenere l'istanza. La newparola chiave suggerisce che la classe è istanziata, cosa che non lo è. Vorrei un metodo statico get()o getInstance()come faccio in Java.
Steven Roose,

11
@SethLadd questo è molto bello ma suggerisco che abbia bisogno di un paio di punti di spiegazione. C'è la strana sintassi Singleton._internal();che assomiglia a una chiamata di metodo quando è davvero una definizione di costruttore. C'è il _internalnome E c'è il nifty point design del linguaggio che Dart ti consente di iniziare (dart out?) Usando un normale costruttore e poi, se necessario, cambiarlo in un factorymetodo senza cambiare tutti i chiamanti.
Jerry101,

174

Ecco un confronto tra diversi modi per creare un singleton in Dart.

1. Costruttore di fabbrica

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Campo statico con getter

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Campo statico

class SingletonThree {

  SingletonThree._privateConstructor();

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

Come istanstiare

I singoli sopra sono istanziati in questo modo:

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

Nota:

Inizialmente l'ho posto come una domanda , ma ho scoperto che tutti i metodi sopra indicati sono validi e la scelta dipende in gran parte dalle preferenze personali.


3
Ho appena votato la tua risposta. Molto più chiaro della risposta accettata. Ancora un'altra domanda: per la seconda e terza via, che senso ha il costruttore privato? Ho visto molte persone farlo, ma non capisco il punto. Uso sempre semplicemente static final SingletonThree instance = SingletonThree(). Lo stesso vale per la seconda strada _instance. Non so quale sia lo svantaggio di non usare un costruttore privato. Finora non ho riscontrato alcun problema sulla mia strada. Il secondo e il terzo modo non stanno comunque bloccando la chiamata al costruttore predefinito.
sgon00,

3
@ sgon00, il costruttore privato è in modo da non poter creare un'altra istanza. Altrimenti chiunque potrebbe fare SingletonThree instance2 = SingletonThree(). Se provi a farlo quando c'è un costruttore privato, otterrai l'errore:The class 'SingletonThree' doesn't have a default constructor.
Suragch

41

Non lo trovo una lettura molto intuitiva new Singleton(). Devi leggere i documenti per sapere che in newrealtà non sta creando una nuova istanza, come farebbe normalmente.

Ecco un altro modo di fare singletons (in sostanza quello che ha detto Andrew sopra).

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

Nota che il singleton non viene creato fino alla prima volta in cui viene chiamato il getter a causa dell'inizializzazione pigra di Dart.

Se preferisci, puoi anche implementare singleton come getter statico nella classe singleton. cioè Thing.singleton, invece di un getter di livello superiore.

Leggi anche la versione di Bob Nystrom sui singoli dal suo libro sui modelli di programmazione del gioco .


1
Questo ha più senso per me, grazie a Greg e alla caratteristica di proprietà di alto livello del dardo.
Eason PI,

Questo non è un idiomatico. È una caratteristica da sogno avere un modello singleton costruito nel linguaggio e lo stai buttando fuori perché non ci sei abituato.
Arash,

1
Sia l'esempio di Seth che questo esempio sono modelli singleton. È davvero una questione di sintassi "new Singleton ()" vs "singleton". Trovo quest'ultimo più chiaro. I costruttori di fabbriche di Dart sono utili, ma non credo che questo sia un buon caso per loro. Penso anche che l'inizializzazione pigra di Dart sia una grande caratteristica, che è sottoutilizzata. Leggi anche l'articolo di Bob sopra - nella maggior parte dei casi raccomanda contro i single.
Greg Lowe,

Consiglio anche di leggere questa discussione sulla mailing list. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe,

Questo è molto meglio. La parola chiave "nuova" implica piuttosto pesantemente la costruzione di un nuovo oggetto. La soluzione accettata sembra davvero sbagliata.
Sava B.

16

Che ne dici di usare una variabile globale nella tua libreria, in questo modo?

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

O questo è disapprovato?

Il modello singleton è necessario in Java dove non esiste il concetto di globali, ma sembra che non si debba fare molta strada in Dart.


5
Le variabili di livello superiore sono interessanti. Tuttavia, chiunque può importare single.dart è libero di costruire un "nuovo Impl ()". Potresti dare un costruttore di sottolineatura a Impl, ma poi il codice all'interno della libreria singleton potrebbe chiamarlo.
Seth Ladd,

E il codice nella tua implementazione non può? Puoi spiegare nella tua risposta perché è meglio di una variabile di livello superiore?
Jan

2
Ciao @Jan, non è meglio o peggio, è solo diverso. Nell'esempio di Andrew, Impl non è una classe singleton. Ha usato correttamente una variabile di livello superiore per facilitare l' Singletonaccesso all'istanza . Nel mio esempio sopra, la Singletonclasse è un vero singleton, solo un'istanza diSingleton può mai esistere nell'isolato.
Seth Ladd,

1
Seth, non hai ragione. In Dart non esiste alcun modo per creare un vero singleton, in quanto non esiste un modo per limitare l'istanziabilità di una classe all'interno della libreria dichiarante. Richiede sempre disciplina dall'autore della biblioteca. Nel tuo esempio, la libreria dichiarante può chiamare new Singleton._internal()tutte le volte che vuole, creando molti oggetti della Singletonclasse. Se la Implclasse nell'esempio di Andrew fosse privata ( _Impl), sarebbe la stessa del tuo esempio. D'altra parte, singleton è un antipattern e nessuno dovrebbe usarlo comunque.
Ladicek,

@Ladicek, non ti fidi degli sviluppatori di una libreria per non chiamare nuovo Singelton._internal(). Si può sostenere che anche gli sviluppatori della classe singelton potrebbero istigare la classe più volte. Certo c'è l'enum singelton ma per me è solo di uso teorico. Un enum è un enum, non un singelton ... Per quanto riguarda l'uso delle variabili di livello superiore (@Andrew e @Seth): nessuno potrebbe scrivere sulla variabile di livello superiore? Non è affatto protetto o mi sto perdendo qualcosa?
Tobias Ritzau

12

Ecco un altro modo possibile:

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

11

Dart singleton di const constructor & factory

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


void main() {
  print(new Singleton() == new Singleton());
  print(identical(new Singleton() , new Singleton()));
}

5

Singleton che non può cambiare l'oggetto dopo l'istanza

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}

4

Modificato @Seth Ladd risposta per chi preferisce lo stile Swift di singleton come .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;
}

Campione:

Auth.shared.username = 'abc';

4

Dopo aver letto tutte le alternative, ho pensato a questo, che mi ricorda un "singleton classico":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}

3
Vorrei cambiare il getInstancemetodo in una instanceproprietà come questa:static AccountService get instance => _instance;
gianlucaparadise,

Mi piace questo. poiché voglio aggiungere qualcosa prima che l'istanza venga restituita e vengano utilizzati altri metodi.
Chitgoks

4

Ecco una semplice risposta:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

3

Ecco un esempio conciso che combina le altre soluzioni. L'accesso al singleton può essere effettuato tramite:

  • Utilizzo di una singletonvariabile globale che punta all'istanza.
  • Il Singleton.instancemodello comune .
  • Utilizzando il costruttore predefinito, che è una factory che restituisce l'istanza.

Nota: è necessario implementare solo una delle tre opzioni in modo che il codice che utilizza il singleton sia coerente.

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

Se è necessario eseguire un'inizializzazione complessa, è sufficiente farlo prima di utilizzare l'istanza in un secondo momento nel programma.

Esempio

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
}

1

Ciao, che ne dici di qualcosa del genere? Implementazione molto semplice, Injector stesso è singleton e ha anche aggiunto delle classi. Naturalmente può essere esteso molto facilmente. Se stai cercando qualcosa di più sofisticato controlla questo pacchetto: 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;
  }
}

0

Questo dovrebbe funzionare.

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

    _(){

    }
    factory GlobalStore()=> instance;


}

Si prega di non pubblicare domande di follow-up come risposte. Il problema con questo codice è che è un po 'prolisso. static GlobalStore get instance => _instance ??= new GlobalStore._();farebbe. Cosa _(){}dovrebbe fare? Questo sembra ridondante.
Günter Zöchbauer,

scusa, è stato un suggerimento, non una domanda di follow-up, _ () {} creerà un costruttore privato giusto?
Vilsad PP,

I costruttori iniziano con il nome della classe. Questo è solo un normale metodo di istanza privata senza un tipo restituito specificato.
Günter Zöchbauer,

1
Ci scusiamo per il downvote, ma penso che sia di scarsa qualità e non aggiunga alcun valore oltre alle risposte esistenti.
Günter Zöchbauer,

2
Mentre questo codice può rispondere alla domanda, fornire un contesto aggiuntivo riguardo a come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
Karl Richter,

0

Dato che non mi piace molto usare la newparola chiave o un altro costruttore come chiamate su singoli, preferirei usare un getter statico chiamato instad esempio:

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

esempio di utilizzo:

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));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.