Como você constrói um Singleton no Dart?


203

O padrão singleton garante que apenas uma instância de uma classe seja criada. Como faço para criar isso no Dart?


Vi várias respostas abaixo, que descrevem várias maneiras de criar uma classe única. Então, estou pensando por que não gostamos desse objeto class_name; if (object == null) retorna object = new class_name; outro objeto de retorno
Avnish kumar

Respostas:


321

Graças aos construtores de fábrica da Dart , é fácil criar um singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Você pode construir assim

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

2
Embora qual o sentido de instanciar duas vezes? Não deveria ser melhor se ocorresse um erro ao instancia-lo pela segunda vez?
westoque

53
Não estou instanciando duas vezes, apenas obtendo uma referência a um objeto Singleton duas vezes. Você provavelmente não faria isso duas vezes seguidas na vida real :) Eu não gostaria que uma exceção fosse lançada, só quero a mesma instância singleton toda vez que digo "new Singleton ()". Eu admito, é um pouco confuso ... newnão significa "construir um novo" aqui, apenas diz "executar o construtor".
Seth Ladd

1
O que exatamente a palavra-chave de fábrica serve aqui? É apenas uma anotação da implementação. Por que é necessário?
Καrτhικ

4
É meio confuso que você esteja usando um construtor para obter a instância. A newpalavra-chave sugere que a classe é instanciada, o que não é. Eu iria para um método estático get()ou getInstance()como eu faço em Java.
Steven Roose

11
@SethLadd isso é muito bom, mas sugiro que ele precise de alguns pontos de explicação. Existe a sintaxe estranha Singleton._internal();que se parece com uma chamada de método quando é realmente uma definição de construtor. Aqui está o _internalnome. E há o ponto de design da linguagem bacana que o Dart permite que você inicie (dart out?) Usando um construtor comum e, se necessário, altere-o para um factorymétodo sem alterar todos os chamadores.
precisa saber é o seguinte

169

Aqui está uma comparação de várias maneiras diferentes de criar um singleton no Dart.

1. Construtor de fábrica

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Campo estático com getter

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

Como instanstiate

Os singletons acima são instanciados assim:

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

Nota:

Eu originalmente fiz isso como uma pergunta , mas descobri que todos os métodos acima são válidos e a escolha depende em grande parte da preferência pessoal.


3
Acabei de votar em sua resposta. Muito mais claro que a resposta aceita. Apenas mais uma pergunta: para a segunda e terceira via, qual é o sentido do construtor privado? Vi muitas pessoas fazendo isso, mas não entendo o ponto. Eu sempre simplesmente uso static final SingletonThree instance = SingletonThree(). O mesmo vale para o segundo caminho para _instance. Não sei qual é a desvantagem de não usar um construtor privado. Até agora, não encontro nenhum problema no meu caminho. A segunda e a terceira maneira não estão bloqueando a chamada para o construtor padrão de qualquer maneira.
sgon00

3
@ sgon00, o construtor privado é para que você não possa criar outra instância. Caso contrário, alguém poderia fazer SingletonThree instance2 = SingletonThree(). Se você tentar fazer isso quando houver um construtor privado, receberá o erro:The class 'SingletonThree' doesn't have a default constructor.
Suragch

40

Não acho uma leitura muito intuitiva new Singleton(). Você precisa ler os documentos para saber quenew na verdade, não está criando uma nova instância, como faria normalmente.

Aqui está outra maneira de fazer singletons (basicamente o que Andrew disse acima).

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

Observe que o singleton não é criado até a primeira vez que o getter é chamado devido à inicialização lenta do Dart.

Se preferir, também é possível implementar singletons como getter estático na classe singleton. ieThing.singleton , em vez de um getter de nível superior.

Leia também a opinião de Bob Nystrom sobre singletons de seu livro de padrões de programação de jogos .


1
Isso faz mais sentido para mim, graças a Greg e ao recurso de propriedade de nível superior do dardo.
Eason PI

Isso não é idiomático. É um recurso de sonho ter um padrão de singleton construído no idioma, e você o está descartando porque não está acostumado.
Arash

1
O exemplo de Seth e este exemplo são padrões únicos. É realmente uma questão de sintaxe "novo Singleton ()" vs "singleton". Acho o último mais claro. Os construtores de fábrica da Dart são úteis, mas não acho que esse seja um bom caso de uso para eles. Eu também acho que a inicialização preguiçosa de Dart é um ótimo recurso, que é subutilizado. Leia também o artigo de Bob acima - ele recomenda contra singletons na maioria dos casos.
Greg Lowe

Eu também recomendo a leitura deste tópico na lista de discussão. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe

Assim é bem melhor. A palavra-chave "new" implica bastante na construção de um novo objeto. A solução aceita parece realmente errada.
Sava B.

