Bagaimana Anda membangun Singleton di Dart?

203

Pola singleton memastikan hanya satu instance kelas yang pernah dibuat. Bagaimana cara membuat ini di Dart?

Seth Ladd
sumber
Saya telah melihat beberapa jawaban di bawah ini yang menggambarkan beberapa cara untuk membuat kelas tunggal. Jadi saya berpikir tentang mengapa kita tidak suka objek class_name ini; if (object == null) return object = new class_name; else return object
Avnish kumar

Jawaban:

321

Berkat konstruktor pabrik Dart , mudah membangun singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Anda dapat membangunnya seperti ini

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}
Seth Ladd
sumber
2
Meskipun apa gunanya instantiating dua kali? Bukankah seharusnya lebih baik jika ia melemparkan kesalahan saat Anda instantiate untuk yang kedua kalinya?
westoque
53
Saya tidak membuat instance dua kali, hanya mendapatkan referensi ke objek Singleton dua kali. Anda mungkin tidak akan melakukannya dua kali berturut-turut dalam kehidupan nyata :) Saya tidak ingin pengecualian dilemparkan, saya hanya ingin contoh singleton yang sama setiap kali saya mengatakan "Singleton baru ()". Saya akui, ini agak membingungkan ... newtidak berarti "membangun yang baru" di sini, itu hanya mengatakan "jalankan konstruktor".
Seth Ladd
1
Apa sebenarnya yang dilayani oleh kata kunci pabrik di sini? Ini murni menjelaskan implementasi. Mengapa itu diperlukan?
Καrτhικ
4
Agak membingungkan bahwa Anda menggunakan konstruktor untuk mendapatkan contoh. Kata newkunci menunjukkan bahwa kelas adalah instantiated, yang bukan. Saya akan menggunakan metode statis get()atau getInstance()seperti yang saya lakukan di Jawa.
Steven Roose
11
@SethLadd ini sangat bagus tapi saya sarankan perlu beberapa poin penjelasan. Ada sintaks aneh Singleton._internal();yang terlihat seperti pemanggilan metode ketika itu benar-benar definisi konstruktor. Itu _internalnamanya. Dan ada titik desain bahasa yang bagus dimana Dart memungkinkan Anda memulai (dart out?) Menggunakan konstruktor biasa dan kemudian, jika perlu, ubah ke factorymetode tanpa mengubah semua penelepon.
Jerry101
169

Berikut ini adalah perbandingan beberapa cara berbeda untuk membuat singleton di Dart.

1. Konstruktor pabrik

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Bidang statis dengan pengambil

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Bidang statis

class SingletonThree {

