Jak budujesz Singleton w Dart?

203

Wzorzec singletonu gwarantuje, że kiedykolwiek zostanie utworzona tylko jedna instancja klasy. Jak zbudować to w Dart?

Seth Ladd
źródło
Widziałem kilka odpowiedzi poniżej, które opisują kilka sposobów na utworzenie singletonu klasowego. Zastanawiam się więc, dlaczego nie lubimy tego obiektu nazwa_klasy; if (object == null) return object = nowa nazwa_klasy; w przeciwnym razie zwróci obiekt
Avnish kumar

Odpowiedzi:

323

Dzięki fabrycznym konstruktorom Dart łatwo jest zbudować singletona:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Możesz to zbudować w ten sposób

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Seth Ladd
źródło
2
Chociaż po co dwa razy tworzyć instancję? Czy nie powinno być lepiej, jeśli po raz drugi wystąpił błąd?
westokalny
53
Nie tworzę go dwa razy, tylko dwa razy otrzymuję odniesienie do obiektu Singleton. Prawdopodobnie nie zrobiłbyś tego dwa razy z rzędu w prawdziwym życiu :) Nie chciałbym zgłaszać wyjątku, chcę tylko tę samą instancję singletonu za każdym razem, gdy mówię „new Singleton ()”. Przyznaję, jest to trochę mylące ... newnie oznacza tutaj „skonstruowania nowego”, a jedynie „uruchom konstruktora”.
Seth Ladd
1
Co dokładnie służy tutaj słowo kluczowe fabryka? Jest to wyłącznie adnotacja o implementacji. Dlaczego jest to wymagane?
Καrτhικ
4
To trochę mylące, że używasz konstruktora, aby uzyskać instancję. newKluczowe sugeruje, że klasa jest instancja, która nie jest. Wybrałbym metodę statyczną get()lub getInstance()jak ja w Javie.
Steven Roose
11
@SethLadd jest to bardzo miłe, ale sugeruję, że potrzebuje kilku wyjaśnień. Istnieje dziwna składnia, Singleton._internal();która wygląda jak wywołanie metody, gdy jest to naprawdę definicja konstruktora. Tam jest _internalnazwa. Jest też sprytny projektowy język, w którym Dart pozwala rozpocząć (rzutem?) Za pomocą zwykłego konstruktora, a następnie, w razie potrzeby, zmienić go na factorymetodę bez zmiany wszystkich wywołujących.
Jerry101
170

Oto porównanie kilku różnych sposobów na utworzenie singletona w Dart.

1. Konstruktor fabryki

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Pole statyczne z geterem

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Pole statyczne

class SingletonThree {

