Як ви будуєте одиночку в Дарт?


205

Однотонний візерунок забезпечує лише один екземпляр класу. Як я будую це в Dart?


Нижче я бачив кілька відповідей, де описано кілька способів складання одиночного класу. Тому я замислююся над тим, чому нам не подобається цей об’єкт class_name; якщо (object == null) повернути об'єкт = new class_name; Ще повернути об’єкт
Авніш кумар

Відповіді:


326

Завдяки заводським конструкторам 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
}

2
Хоча який сенс його інстанціювати двічі? Чи не повинно бути краще, якщо він кинув помилку, коли ви інстанціюєте її вдруге?
westoque

53
Я не інстантую це двічі, просто отримую посилання на об'єкт Singleton двічі. Ви, мабуть, цього не робили б двічі поспіль у реальному житті :) Я не хотів би відкидати виняток, я просто хочу той самий екземпляр одиночної форми кожного разу, коли я кажу "новий Singleton ()". Я визнаю, це трохи заплутано ... newне означає "побудувати нове" тут, це просто говорить "запустити конструктор".
Сет Ладд

1
Що саме тут працює ключове слово заводу? Це суто анотувати реалізацію. Чому це потрібно?
Καrτhικ

4
Це щось заплутано, що ви використовуєте конструктор для отримання екземпляра. newКлючове слово передбачає , що клас конкретизується, що це не так . Я б пішов на статичний метод get()або getInstance()як я роблю в Java.
Стівен Руз

11
@SethLadd це дуже приємно, але я вважаю, що йому потрібно кілька пунктів пояснень. Існує дивний синтаксис Singleton._internal();, схожий на виклик методу, коли це дійсно визначення конструктора. Там _internalназва. І є прекрасний мовний дизайн, який дозволяє Дартру запускати (виривати?) За допомогою звичайного конструктора, а потім, якщо потрібно, змінювати його на factoryметод, не змінюючи всіх абонентів.
Джеррі101

174

Ось порівняння декількох різних способів створити сингл у 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;

Примітка:

Я спочатку ставив це як питання , але виявив, що всі вищезазначені методи є дійсними і вибір багато в чому залежить від особистих переваг.


3
Я просто підтримав вашу відповідь. Набагато чіткіше прийнятої відповіді. Ще одне питання: для другого і третього способу, який сенс приватного конструктора? Я бачив, що багато людей це робили, але не розумію сенсу. Я завжди просто користуюся static final SingletonThree instance = SingletonThree(). Те ж саме стосується другого способу _instance. Я не знаю, у чому мінус не використовувати приватний конструктор. Поки що я не знаходжу жодних проблем на шляху. Другий і третій способи жодним чином не блокують виклик конструктора за замовчуванням.
sgon00

3
@ 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');
   }
}

головна.дарт

import 'package:thing/thing.dart';

main() {
  print('#1');
  thing.foo();
}

Зауважте, що сингл не створюється до першого виклику геттера через ліниву ініціалізацію Дарта.

Якщо ви віддаєте перевагу, ви можете також застосувати одиночні кнопки як статичний геттер у класі однотонних. тобто Thing.singletonзамість геттера верхнього рівня.

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


1
Це має більше сенсу для мене, завдяки Грегу та особливості властивості дартса верхнього рівня.
Eason PI

Це не ідіоматично. Це особливість мрії - створити однотонний візерунок на мові, і ви його викидаєте, оскільки ви до цього не звикли.
Араш

1
І приклад Сет, і цей приклад є однотонними візерунками. Це справді питання синтаксису "новий Singleton ()" проти "singleton". Я вважаю останнє більш зрозумілим. Фабричні конструктори Dart є корисними, але я не думаю, що це для них корисний випадок. Я також вважаю, що лінива ініціалізація Дарта є чудовою особливістю, яку недостатньо використовують. Читайте також статтю Боба вище - він рекомендує проти одинаків у більшості випадків.
Грег Лоу

Я також рекомендую прочитати цю тему в списку розсилки. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Грег Лоу

Це набагато краще. "Нове" ключове слово досить сильно передбачає побудову нового об'єкта. Прийняте рішення відчуває себе справді неправильним.
Сава Б.

16

А що з просто використанням глобальної змінної у вашій бібліотеці?

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

Або це нахмурилося?

Однотонний візерунок необхідний на Java, де поняття глобалів не існує, але, схоже, вам не потрібно було б пройти довгий шлях у Дартсі.


5
Змінні верхнього рівня класні. Однак кожен, хто може імпортувати single.dart, вільний створити "новий Impl ()". Ви можете дати конструктору підкреслення Impl, але тоді код всередині бібліотеки сингл може викликати цей конструктор.
Сет Ладд

І код у вашій реалізації не може? Чи можете ви пояснити у своїй відповіді, чому це краще, ніж змінна верхнього рівня?
січня

2
Привіт @Jan, це не краще і не гірше, це просто інше. У прикладі Ендрю, Impl не є однотонним класом. Він правильно використав змінну верхнього рівня, щоб полегшити доступ до примірника Singleton. У моєму прикладі вище, Singletonклас - це справжній сингтон, в ізоляті Singletonколи-небудь може існувати лише один екземпляр .
Сет Ладд

1
Сет, ти не правий. У Дарті немає можливості побудувати справжній синглтон, оскільки немає можливості обмежити імстабільність класу всередині бібліотеки декларування. Це завжди вимагає дисципліни від автора бібліотеки. У вашому прикладі бібліотека, що оголошує, може викликати new Singleton._internal()стільки разів, скільки хоче, створюючи безліч об'єктів Singletonкласу. Якби Implклас на прикладі Ендрю був приватним ( _Impl), він був би таким самим, як і ваш приклад. З іншого боку, одиночний антипатерн, і ніхто його не повинен використовувати.
Ладичек

@Ladicek, чи не довіряєте розробникам бібліотеки, щоб вони не телефонували новим Singelton._internal(). Ви можете стверджувати, що розробники класу singelton також могли кілька разів інсталювати клас. Впевнений, що є 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;
}

11

Дарт синглтон від конструктора const & 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

Синглтон, який не може змінити об'єкт після екземпляра

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

Модифікована @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';

4

Прочитавши всі альтернативи, я придумав це, що нагадує мені "класичний синглтон":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}

3
Я змінив би getInstanceметод у instanceтакій властивості:static AccountService get instance => _instance;
gianlucaparadise

мені подобається це. оскільки я хочу додати якусь річ до повернення екземпляра та інших методів.
читгокси

4

Ось проста відповідь:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

3

Ось стислий приклад, який поєднує інші рішення. Доступ до синглтону можна зробити:

  • Використання a 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
}

1

Привіт, що з подібним? Дуже проста реалізація, сам Injector є однотонним, а також додав до нього класи. Звичайно, це можна легко продовжити. Якщо ви шукаєте щось більш складне, перевірте цей пакет: 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

Це має спрацювати.

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

Вибачте, що це було пропозицією, а не подальшим запитанням, _ () {} створить приватний конструктор правильно?
Вільсад ПП

Конструктори починаються з назви класу. Це просто звичайний метод приватного примірника без вказаного типу повернення.
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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.