¿Cómo se construye un Singleton en Dart?

203

El patrón singleton garantiza que solo se cree una instancia de una clase. ¿Cómo construyo esto en Dart?

Seth Ladd
fuente
He visto varias respuestas a continuación que describen varias formas de hacer un singleton de clase. Así que estoy pensando por qué no nos gusta este objeto class_name; if (object == null) return object = new class_name; más devolver objeto
Avnish Kumar

Respuestas:

321

Gracias a los constructores de fábrica de Dart , es fácil construir un singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Puedes construirlo así

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Seth Ladd
fuente
2
Aunque, ¿cuál es el punto de instanciarlo dos veces? ¿No debería ser mejor si arrojó un error cuando lo instancia por segunda vez?
Westoque
53
No lo instanciaré dos veces, solo obtendré una referencia a un objeto Singleton dos veces. Probablemente no lo harías dos veces seguidas en la vida real :) No quisiera que se lanzara una excepción, solo quiero la misma instancia de singleton cada vez que digo "new Singleton ()". Lo admito, es un poco confuso ... newno significa "construir uno nuevo" aquí, solo dice "ejecutar el constructor".
Seth Ladd
1
¿Qué sirve exactamente la palabra clave de fábrica aquí? Es puramente anotar la implementación. ¿Por qué se requiere?
Καrτhικ
44
Es un poco confuso que esté utilizando un constructor para obtener la instancia. La newpalabra clave sugiere que la clase se instancia, lo que no es. Iría por un método estático get()o getInstance()como lo hago en Java.
Steven Roose
11
@SethLadd esto es muy bueno, pero sugiero que necesita un par de puntos de explicación. Existe la sintaxis extraña Singleton._internal();que parece una llamada a un método cuando realmente es una definición de constructor. Ahí está el _internalnombre. Y existe el ingenioso punto de diseño del lenguaje que Dart le permite comenzar (¿lanzarse?) Usando un constructor ordinario y luego, si es necesario, cambiarlo a un factorymétodo sin cambiar todas las llamadas.
Jerry101
169

Aquí hay una comparación de varias formas diferentes de crear un singleton en Dart.

1. Constructor de fábrica

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Campo estático con captador

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. campo estático

class SingletonThree {

  SingletonThree._privateConstructor();

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

Cómo instaurar

Los singletons anteriores se instancian de esta manera:

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

Nota:

Originalmente hice esto como una pregunta , pero descubrí que todos los métodos anteriores son válidos y la elección depende en gran medida de la preferencia personal.

Suragch
fuente
3
Acabo de votar tu respuesta. Mucho más claro que la respuesta aceptada. Solo una pregunta más: para la segunda y tercera forma, ¿cuál es el punto del constructor privado? Vi que mucha gente hizo eso, pero no entiendo el punto. Siempre lo uso simplemente static final SingletonThree instance = SingletonThree(). Lo mismo ocurre con la segunda forma para _instance. No sé cuál es la desventaja de no usar un constructor privado. Hasta ahora, no encuentro ningún problema en mi camino. La segunda y tercera forma no están bloqueando la llamada al constructor predeterminado de todos modos.
sgon00
3
@ sgon00, el constructor privado es para que no puedas hacer otra instancia. De lo contrario, cualquiera podría hacerlo SingletonThree instance2 = SingletonThree(). Si intenta hacer esto cuando hay un constructor privado, obtendrá el error:The class 'SingletonThree' doesn't have a default constructor.
Suragch
40

No me parece una lectura muy intuitiva new Singleton(). Debe leer los documentos para saber que en newrealidad no está creando una nueva instancia, como lo haría normalmente.

Aquí hay otra forma de hacer singletons (Básicamente lo que Andrew dijo anteriormente).

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

Tenga en cuenta que el singleton no se crea hasta la primera vez que se llama al getter debido a la inicialización diferida de Dart.

Si lo prefiere, también puede implementar singletons como getter estático en la clase singleton. es decir Thing.singleton, en lugar de un captador de nivel superior.

Lea también la versión de Bob Nystrom sobre los singletons de su libro de patrones de programación de juegos .

Greg Lowe
fuente
1
Esto tiene más sentido para mí, gracias a Greg y la característica de propiedad de nivel superior de dardo.
Eason PI
Esto no es idiomático. Es una característica ideal tener una construcción de patrón único en el lenguaje, y lo está descartando porque no está acostumbrado.
Arash
1
Tanto el ejemplo de Seth como este ejemplo son patrones únicos. Es realmente una cuestión de sintaxis "new Singleton ()" vs "singleton". Este último me parece más claro. Los constructores de fábrica de Dart son útiles, pero no creo que sea un buen caso de uso para ellos. También creo que la inicialización perezosa de Dart es una gran característica, que está infrautilizada. Lea también el artículo anterior de Bob: él recomienda no usar singleton en la mayoría de los casos.
Greg Lowe
También recomiendo leer este hilo en la lista de correo. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe
Esto es mucho mejor La palabra clave "nuevo" implica bastante la construcción de un nuevo objeto. La solución aceptada se siente realmente mal.
Sava B.
16

¿Qué tal si usas una variable global dentro de tu biblioteca, así?

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 está mal visto?

El patrón singleton es necesario en Java, donde el concepto de globals no existe, pero parece que no deberías tener que recorrer el camino en Dart.

Seth Ladd
fuente
55
Las variables de nivel superior son geniales. Sin embargo, cualquiera que pueda importar single.dart es libre de construir un "nuevo Impl ()". Podría darle un constructor de subrayado a Impl, pero luego el código dentro de la biblioteca singleton podría llamar a ese constructor.
Seth Ladd
¿Y el código en su implementación no puede? ¿Puede explicar en su respuesta por qué es mejor que una variable de nivel superior?
Jan
2
Hola @ Jan, no es mejor ni peor, es simplemente diferente. En el ejemplo de Andrew, Impl no es una clase singleton. Utilizó correctamente una variable de nivel superior para facilitar el Singletonacceso a la instancia . En mi ejemplo anterior, la Singletonclase es un singleton real, solo una instancia de Singletonpuede existir en el aislamiento.
Seth Ladd
1
Seth, no tienes razón. No hay forma en Dart de construir un verdadero singleton, ya que no hay forma de restringir la instanciación de una clase dentro de la biblioteca de declaración. Siempre requiere disciplina del autor de la biblioteca. En su ejemplo, la biblioteca declarante puede llamar new Singleton._internal()tantas veces como quiera, creando muchos objetos de la Singletonclase. Si la Implclase en el ejemplo de Andrew fuera privada ( _Impl), sería la misma que su ejemplo. Por otro lado, singleton es un antipatrón y nadie debería usarlo de todos modos.
Ladicek
@Ladicek, no confíes en que los desarrolladores de una biblioteca no llamen nuevo Singelton._internal(). Puede argumentar que los desarrolladores de la clase singelton también podrían instaurar la clase varias veces. Claro que existe el enum singelton, pero para mí es solo de uso teórico. Una enumeración es una enumeración, no un singelton ... En cuanto al uso de variables de nivel superior (@Andrew y @Seth): ¿Nadie podría escribir en la variable de nivel superior? De ninguna manera está protegido, ¿o me estoy perdiendo algo?
Tobias Ritzau
12

Aquí hay otra forma posible:

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
fuente
11

Dart Singleton de 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()));
}
Ticore Shih
fuente
5