  SingletonThree._privateConstructor();

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

Jak zainicjować

Powyższe singletony są tworzone w następujący sposób:

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

Uwaga:

Początkowo zadałem to pytanie , ale odkryłem, że wszystkie powyższe metody są prawidłowe, a wybór w dużej mierze zależy od osobistych preferencji.

Suragch
źródło
3
Właśnie głosowałem twoją odpowiedź. Znacznie wyraźniej niż zaakceptowana odpowiedź. Jeszcze tylko jedno pytanie: po co drugi i trzeci sposób, jaki jest sens prywatnego konstruktora? Widziałem, jak wiele osób to robiło, ale nie rozumiem o co chodzi. Zawsze po prostu używam static final SingletonThree instance = SingletonThree(). To samo dotyczy drugiego sposobu _instance. Nie wiem, co jest wadą nieużywania prywatnego konstruktora. Jak dotąd nie mam żadnych problemów. Drugi i trzeci sposób i tak nie blokują wywołania domyślnego konstruktora.
sgon00
3
@ sgon00, prywatny konstruktor jest tak, że nie można utworzyć innej instancji. W przeciwnym razie każdy mógłby to zrobić SingletonThree instance2 = SingletonThree(). Jeśli spróbujesz to zrobić, gdy istnieje prywatny konstruktor, pojawi się błąd:The class 'SingletonThree' doesn't have a default constructor.
Suragch
40

Nie uważam tego za bardzo intuicyjne czytanie new Singleton(). Musisz przeczytać dokumenty, aby wiedzieć, że newtak naprawdę nie tworzy nowej instancji, jak zwykle.

Oto inny sposób na tworzenie singletonów (w zasadzie to, co Andrew powiedział powyżej).

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

Zauważ, że singleton nie zostanie utworzony do pierwszego wywołania gettera z powodu leniwej inicjalizacji Dart.

Jeśli wolisz, możesz także zaimplementować singletony jako statyczny getter w klasie singletonów. tj. Thing.singletonzamiast getter najwyższego poziomu.

Przeczytaj także wypowiedź Boba Nystroma dotyczącą singletonów ze swojej książki wzorców programowania gier .

Greg Lowe
źródło
1
Ma to dla mnie więcej sensu, dzięki Gregowi i funkcji najwyższego poziomu właściwości rzutki.
Eason PI
To nie jest idiomatyczne. To marzenie, aby mieć wzorzec singletonu w języku, a ty go wyrzucasz, ponieważ nie jesteś do tego przyzwyczajony.
Arash
1
Zarówno przykład Setha, jak i ten przykład są wzorcami singletonowymi. To naprawdę kwestia składni „new Singleton ()” vs „singleton”. Uważam to drugie za bardziej jasne. Konstruktorzy fabryki Dart są przydatni, ale nie sądzę, że jest to dla nich dobry przypadek użycia. Myślę również, że leniwa inicjalizacja Dart jest świetną funkcją, która jest niewykorzystana. Przeczytaj także powyższy artykuł Boba - w większości przypadków odradza on singletony.
Greg Lowe
Polecam również przeczytanie tego wątku na liście mailingowej. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe
To jest o wiele lepsze. Słowo kluczowe „nowy” dość mocno implikuje budowę nowego obiektu. Przyjęte rozwiązanie wydaje się naprawdę złe.
Sava B.
16

Co powiesz na takie użycie globalnej zmiennej w bibliotece?

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

A może to się marszczy?

Wzorzec singletonu jest konieczny w Javie, w której nie istnieje koncepcja globałów, ale wydaje się, że nie powinieneś iść daleko w Dart.

Seth Ladd
źródło
5
Zmienne najwyższego poziomu są fajne. Jednak każdy, kto może zaimportować single.dart, może utworzyć „nową Impl ()”. Możesz podać konstruktorowi podkreślenia do Impl, ale następnie kod w bibliotece singletona może wywołać tego konstruktora.
Seth Ladd,
A kod w twojej implementacji nie może? Czy potrafisz wyjaśnić w swojej odpowiedzi, dlaczego jest lepsza niż zmienna najwyższego poziomu?
stycznia
2
Cześć, Jan, nie jest ani lepsze ani gorsze, po prostu jest inne. W przykładzie Andrew Impl nie jest lekcją singleton. Prawidłowo użył zmiennej najwyższego poziomu, aby ułatwić dostęp do instancji Singleton. W moim przykładzie powyżej Singletonklasa jest prawdziwym singletonem, tylko jedna instancja Singletonmoże kiedykolwiek istnieć w izolatce.
Seth Ladd,
1
Seth, nie masz racji. W Dart nie ma sposobu na zbudowanie prawdziwego singletona, ponieważ nie ma sposobu na ograniczenie instancji klasy wewnątrz deklarującej biblioteki. Zawsze wymaga dyscypliny od autora biblioteki. W twoim przykładzie biblioteka deklarująca może wywoływać new Singleton._internal()tyle razy, ile chce, tworząc wiele obiektów Singletonklasy. Jeśli Implklasa w przykładzie Andrew była prywatna ( _Impl), byłaby taka sama jak twój przykład. Z drugiej strony singleton jest anty-wzorem i nikt nie powinien go używać.
Ladicek
@Ladicek, czy nie ufasz twórcom biblioteki, że nie będą nazywać nowych Singelton._internal(). Można argumentować, że twórcy klasy singelton mogliby również zainicjować klasę kilka razy. Pewnie, że istnieje enum singelton, ale dla mnie jest to tylko zastosowanie teoretyczne. Wyliczenie jest wyliczeniem, a nie pojedynczym ... Jeśli chodzi o użycie zmiennych najwyższego poziomu (@Andrew i @Seth): Czy nikt nie mógłby pisać do zmiennej najwyższego poziomu? W żadnym wypadku nie jest chroniony, czy coś mi brakuje?
Tobias Ritzau
12

Oto inny możliwy sposób:

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
źródło
11

Dart Singleton firmy Const Konstruktor i fabryka

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
źródło
5

Singleton, który nie może zmienić obiektu po instancji

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
źródło
4

Zmodyfikowano odpowiedź @Seth Ladd, która preferuje szybki styl singletonu, taki jak .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;
}