16

Que tal usar apenas uma variável global em sua biblioteca?

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 isso é desaprovado?

O padrão singleton é necessário em Java, onde o conceito de globais não existe, mas parece que você não precisa percorrer o caminho mais longo no Dart.


5
Variáveis ​​de nível superior são legais. No entanto, qualquer pessoa que possa importar single.dart pode criar um "novo Impl ()". Você poderia dar um construtor de sublinhado ao Impl, mas o código dentro da biblioteca singleton poderia chamar esse construtor.
Seth Ladd

E o código na sua implementação não pode? Você pode explicar em sua resposta por que é melhor do que uma variável de nível superior?
Janeiro

2
Oi @Jan, não é melhor ou pior, é apenas diferente. No exemplo de Andrew, o Impl não é uma classe singleton. Ele usou corretamente uma variável de nível superior para facilitar o Singletonacesso à instância . No meu exemplo acima, a Singletonclasse é um singleton real, apenas uma instância de Singletonpode existir no isolado.
Seth Ladd

1
Seth, você não está certo. Não como o Dart criar um verdadeiro singleton, pois não há como restringir a instabilidade de uma classe dentro da biblioteca declaradora. Sempre requer disciplina do autor da biblioteca. No seu exemplo, a biblioteca declaradora pode chamar new Singleton._internal()quantas vezes quiser, criando muitos objetos da Singletonclasse. Se a Implclasse no exemplo de Andrew fosse private ( _Impl), seria a mesma que o seu exemplo. Por outro lado, o singleton é um antipadrão e ninguém deve usá-lo de qualquer maneira.
Ladicek

@Ladicek, não confie nos desenvolvedores de uma biblioteca para não chamar de novo Singelton._internal(). Você pode argumentar que os desenvolvedores da classe singelton também poderiam instatá-la várias vezes. Claro que existe o enum singelton, mas para mim é apenas de uso teórico. Um enum é um enum, não um singelton ... Quanto ao uso de variáveis ​​de nível superior (@Andrew e @Seth): ninguém poderia escrever na variável de nível superior? É de nenhuma maneira protegida, ou estou faltando alguma coisa?
Tobias Ritzau 9/10/12

12

Aqui está outra maneira possível:

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 por 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 que não pode alterar o objeto após a instância

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

Resposta modificada de @Seth Ladd para quem prefere o 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;
}

Amostra:

Auth.shared.username = 'abc';

4

Depois de ler todas as alternativas, eu vim com isso, o que me lembra um "singleton clássico":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}

3
Eu iria mudar o getInstancemétodo em uma instancepropriedade como esta:static AccountService get instance => _instance;
gianlucaparadise

eu gosto disso. desde que eu quero adicionar alguma coisa antes que a instância seja retornada e outros métodos sejam usados.
chitgoks 25/06

4

Aqui está uma resposta simples:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

3

Aqui está um exemplo conciso que combina as outras soluções. O acesso ao singleton pode ser feito por:

  • Usando um singleton variável global que aponta para a instância.
  • O comum Singleton.instance padrão .
  • Usando o construtor padrão, que é uma fábrica que retorna a instância.

Nota: Você deve implementar apenas uma das três opções para que o código usando o singleton seja consistente.

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 você precisar fazer uma inicialização complexa, precisará fazê-lo antes de usar a instância posteriormente no programa.

Exemplo

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

Olá, que tal algo assim? Implementação muito simples, o próprio Injector é único e também adicionou classes a ele. Claro que pode ser estendido com muita facilidade. Se você está procurando algo mais sofisticado, verifique este pacote: 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

Isso deve funcionar.

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

    _(){

    }
    factory GlobalStore()=> instance;


}

Não poste perguntas de acompanhamento como respostas. O problema com esse código é que ele é um pouco detalhado. static GlobalStore get instance => _instance ??= new GlobalStore._();faria. O que _(){}deveria fazer? Isso parece redundante.
Günter Zöchbauer 13/10

desculpe, isso foi uma sugestão, não uma pergunta de acompanhamento, _ () {} criará um construtor privado, certo?
Vilsad PP

Os construtores começam com o nome da classe. Este é apenas um método de instância privada normal sem um tipo de retorno especificado.
Günter Zöchbauer 13/10

1
Desculpe pelo voto negativo, mas acho que é de baixa qualidade e não agrega nenhum valor além das respostas existentes.
Günter Zöchbauer 13/10

2
Embora esse código possa responder à pergunta, fornecer um contexto adicional sobre como e / ou por que resolve o problema melhoraria o valor a longo prazo da resposta.
Karl Richter

0

Como não gosto muito de usar a newpalavra - chave ou outro construtor como chamadas em singletons, eu preferiria usar um getter estático chamado, instpor exemplo:

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

exemplo 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));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.