Как построить синглтон в Dart?

244

Шаблон singleton гарантирует, что когда-либо будет создан только один экземпляр класса. Как мне построить это в Dart?

Сет Лэдд
источник
Я видел несколько ответов ниже, в которых описывается несколько способов создания синглтона класса. Итак, я думаю о том, почему нам не нравится этот объект class_name; if (object == null) return object = new class_name; else return object
Авниш Кумар

Ответы:

370

Благодаря конструкторам фабрики Dart создать синглтон легко:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Вы можете построить это так

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Сет Лэдд
источник
3
Хотя какой смысл создавать его дважды? Разве не должно быть лучше, если он выдает ошибку при повторном создании экземпляра?
Westoque
62
Я не создаю его дважды, просто дважды получаю ссылку на объект Singleton. Вы, вероятно, не сделаете это дважды подряд в реальной жизни :) Я бы не хотел, чтобы генерировалось исключение, я просто хочу один и тот же экземпляр singleton каждый раз, когда я говорю «new Singleton ()». Признаюсь, это немного сбивает с толку ... newздесь не означает «создать новый», а просто «запустить конструктор».
Сет Лэдд,
1
Что именно здесь служит ключевым словом factory? Это чисто аннотирование реализации. Зачем это нужно?
Καrτhικ 03
6
Это немного сбивает с толку, что вы используете конструктор для получения экземпляра. newКлючевое слово предполагает , что класс конкретизируется, что это не так . Я бы выбрал статический метод get()или getInstance()как в Java.
Стивен Руз
13
@SethLadd, это очень мило, но я предлагаю пояснить пару моментов. Есть странный синтаксис, Singleton._internal();который выглядит как вызов метода, когда на самом деле это определение конструктора. Это _internalимя. И есть изящный дизайн языка, который Dart позволяет вам начать (выскочить?), Используя обычный конструктор, а затем, если необходимо, изменить его на factoryметод, не меняя всех вызывающих.
Jerry101
233

Вот сравнение нескольких различных способов создания синглтона в Dart.

1. Завод-конструктор

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Статическое поле с геттером

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Статическое поле

class SingletonThree {

  SingletonThree._privateConstructor();

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

Как создать экземпляр

Вышеупомянутые синглтоны создаются следующим образом:

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

Заметка:

Первоначально я задал это вопросом , но обнаружил, что все вышеперечисленные методы действительны и выбор во многом зависит от личных предпочтений.

Suragch
источник
3
Я только что проголосовал за твой ответ. Намного более ясный, чем принятый ответ. Еще один вопрос: для второго и третьего способа в чем смысл частного конструктора? Я видел, как многие люди так поступали, но не понимаю сути. Я всегда просто пользуюсь static final SingletonThree instance = SingletonThree(). То же самое и со вторым способом _instance. Я не знаю, в чем недостаток отказа от частного конструктора. Пока проблем на своем пути не нахожу. Второй и третий способы в любом случае не блокируют вызов конструктора по умолчанию.
sgon00
4
@ sgon00, частный конструктор таков, что вы не можете создать другой экземпляр. В противном случае это мог сделать кто угодно SingletonThree instance2 = SingletonThree(). Если вы попытаетесь сделать это, когда есть частный конструктор, вы получите сообщение об ошибке:The class 'SingletonThree' doesn't have a default constructor.
Suragch
41

Я не нахожу это интуитивным чтением new Singleton(). Вы должны прочитать документацию, чтобы знать, что на newсамом деле не создается новый экземпляр, как обычно.

Вот еще один способ делать синглтоны (в основном то, что сказал Эндрю выше).

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

Обратите внимание, что синглтон не создается до первого вызова метода получения из-за ленивой инициализации Дарта.

Если вы предпочитаете, вы также можете реализовать синглтоны в качестве статического получателя в одноэлементном классе. т.е. Thing.singletonвместо геттера верхнего уровня.

Также прочтите взгляд Боба Нистрома на синглтоны из его книги паттернов программирования игр .

Грег Лоу
источник
1
Для меня это имеет больше смысла, благодаря Грегу и особенности свойства dart верхнего уровня.
Eason PI
Это не идиоматика. Создание одноэлементного шаблона в языке - мечта, которую вы выбрасываете, потому что вы к ней не привыкли.
Араш
1
И пример Сета, и этот пример представляют собой одноэлементные шаблоны. Это действительно вопрос синтаксиса «new Singleton ()» против «singleton». Я считаю последнее более ясным. Конструкторы фабрики Дарта полезны, но я не думаю, что это хороший вариант их использования. Я также считаю, что ленивая инициализация Дарта - отличная функция, которой недостаточно. Также прочтите статью Боба выше - он не рекомендует использовать одиночные игры в большинстве случаев.
Грег Лоу
Я также рекомендую прочитать эту ветку в списке рассылки. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Грег Лоу,
Так лучше. Ключевое слово «new» в значительной степени подразумевает создание нового объекта. Принятое решение кажется действительно неправильным.
Сава Б.
20

Как насчет того, чтобы просто использовать глобальную переменную в вашей библиотеке, например?

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

Или это не одобряют?

Шаблон singleton необходим в Java, где не существует концепции глобальных объектов, но похоже, что вам не нужно долго обходить Dart.

Сет Лэдд
источник
5
Переменные верхнего уровня - это круто. Однако любой, кто может импортировать single.dart, может создать «новый Impl ()». Вы можете передать Impl конструктор подчеркивания, но тогда код внутри одноэлементной библиотеки может вызвать этот конструктор.
Сет Лэдд,
А код в вашей реализации не может? Можете ли вы объяснить в своем ответе, почему это лучше, чем переменная верхнего уровня?
янв., В
2
Привет @Jan, это не лучше и не хуже, просто другое. В примере Эндрю Impl не является одноэлементным классом. Он правильно использовал переменную верхнего уровня, чтобы облегчить Singletonдоступ к экземпляру . В моем примере выше Singletonкласс является настоящим синглтоном, только один экземпляр Singletonможет существовать в изоляте.
Сет Лэдд,
1
Сет, ты не прав. В Dart нет способа создать настоящий синглтон, поскольку нет способа ограничить возможность создания экземпляров класса внутри библиотеки объявления. Это всегда требует от автора библиотеки дисциплины. В вашем примере объявляющая библиотека может вызывать new Singleton._internal()сколько угодно раз, создавая множество объектов Singletonкласса. Если бы Implкласс в примере Эндрю был private ( _Impl), он был бы таким же, как ваш пример. С другой стороны, синглтон - это антипаттерн, и никому не следует его использовать.
Ladicek
@Ladicek, разве ты не веришь, что разработчики библиотеки не назовут новые Singelton._internal(). Вы можете возразить, что разработчики класса singleton также могут инсталлировать класс несколько раз. Конечно, есть enum singelton, но для меня это только теоретическое использование. Перечисление - это перечисление, а не одиночный элемент ... Что касается использования переменных верхнего уровня (@Andrew и @Seth): никто не мог писать в переменную верхнего уровня? Он ни в коем случае не защищен, или я что-то упустил?
Тобиас Ритцау,
12

Вот еще один возможный способ:

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
источник
11

Dart singleton от константного конструктора и фабрики

class Singleton {
  factory Singleton() =>
    Singleton._internal_();
  Singleton._internal_();
}
 
 
void main() {
  print(new Singleton() == new Singleton());
  print(identical(new Singleton() , new Singleton()));
}
Тикоре Ши
источник
6

Синглтон, который не может изменить объект после создания экземпляра

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, age: 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}
Лукас Брайтембах
источник
5

