Comment construire un Singleton dans Dart?

205

Le modèle singleton garantit qu'une seule instance d'une classe est créée. Comment créer cela dans Dart?

Seth Ladd
la source
J'ai vu plusieurs réponses ci-dessous qui décrivent plusieurs façons de créer un singleton de classe. Je réfléchis donc à la raison pour laquelle nous n'aimons pas cet objet class_name; if (object == null) return object = new nom_classe; else return object
Avnish kumar

Réponses:

328

Grâce aux constructeurs d'usine de Dart , il est facile de créer un singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Vous pouvez le construire comme ça

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Seth Ladd
la source
2
Mais quel est l'intérêt de l'instancier deux fois? Ne devrait-il pas être préférable qu'il génère une erreur lorsque vous l'instanciez la deuxième fois?
westoque
54
Je ne l'instancie pas deux fois, je reçois simplement une référence à un objet Singleton deux fois. Vous ne le feriez probablement pas deux fois de suite dans la vraie vie :) Je ne voudrais pas qu'une exception soit levée, je veux juste la même instance de singleton à chaque fois que je dis "new Singleton ()". J'admets que c'est un peu déroutant ... newça ne veut pas dire "en construire un nouveau" ici, ça dit juste "exécuter le constructeur".
Seth Ladd
1
À quoi sert exactement le mot-clé d'usine ici? Il s'agit purement d'annoter la mise en œuvre. Pourquoi est-ce nécessaire?
Καrτhικ
4
Il est un peu déroutant d'utiliser un constructeur pour obtenir l'instance. Le newmot clé suggère que la classe est instanciée, ce qui n'est pas le cas. J'irais pour une méthode statique get()ou getInstance()comme je le fais en Java.
Steven Roose
11
@SethLadd c'est très bien mais je suggère que cela nécessite quelques points d'explication. Il y a la syntaxe étrange Singleton._internal();qui ressemble à un appel de méthode alors qu'il s'agit vraiment d'une définition de constructeur. Voilà le _internalnom. Et il y a le point de conception du langage astucieux que Dart vous permet de commencer (darder?) En utilisant un constructeur ordinaire, puis, si nécessaire, de le changer en factoryméthode sans changer tous les appelants.
Jerry101
174

Voici une comparaison de plusieurs façons différentes de créer un singleton dans Dart.

1. Constructeur d'usine

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Champ statique avec getter

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Champ statique

class SingletonThree {

  SingletonThree._privateConstructor();

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

Comment instanster

Les singletons ci-dessus sont instanciés comme ceci:

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

Remarque:

J'ai initialement posé cette question , mais j'ai découvert que toutes les méthodes ci-dessus sont valides et que le choix dépend en grande partie de vos préférences personnelles.

Suragch
la source
3
Je viens de voter pour votre réponse. Beaucoup plus clair que la réponse acceptée. Encore une question: pour la deuxième et la troisième manière, quel est l'intérêt du constructeur privé? J'ai vu beaucoup de gens faire ça, mais je ne comprends pas le point. J'utilise toujours simplement static final SingletonThree instance = SingletonThree(). Il en va de même pour la deuxième méthode _instance. Je ne sais pas quel est l'inconvénient de ne pas utiliser de constructeur privé. Jusqu'à présent, je ne trouve aucun problème sur mon chemin. De toute façon, les deuxième et troisième moyens ne bloquent pas l'appel au constructeur par défaut.
sgon00
3
@ sgon00, le constructeur privé est pour que vous ne puissiez pas créer une autre instance. Sinon, n'importe qui pourrait le faire SingletonThree instance2 = SingletonThree(). Si vous essayez de faire cela quand il y a un constructeur privé, vous obtiendrez l'erreur:The class 'SingletonThree' doesn't have a default constructor.
Suragch
41

Je ne trouve pas la lecture très intuitive new Singleton(). Vous devez lire la documentation pour savoir que cela newne crée pas réellement une nouvelle instance, comme cela le ferait normalement.

Voici une autre façon de faire des singletons (en gros ce qu'Andrew a dit ci-dessus).

lib / chose.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();
}

Notez que le singleton n'est créé que lors du premier appel du getter en raison de l'initialisation paresseuse de Dart.

Si vous préférez, vous pouvez également implémenter des singletons comme getter statique sur la classe singleton. c'est-à Thing.singleton- dire au lieu d'un getter de premier niveau.

Lisez également le point de vue de Bob Nystrom sur les singletons dans son livre de modèles de programmation de jeu .

Greg Lowe
la source
1
Cela a plus de sens pour moi, grâce à Greg et à la fonctionnalité de propriété de premier niveau de dart.
Eason PI du
Ce n'est pas idiomatique. C'est une fonctionnalité de rêve d'avoir un modèle unique dans le langage, et vous le jetez parce que vous n'y êtes pas habitué.
Arash
1
L'exemple de Seth et cet exemple sont des modèles singleton. C'est vraiment une question de syntaxe "new Singleton ()" vs "singleton". Je trouve ce dernier plus clair. Les constructeurs d'usine de Dart sont utiles, mais je ne pense pas que ce soit un bon cas d'utilisation pour eux. Je pense également que l'initialisation paresseuse de Dart est une fonctionnalité intéressante, qui est sous-utilisée. Lisez également l'article de Bob ci-dessus - il recommande d'éviter les singletons dans la plupart des cas.
Greg Lowe
Je recommande également de lire ce fil sur la liste de diffusion. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe
C'est bien mieux. Le mot-clé «nouveau» implique assez fortement la construction d'un nouvel objet. La solution acceptée semble vraiment fausse.
Sava B.
16

Qu'en est-il simplement d'utiliser une variable globale dans votre bibliothèque, comme ça?

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

Ou est-ce mal vu?

Le modèle singleton est nécessaire en Java où le concept de globals n'existe pas, mais il semble que vous ne devriez pas avoir besoin de parcourir le long chemin dans Dart.

Seth Ladd
la source
5
Les variables de premier niveau sont cool. Cependant, quiconque peut importer single.dart est libre de construire un "new Impl ()". Vous pouvez donner un constructeur de soulignement à Impl, mais le code à l' intérieur de la bibliothèque singleton peut appeler ce constructeur.
Seth Ladd
Et le code de votre implémentation ne peut pas? Pouvez-vous expliquer dans votre réponse pourquoi c'est mieux qu'une variable de premier niveau?
janvier
2
Salut @Jan, ce n'est ni meilleur ni pire, c'est juste différent. Dans l'exemple d'Andrew, Impl n'est pas une classe singleton. Il a correctement utilisé une variable de niveau supérieur pour rendre l'instance Singletonfacile d'accès. Dans mon exemple ci-dessus, la Singletonclasse est un vrai singleton, une seule instance de Singletonpeut exister dans l'isolat.
Seth Ladd
1
Seth, tu n'as pas raison. Il n'y a aucun moyen dans Dart de construire un vrai singleton, car il n'y a aucun moyen de restreindre l'instancabilité d'une classe à l' intérieur de la bibliothèque de déclaration. Cela exige toujours de la discipline de la part de l'auteur de la bibliothèque. Dans votre exemple, la bibliothèque déclarante peut appeler new Singleton._internal()autant de fois qu'elle le souhaite, créant de nombreux objets de la Singletonclasse. Si la Implclasse de l'exemple d'Andrew était private ( _Impl), ce serait la même chose que votre exemple. D'un autre côté, singleton est un anti-modèle et personne ne devrait l'utiliser de toute façon.
Ladicek
@Ladicek, ne faites pas confiance aux développeurs d'une bibliothèque pour ne pas en appeler de nouveau Singelton._internal(). Vous pouvez affirmer que les développeurs de la classe singelton pourraient également installer la classe plusieurs fois. Bien sûr, il y a l'énum singelton, mais pour moi, ce n'est que théorique. Une énumération est une énumération, pas un singelton ... Quant à l'utilisation des variables de niveau supérieur (@Andrew et @Seth): Personne ne pourrait écrire dans la variable de niveau supérieur? Il n'est en aucun cas protégé ou est-ce que je manque quelque chose?
Tobias Ritzau
12

Voici un autre moyen possible:

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
la source
11

Dart singleton par constructeur et usine const

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
la source
5

Singleton qui ne peut pas changer l'objet après l'instance

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
la source
4

Réponse modifiée de @Seth Ladd pour qui préfère le style de singleton Swift comme .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;
}

