Làm thế nào để bạn xây dựng một Singleton trong Dart?


202

Mẫu singleton đảm bảo chỉ có một phiên bản của một lớp được tạo. Làm thế nào để tôi xây dựng điều này trong Dart?


Tôi đã thấy một số câu trả lời dưới đây mô tả một số cách để tạo ra một singleton lớp. Vì vậy, tôi đang suy nghĩ về lý do tại sao chúng ta không thích đối tượng class_name này; if (object == null) return object = new class_name; đối tượng trả lại khác
Avarn kumar

Câu trả lời:


320

Cảm ơn các nhà xây dựng nhà máy của Dart , thật dễ dàng để xây dựng một singleton:

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

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

Bạn có thể xây dựng nó như thế này

main() {
  var s1 = Singleton();
  var s2 = Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}

2
Mặc dù điểm bắt đầu của nó hai lần là gì? Không nên tốt hơn nếu nó gây ra lỗi khi bạn khởi tạo nó lần thứ hai?
westoque

53
Tôi không khởi tạo nó hai lần, chỉ cần tham chiếu đến một đối tượng Singleton hai lần. Bạn có thể sẽ không làm điều đó hai lần liên tiếp trong cuộc sống thực :) Tôi sẽ không muốn ném ngoại lệ, tôi chỉ muốn cùng một ví dụ đơn lẻ mỗi khi tôi nói "Singleton mới ()". Tôi thừa nhận, nó hơi khó hiểu ... newkhông có nghĩa là "xây dựng một cái mới" ở đây, nó chỉ nói "chạy hàm tạo".
Seth Ladd

1
Chính xác thì từ khóa nhà máy phục vụ ở đây là gì? Nó hoàn toàn là chú thích việc thực hiện. Tại sao nó được yêu cầu?
Καrτhικ

4
Thật khó hiểu khi bạn đang sử dụng một hàm tạo để lấy cá thể. Các newtừ khóa gợi ý rằng lớp được khởi tạo, mà nó không phải là. Tôi sẽ dùng một phương thức tĩnh get()hoặc getInstance()giống như tôi làm trong Java.
Steven Roose

11
@SethLadd điều này rất hay nhưng tôi đề nghị nó cần một vài điểm giải thích. Có cú pháp kỳ lạ Singleton._internal();trông giống như một cuộc gọi phương thức khi nó thực sự là một định nghĩa hàm tạo. Có _internaltên. Và có một điểm thiết kế ngôn ngữ tiện lợi mà Dart cho phép bạn bắt đầu (phóng ra?) Bằng cách sử dụng một hàm tạo thông thường và sau đó, nếu cần, hãy thay đổi nó thành một factoryphương thức mà không thay đổi tất cả người gọi.
Jerry101

169

Dưới đây là so sánh một số cách khác nhau để tạo ra một singleton trong Dart.

1. Xây dựng nhà máy

class SingletonOne {

  SingletonOne._privateConstructor();

  static final SingletonOne _instance = SingletonOne._privateConstructor();

  factory SingletonOne() {
    return _instance;
  }

}

2. Trường tĩnh với getter

class SingletonTwo {

  SingletonTwo._privateConstructor();

  static final SingletonTwo _instance = SingletonTwo._privateConstructor();

  static SingletonTwo get instance => _instance;
  
}

3. Trường tĩnh

class SingletonThree {

  SingletonThree._privateConstructor();

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

Làm thế nào để kích thích

Các singletons ở trên được khởi tạo như thế này:

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

Ghi chú:

Ban đầu tôi đã hỏi điều này như một câu hỏi , nhưng phát hiện ra rằng tất cả các phương pháp trên đều hợp lệ và sự lựa chọn chủ yếu phụ thuộc vào sở thích cá nhân.


3
Tôi chỉ nêu lên câu trả lời của bạn. Rõ ràng hơn nhiều so với câu trả lời được chấp nhận. Chỉ còn một câu hỏi nữa: đối với cách thứ hai và thứ ba, điểm của nhà xây dựng tư nhân là gì? Tôi thấy nhiều người đã làm điều đó, nhưng tôi không hiểu vấn đề. Tôi luôn luôn đơn giản sử dụng static final SingletonThree instance = SingletonThree(). Cùng đi đến cách thứ hai cho _instance. Tôi không biết những bất lợi của việc không sử dụng một nhà xây dựng tư nhân. Cho đến nay, tôi không tìm thấy bất kỳ vấn đề theo cách của tôi. Cách thứ hai và thứ ba không chặn cuộc gọi đến hàm tạo mặc định.
sgon00

3
@ sgon00, hàm tạo riêng là để bạn không thể tạo một thể hiện khác. Nếu không thì ai cũng có thể làm được SingletonThree instance2 = SingletonThree(). Nếu bạn cố gắng làm điều này khi có một nhà xây dựng tư nhân, bạn sẽ nhận được lỗi:The class 'SingletonThree' doesn't have a default constructor.
Suragch

40

Tôi không thấy nó đọc rất trực quan new Singleton(). Bạn phải đọc các tài liệu để biết rằng newthực tế không tạo ra một thể hiện mới, như thường lệ.

Đây là một cách khác để làm singletons (Về cơ bản những gì Andrew đã nói ở trên).

lib / điều.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();
}