Singleton que no puede cambiar el objeto después de la instancia

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
fuente
4

Respuesta de @Seth Ladd modificada para quién prefiere el estilo Swift de singleton como .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;
}

Muestra:

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

Después de leer todas las alternativas se me ocurrió esto, que me recuerda un "singleton clásico":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
daveoncode
fuente
3
Cambiaría el getInstancemétodo en una instancepropiedad como esta:static AccountService get instance => _instance;
gianlucaparadise
me gusta esto. ya que quiero agregar algo antes de que se devuelva la instancia y se utilicen otros métodos.
Chitgoks
4

Aquí hay una respuesta simple:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

Aquí hay un ejemplo conciso que combina las otras soluciones. El acceso al singleton se puede hacer mediante:

  • Usando una singletonvariable global que apunta a la instancia.
  • El Singleton.instancepatrón común .
  • Usando el constructor predeterminado, que es una fábrica que devuelve la instancia.

Nota: Debe implementar solo una de las tres opciones para que el código que use el singleton sea coherente.

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 necesita hacer una inicialización compleja, solo tendrá que hacerlo antes de usar la instancia más adelante en el programa.

Ejemplo

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
fuente
1

Hola, ¿qué tal algo como esto? Implementación muy simple, el inyector en sí mismo es singleton y también agregó clases en él. Por supuesto, se puede extender muy fácilmente. Si está buscando algo más sofisticado, consulte este paquete: 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
fuente
0

Esto debería funcionar.

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

    _(){

    }
    factory GlobalStore()=> instance;


}
Vilsad PP
fuente
No publique preguntas de seguimiento como respuestas. El problema con este código es que es un poco detallado. static GlobalStore get instance => _instance ??= new GlobalStore._();haría. ¿Qué se _(){}supone que debe hacer? Esto parece redundante.
Günter Zöchbauer
lo siento, fue una sugerencia, no una pregunta de seguimiento, _ () {} creará un constructor privado, ¿verdad?
Vilsad PP
Los constructores comienzan con el nombre de la clase. Este es solo un método de instancia privada normal sin un tipo de retorno especificado.
Günter Zöchbauer
1
Perdón por el voto negativo, pero creo que es de baja calidad y no agrega ningún valor además de las respuestas existentes.
Günter Zöchbauer
2
Si bien este código puede responder la pregunta, proporcionar un contexto adicional con respecto a cómo y / o por qué resuelve el problema mejoraría el valor a largo plazo de la respuesta.
Karl Richter
0

Como no soy muy aficionado a usar la newpalabra clave u otro constructor como llamadas en singletons, preferiría usar un getter estático llamado, instpor ejemplo:

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

ejemplo de uso:

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
fuente