  SingletonThree._privateConstructor();

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

Bagaimana cara instan

Para lajang di atas dipakai seperti ini:

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

catatan:

Saya awalnya mengajukan ini sebagai pertanyaan , tetapi menemukan bahwa semua metode di atas valid dan pilihannya sangat tergantung pada preferensi pribadi.

Suragch
sumber
3
Saya baru saja mengangkat jawaban Anda. Jauh lebih jelas daripada jawaban yang diterima. Hanya satu pertanyaan lagi: untuk cara kedua dan ketiga, apa gunanya konstruktor pribadi? Saya melihat banyak orang melakukan itu, tetapi saya tidak mengerti intinya. Saya selalu hanya menggunakan static final SingletonThree instance = SingletonThree(). Hal yang sama berlaku untuk cara kedua _instance. Saya tidak tahu apa kerugiannya tidak menggunakan konstruktor pribadi. Sejauh ini, saya tidak menemukan masalah di jalan saya. Cara kedua dan ketiga tidak memblokir panggilan ke konstruktor default.
sgon00
3
@ sgon00, konstruktor pribadi adalah sehingga Anda tidak dapat membuat contoh lain. Kalau tidak ada yang bisa melakukannya SingletonThree instance2 = SingletonThree(). Jika Anda mencoba melakukan ini ketika ada konstruktor pribadi, Anda akan mendapatkan kesalahan:The class 'SingletonThree' doesn't have a default constructor.
Suragch
40

Saya tidak menemukan itu bacaan yang sangat intuitif new Singleton(). Anda harus membaca dokumen untuk mengetahui bahwa newsebenarnya tidak membuat contoh baru, seperti biasanya.

Inilah cara lain untuk melakukan lajang (Pada dasarnya apa yang dikatakan Andrew di atas).

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

Perhatikan bahwa singleton tidak dapat dibuat sampai pertama kali pengambil tersebut dipanggil karena inisialisasi malas dari Dart.

Jika Anda mau, Anda juga dapat menerapkan lajang sebagai pengambil statis di kelas lajang. yaitu Thing.singleton, bukannya pengambil tingkat atas.

Baca juga pendapat Bob Nystrom tentang para lajang dari buku pola pemrograman Game-nya .

Greg Lowe
sumber
1
Ini lebih masuk akal bagi saya, terima kasih kepada Greg dan fitur properti tingkat atas panah.
Eason PI
Ini bukan idiom. Ini adalah fitur mimpi untuk memiliki pola singleton membangun dalam bahasa, dan Anda membuangnya karena Anda tidak terbiasa.
Arash
1
Contoh Seth dan contoh ini adalah pola tunggal. Ini benar-benar pertanyaan tentang sintaks "Singleton baru ()" vs "singleton". Saya menemukan yang terakhir lebih jelas. Konstruktor pabrik Dart berguna, tetapi saya tidak berpikir ini adalah kasus penggunaan yang baik untuk mereka. Saya juga berpikir inisialisasi malas malas adalah fitur hebat, yang kurang dimanfaatkan. Baca juga artikel Bob di atas - ia merekomendasikan terhadap lajang dalam banyak kasus.
Greg Lowe
Saya juga merekomendasikan membaca utas ini di milis. groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe
Ini jauh lebih baik. Kata kunci "baru" cukup menyiratkan pembangunan objek baru. Solusi yang diterima terasa sangat salah.
Sava B.
16

Bagaimana dengan hanya menggunakan variabel global dalam perpustakaan Anda, seperti itu?

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

Atau ini disukai?

Pola tunggal diperlukan di Jawa di mana konsep global tidak ada, tetapi sepertinya Anda tidak perlu pergi jauh-jauh di Dart.

Seth Ladd
sumber
5
Variabel tingkat atas itu keren. Namun, siapa pun yang dapat mengimpor single.dart bebas membuat "Impl baru ()". Anda bisa memberikan konstruktor garis bawah ke Impl, tetapi kemudian kode di dalam pustaka tunggal dapat memanggil konstruktor itu.
Seth Ladd
Dan kode dalam implementasi Anda tidak bisa? Bisakah Anda menjelaskan dalam jawaban Anda mengapa lebih baik daripada variabel tingkat atas?
Jan
2
Hai @ Jan, ini tidak lebih baik atau lebih buruk, hanya saja berbeda. Dalam contoh Andrew, Impl bukan kelas tunggal. Dia benar menggunakan variabel level atas untuk membuat instance Singletonmudah diakses. Dalam contoh saya di atas, Singletonkelas adalah singleton nyata, hanya satu contoh yang Singletonbisa ada di isolat.
Seth Ladd
1
Seth, kamu tidak benar. Tidak ada cara di Dart untuk membangun singleton yang benar, karena tidak ada cara untuk membatasi instantiabilitas kelas di dalam perpustakaan yang mendeklarasikan. Itu selalu membutuhkan disiplin dari penulis perpustakaan. Dalam contoh Anda, perpustakaan yang mendeklarasikan dapat memanggil new Singleton._internal()sebanyak yang diinginkan, menciptakan banyak objek dari Singletonkelas. Jika Implkelas dalam contoh Andrew adalah pribadi ( _Impl), itu akan sama dengan contoh Anda. Di sisi lain, singleton adalah antipattern dan tidak ada yang harus menggunakannya.
Ladicek
@Ladicek, jangan Anda percaya pengembang perpustakaan untuk tidak memanggil baru Singelton._internal(). Anda dapat berargumen bahwa pengembang dari kelas singelton dapat membuat instance kelas beberapa kali juga. Tentu ada enum singelton tetapi bagi saya itu hanya penggunaan teori. Enum adalah enum, bukan singelton ... Adapun penggunaan variabel tingkat atas (@Andrew dan @Seth): Tidak bisakah ada yang menulis ke variabel tingkat atas? Itu sama sekali tidak dilindungi, atau apakah saya kehilangan sesuatu?
Tobias Ritzau
12

Berikut cara lain yang mungkin:

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
sumber
11

Dart singleton oleh konstruktor & pabrik

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
sumber
5

Singleton yang tidak dapat mengubah objek setelah contoh

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
sumber
4

Diubah @Seth Ladd untuk siapa yang lebih suka gaya Swift singleton seperti .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;
}