Lưu ý rằng singleton không được tạo cho đến lần đầu tiên getter được gọi do khởi tạo lười biếng của Dart.

Nếu bạn thích, bạn cũng có thể triển khai singletons dưới dạng getter tĩnh trên lớp singleton. tức là Thing.singleton, thay vì một getter cấp cao nhất.

Đồng thời đọc Bob Nystrom tham gia các singletons từ cuốn sách mô hình lập trình trò chơi của anh ấy .


1
Điều này có ý nghĩa hơn với tôi, nhờ Greg và tính năng của tài sản phi tiêu cấp cao nhất.
Eason PI

Đây không phải là thành ngữ. Đó là một tính năng mơ ước để có một mô hình đơn lẻ được xây dựng bằng ngôn ngữ và bạn đang ném nó đi vì bạn không quen với nó.
Arash

1
Cả ví dụ của Seth và ví dụ này đều là các mẫu đơn. Đây thực sự là một câu hỏi về cú pháp "new Singleton ()" vs "singleton". Tôi thấy sau này rõ ràng hơn. Các nhà xây dựng nhà máy của Dart rất hữu ích, nhưng tôi không nghĩ đây là trường hợp sử dụng tốt cho họ. Tôi cũng nghĩ khởi tạo lười biếng của Dart là một tính năng tuyệt vời, không được sử dụng. Cũng đọc bài viết của Bob ở trên - anh ấy khuyên bạn nên chống lại những người độc thân trong hầu hết các trường hợp.
Greg Lowe

Tôi cũng khuyên bạn nên đọc chủ đề này trong danh sách gửi thư. Groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/ mẹo
Greg Lowe

Đây là cách tốt hơn. Từ khóa "mới" khá nhiều ngụ ý việc xây dựng một đối tượng mới. Các giải pháp được chấp nhận cảm thấy thực sự sai.
Sava B.

16

Điều gì về việc chỉ sử dụng một biến toàn cục trong thư viện của bạn, như vậy?

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

Hay điều này là nhíu mày?

Mẫu đơn là cần thiết trong Java, nơi khái niệm toàn cầu không tồn tại, nhưng có vẻ như bạn không cần phải đi một chặng đường dài trong Dart.


5
Biến cấp cao nhất là mát mẻ. Tuy nhiên, bất cứ ai có thể nhập single.dart đều có thể tự do xây dựng "Impl ()" mới. Bạn có thể cung cấp một hàm tạo gạch dưới cho Impl, nhưng sau đó mã bên trong thư viện singleton có thể gọi hàm tạo đó.
Seth Ladd

Và mã trong thực hiện của bạn không thể? Bạn có thể giải thích trong câu trả lời của bạn tại sao nó tốt hơn một biến cấp cao nhất không?
Jan

2
Xin chào @Jan, nó không tốt hơn hay tệ hơn, nó chỉ khác biệt. Trong ví dụ của Andrew, Impl không phải là một lớp đơn. Anh ta đã sử dụng chính xác một biến cấp cao nhất để làm cho cá thể Singletondễ dàng truy cập. Trong ví dụ của tôi ở trên, Singletonlớp là một singleton thực sự, chỉ có một trường hợp duy nhất Singletoncó thể tồn tại trong sự cô lập.
Seth Ladd

1
Seth, bạn không đúng. Không cách nào trong Dart để xây dựng một singleton thực sự, vì không có cách nào để hạn chế tính tức thời của một lớp trong thư viện khai báo. Nó luôn đòi hỏi kỷ luật từ tác giả thư viện. Trong ví dụ của bạn, thư viện khai báo có thể gọi new Singleton._internal()bao nhiêu lần tùy ý, tạo ra rất nhiều đối tượng của Singletonlớp. Nếu Impllớp trong ví dụ của Andrew là private ( _Impl), thì nó sẽ giống như ví dụ của bạn. Mặt khác, singleton là một antipotype và dù sao cũng không nên sử dụng nó.
Ladicek