Próba:

Auth.shared.username = 'abc';
DazChong
źródło
4

Po przeczytaniu wszystkich alternatyw wymyśliłem to, co przypomina mi „klasyczny singleton”:

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
daveoncode
źródło
3
Zmieniłbym getInstancemetodę w takiej instancenieruchomości:static AccountService get instance => _instance;
gianlucaparadise
lubię to. ponieważ chcę dodać coś przed zwróceniem instancji i użyciem innych metod.
chitgoks,
4

Oto prosta odpowiedź:

class Singleton {
  static Singleton _instance;

  Singleton._();

  static Singleton get getInstance => _instance = _instance ?? Singleton._();
}
Hamed
źródło
3

Oto zwięzły przykład, który łączy inne rozwiązania. Dostęp do singletonu można uzyskać poprzez:

  • Korzystanie ze singletonzmiennej globalnej, która wskazuje na instancję.
  • Wspólny Singleton.instancewzór.
  • Korzystanie z domyślnego konstruktora, który jest fabryką zwracającą instancję.

Uwaga: Należy zaimplementować tylko jedną z trzech opcji, aby kod korzystający z singletonu był spójny.

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

Jeśli potrzebujesz skomplikowanej inicjalizacji, musisz to zrobić przed użyciem instancji później w programie.

Przykład

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
źródło
1

Cześć, a może coś takiego? Bardzo prosta implementacja, sam Injector jest singletonem, a także dodaje do niego klasy. Oczywiście można go bardzo łatwo przedłużyć. Jeśli szukasz czegoś bardziej zaawansowanego, sprawdź ten pakiet: 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
źródło
0

To powinno działać.

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

    _(){

    }
    factory GlobalStore()=> instance;


}
Vilsad PP
źródło
Nie publikuj pytań uzupełniających jako odpowiedzi. Problem z tym kodem polega na tym, że jest trochę zbyt szczegółowy. static GlobalStore get instance => _instance ??= new GlobalStore._();zrobiłaby. Co _(){}ma robić? To wydaje się zbędne.
Günter Zöchbauer
przepraszam, to była sugestia, a nie kolejne pytanie, _ () {} stworzy prywatnego konstruktora, prawda?
Vilsad PP
Konstruktory zaczynają się od nazwy klasy. Jest to zwykła metoda instancji prywatnej bez określonego typu zwrotu.
Günter Zöchbauer
1
Przepraszam za opinię, ale myślę, że jest złej jakości i nie dodaje żadnej wartości oprócz istniejących odpowiedzi.
Günter Zöchbauer
2
Chociaż ten kod może odpowiedzieć na pytanie, zapewnienie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Karl Richter
0

Ponieważ nie lubię używać newsłowa kluczowego lub innego konstruktora, takiego jak wywołania singletonów, wolałbym użyć statycznego gettera o nazwie instna przykład:

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

przykładowe użycie:

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
źródło