Sampel:

Auth.shared.username = 'abc';
DazChong
sumber
4

Setelah membaca semua alternatif saya menemukan ini, yang mengingatkan saya pada "singleton klasik":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}
daveoncode
sumber
3
Saya akan mengubah getInstancemetode di instanceproperti seperti ini:static AccountService get instance => _instance;
gianlucaparadise
saya suka ini. karena saya ingin menambahkan sesuatu sebelum instance dikembalikan dan metode lain digunakan.
chitgoks
4

Ini jawaban sederhana:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

Berikut adalah contoh singkat yang menggabungkan solusi lain. Mengakses singleton dapat dilakukan dengan:

  • Menggunakan singletonvariabel global yang menunjuk ke instance.
  • Singleton.instancePola umum .
  • Menggunakan konstruktor default, yang merupakan pabrik yang mengembalikan instance.

Catatan: Anda harus menerapkan hanya satu dari tiga opsi sehingga kode menggunakan singleton konsisten.

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

Jika Anda perlu melakukan inisialisasi yang kompleks, Anda hanya perlu melakukannya sebelum menggunakan instance nanti dalam program.

Contoh

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
sumber
1

Halo bagaimana dengan yang seperti ini? Implementasi yang sangat sederhana, Injector sendiri adalah singleton dan juga menambahkan kelas ke dalamnya. Tentu saja dapat diperpanjang dengan sangat mudah. Jika Anda mencari sesuatu yang lebih canggih, periksa paket ini: 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
sumber
0

Ini seharusnya bekerja.

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

    _(){

    }
    factory GlobalStore()=> instance;


}
Vilsad PP
sumber
Tolong jangan posting pertanyaan tindak lanjut sebagai jawaban. Masalah dengan kode ini adalah sedikit verbose. static GlobalStore get instance => _instance ??= new GlobalStore._();akan lakukan. Apa yang _(){}seharusnya dilakukan? Ini sepertinya berlebihan.
Günter Zöchbauer
maaf, itu saran, bukan pertanyaan tindak lanjut, _ () {} akan membuat konstruktor pribadi bukan?
Vilsad PP
Konstruktor mulai dengan nama kelas. Ini hanya metode instance pribadi normal tanpa tipe pengembalian yang ditentukan.
Günter Zöchbauer
1
Maaf untuk downvote, tapi saya rasa kualitasnya buruk dan tidak menambah nilai selain jawaban yang ada.
Günter Zöchbauer
2
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa memecahkan masalah akan meningkatkan nilai jangka panjang jawaban.
Karl Richter
0

Karena saya tidak terlalu suka menggunakan newkata kunci atau konstruktor lain seperti panggilan pada lajang, saya lebih suka menggunakan pengambil statis yang dipanggil instmisalnya:

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

contoh penggunaan:

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
sumber