@Ladicek, bạn không tin tưởng các nhà phát triển thư viện không gọi mới Singelton._internal(). Bạn có thể lập luận rằng các nhà phát triển của lớp singelton cũng có thể tạo ra lớp này nhiều lần. Chắc chắn là có enumton enumton nhưng với tôi nó chỉ là sử dụng lý thuyết. Một enum là một enum, không phải là một singelton ... Đối với việc sử dụng các biến cấp cao nhất (@Andrew và @Seth): Không ai có thể ghi vào biến cấp cao nhất? Đó không phải là được bảo vệ, hoặc tôi đang thiếu một cái gì đó?
Tobias Ritzau

12

Đây là một cách có thể khác:

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

Đơn vị phi tiêu bởi const constructor & nhà máy

class Singleton {
  factory Singleton() =>
    const Singleton._internal_();
  const Singleton._internal_();
}


void main() {
  print(new Singleton() == new Singleton());
  print(identical(new Singleton() , new Singleton()));
}

5

Ví dụ như Singleton không thể thay đổi đối tượng

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

Câu trả lời @Seth Ladd đã sửa đổi cho những người thích phong cách singleton của Swift như .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;
}

Mẫu vật:

Auth.shared.username = 'abc';

4

Sau khi đọc tất cả các lựa chọn thay thế, tôi đã nghĩ ra cái này, nó gợi cho tôi một "singleton cổ điển":

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

  AccountService._internal();

  static AccountService getInstance() {
    return _instance;
  }
}

3
Tôi sẽ thay đổi getInstancephương thức trong một thuộc instancetính như thế này:static AccountService get instance => _instance;
gianlucaparadise

tôi thích điều này. vì tôi muốn thêm một số thứ trước khi cá thể được trả về và các phương thức khác được sử dụng.
chitgoks

4

Đây là một câu trả lời đơn giản:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

3

Đây là một ví dụ ngắn gọn kết hợp các giải pháp khác. Truy cập singleton có thể được thực hiện bằng cách:

  • Sử dụng một singletonbiến toàn cục trỏ đến thể hiện.
  • Các Singleton.instancemô hình phổ biến .
  • Sử dụng hàm tạo mặc định, là một nhà máy trả về thể hiện.

Lưu ý: Bạn chỉ nên thực hiện một trong ba tùy chọn để mã sử dụng singleton phù hợp.

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

Nếu bạn cần thực hiện khởi tạo phức tạp, bạn sẽ phải làm như vậy trước khi sử dụng ví dụ sau trong chương trình.

Thí dụ

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

Xin chào, những gì như thế này? Thực hiện rất đơn giản, Injection chính nó là singleton và cũng đã thêm các lớp vào nó. Tất nhiên có thể được mở rộng rất dễ dàng. Nếu bạn đang tìm kiếm thứ gì đó tinh vi hơn, hãy kiểm tra gói này: 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

Điều này nên làm việc.

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

    _(){

    }
    factory GlobalStore()=> instance;


}

Xin vui lòng không gửi câu hỏi tiếp theo như câu trả lời. Vấn đề với mã này là nó hơi dài dòng. static GlobalStore get instance => _instance ??= new GlobalStore._();sẽ làm. Phải _(){}làm gì đây? Điều này có vẻ dư thừa.
Günter Zöchbauer

xin lỗi, đó là một gợi ý, không phải là câu hỏi tiếp theo, _ () {} sẽ tạo một hàm tạo riêng phải không?
Vilsad PP

Các nhà xây dựng bắt đầu với tên lớp. Đây chỉ là một phương thức cá nhân bình thường mà không có kiểu trả về được chỉ định.
Günter Zöchbauer

1
Xin lỗi vì downvote, nhưng tôi nghĩ nó kém chất lượng và không thêm bất kỳ giá trị nào ngoài các câu trả lời hiện có.
Günter Zöchbauer

2
Mặc dù mã này có thể trả lời câu hỏi, việc cung cấp ngữ cảnh bổ sung về cách thức và / hoặc lý do giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời.
Karl Richter

0

Vì tôi không thích sử dụng newtừ khóa hoặc hàm tạo khác như các cuộc gọi trên singletons, tôi thích sử dụng một getter tĩnh được gọi là inství dụ:

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

sử dụng ví dụ:

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));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.