Wzorzec singletonu gwarantuje, że kiedykolwiek zostanie utworzona tylko jedna instancja klasy. Jak zbudować to w Dart?
Wzorzec singletonu gwarantuje, że kiedykolwiek zostanie utworzona tylko jedna instancja klasy. Jak zbudować to w Dart?
Odpowiedzi:
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
}
newnie oznacza tutaj „skonstruowania nowego”, a jedynie „uruchom konstruktora”.
newKluczowe sugeruje, że klasa jest instancja, która nie jest. Wybrałbym metodę statyczną get()lub getInstance()jak ja w Javie.
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.
Oto porównanie kilku różnych sposobów na utworzenie singletona w Dart.
class SingletonOne {
SingletonOne._privateConstructor();
static final SingletonOne _instance = SingletonOne._privateConstructor();
factory SingletonOne() {
return _instance;
}
}
class SingletonTwo {
SingletonTwo._privateConstructor();
static final SingletonTwo _instance = SingletonTwo._privateConstructor();
static SingletonTwo get instance => _instance;
}
class SingletonThree {
SingletonThree._privateConstructor();
static final SingletonThree instance = SingletonThree._privateConstructor();
}
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.
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.
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.
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 .
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.
Singleton. W moim przykładzie powyżej Singletonklasa jest prawdziwym singletonem, tylko jedna instancja Singletonmoże kiedykolwiek istnieć w izolatce.
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ć.
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?
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;
}
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()));
}
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}
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';
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;
}
}
getInstancemetodę w takiej instancenieruchomości:static AccountService get instance => _instance;
Oto zwięzły przykład, który łączy inne rozwiązania. Dostęp do singletonu można uzyskać poprzez:
singletonzmiennej globalnej, która wskazuje na instancję.Singleton.instancewzór.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
}
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;
}
}
To powinno działać.
class GlobalStore {
static GlobalStore _instance;
static GlobalStore get instance {
if(_instance == null)
_instance = new GlobalStore()._();
return _instance;
}
_(){
}
factory GlobalStore()=> instance;
}
static GlobalStore get instance => _instance ??= new GlobalStore._();zrobiłaby. Co _(){}ma robić? To wydaje się zbędne.
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));