Прочитав все альтернативы, я пришел к такому выводу, который напоминает мне «классический синглтон»:

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
daveoncode
источник
3
Я бы изменил getInstanceметод в таком instanceсвойстве:static AccountService get instance => _instance;
gianlucaparadise
Мне это нравится. так как я хочу добавить что-то до того, как экземпляр будет возвращен и будут использоваться другие методы.
chitgoks
4

Измененный ответ @Seth Ladd для тех, кто предпочитает стиль Swift для синглтонов, например .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;
}

Образец:

Auth.shared.username = 'abc';
DazChong
источник
3

Вот краткий пример, объединяющий другие решения. Доступ к синглтону можно выполнить:

  • Использование singletonглобальной переменной, указывающей на экземпляр.
  • Общий Singleton.instanceшаблон.
  • Использование конструктора по умолчанию, который является фабрикой, возвращающей экземпляр.

Примечание. Вы должны реализовать только один из трех вариантов, чтобы код, использующий одноэлементный код, был согласованным.

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

Если вам нужно выполнить сложную инициализацию, вам просто нужно сделать это, прежде чем использовать экземпляр позже в программе.

пример

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
}
Джейкоб Филлипс
источник
3

Вот простой ответ:

class Singleton {
  static Singleton _instance;

  Singleton._();

  static Singleton get getInstance => _instance = _instance ?? Singleton._();
}
Хамед
источник
1

Привет, а как насчет этого? Очень простая реализация, Инжектор сам по себе синглтон, а также добавил в него классы. Конечно, можно очень легко расширить. Если вы ищете что-то более сложное, проверьте этот пакет: 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;
  }
}
Филип Джерга
источник
1

Вот как я использую синглтон в своих проектах

class FooAPI {
  foo() {
    // some async func to api
  }
}

class SingletonService {
  FooAPI _fooAPI;

  static final SingletonService _instance = SingletonService._internal();

  static SingletonService instance = SingletonService();

  factory SingletonService() {
    return _instance;
  }

  SingletonService._internal() {
    // TODO: add init logic if needed
    // FOR EXAMPLE API parameters
  }

  void foo() async {
    await _fooAPI.foo();
  }
}

void main(){
  SingletonService.instance.foo();
}
Султанмырза Касымбеков
источник
0

Это должно работать.

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._();сделал бы. Что _(){}делать? Это кажется лишним.
Günter Zöchbauer
извините, это было предложение, а не дополнительный вопрос, _ () {} создаст закрытый конструктор, верно?
Vilsad PP
Конструкторы начинаются с имени класса. Это просто обычный метод частного экземпляра без указанного типа возврата.
Günter Zöchbauer
1
Извините за отрицательный голос, но я думаю, что это плохое качество и не добавляет никакой ценности в дополнение к существующим ответам.
Гюнтер Цёхбауэр,
2
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и / или почему он решает проблему, улучшит долгосрочную ценность ответа.
Карл Рихтер
0

Поскольку я не очень люблю использовать newключевое слово или другой конструктор, например вызовы синглтонов, я бы предпочел использовать статический метод instполучения, например:

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

пример использования:

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
источник