Échantillon:

Auth.shared.username = 'abc';
DazChong
la source
4

Après avoir lu toutes les alternatives, je suis venu avec ceci, qui me rappelle un "singleton classique":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
daveoncode
la source
3
Je changerais la getInstanceméthode dans une instancepropriété comme celle-ci:static AccountService get instance => _instance;
gianlucaparadise
J'aime ça. puisque je veux ajouter quelque chose avant que l'instance ne soit renvoyée et que d'autres méthodes soient utilisées.
chitgoks
4

Voici une réponse simple:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

Voici un exemple concis qui combine les autres solutions. L'accès au singleton peut se faire par:

  • Utilisation d'une singletonvariable globale qui pointe vers l'instance.
  • Le Singleton.instancemodèle commun .
  • En utilisant le constructeur par défaut, qui est une fabrique qui renvoie l'instance.

Remarque: vous ne devez implémenter qu'une des trois options afin que le code utilisant le singleton soit cohérent.

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

Si vous devez effectuer une initialisation complexe, vous devrez simplement le faire avant d'utiliser l'instance ultérieurement dans le programme.

Exemple

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
la source
1

Bonjour et quelque chose comme ça? Implémentation très simple, Injector lui-même est singleton et y a également ajouté des classes. Bien sûr, peut être étendu très facilement. Si vous cherchez quelque chose de plus sophistiqué, vérifiez ce package: 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
la source
0

Cela devrait fonctionner.

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

    _(){

    }
    factory GlobalStore()=> instance;


}
Vilsad PP
la source
Veuillez ne pas publier de questions de suivi comme réponses. Le problème avec ce code est qu'il est un peu détaillé. static GlobalStore get instance => _instance ??= new GlobalStore._();ferait. Que _(){}doit-on faire? Cela semble redondant.
Günter Zöchbauer
désolé, c'était une suggestion, pas une question de suivi, _ () {} créera un constructeur privé, non?
Vilsad PP
Les constructeurs commencent par le nom de la classe. Il s'agit simplement d'une méthode d'instance privée normale sans type de retour spécifié.
Günter Zöchbauer
1
Désolé pour le vote négatif, mais je pense qu'il est de mauvaise qualité et n'ajoute aucune valeur en plus des réponses existantes.
Günter Zöchbauer
2
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la manière et / ou la raison pour laquelle il résout le problème améliorerait la valeur à long terme de la réponse.
Karl Richter
0

Comme je n'aime pas beaucoup utiliser le newmot - clé ou un autre constructeur comme les appels sur des singletons, je préférerais utiliser un getter statique appelé instpar exemple:

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

exemple d'utilisation:

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
la source