คุณจะสร้างซิงเกิลตันที่ Dart ได้อย่างไร?


205

รูปแบบซิงเกิลทำให้มั่นใจได้ว่ามีเพียงคลาสเดียวเท่านั้นที่ถูกสร้างขึ้น ฉันจะสร้างสิ่งนี้ใน Dart ได้อย่างไร


ฉันเห็นคำตอบหลายข้อด้านล่างซึ่งอธิบายหลายวิธีในการสร้างชั้นเรียนเดี่ยว ดังนั้นฉันจึงคิดว่าทำไมเราไม่ชอบวัตถุ class_name นี้ ถ้า (object == null) return object = new class_name; else return object
Avnish kumar

คำตอบ:


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
ฉันไม่ได้ยกตัวอย่างมันสองครั้งเพียงแค่ได้รับการอ้างอิงถึงวัตถุเดี่ยวครั้ง คุณอาจจะไม่ทำสองครั้งติดต่อกันในชีวิตจริง :) ฉันไม่ต้องการให้มีข้อยกเว้นเกิดขึ้นฉันแค่ต้องการอินสแตนซ์ซิงเกิลเดียวกันทุกครั้งที่ฉันพูดว่า "ซิงเกิลใหม่ ()" ฉันยอมรับว่ามันค่อนข้างสับสน ... newไม่ได้หมายความว่า "สร้างใหม่" ที่นี่เพียงแค่บอกว่า "เรียกใช้ตัวสร้าง"
Seth Ladd

1
คำหลักจากโรงงานให้บริการตรงนี้คืออะไร มันเป็นคำอธิบายประกอบการใช้งานอย่างหมดจด ทำไมถึงต้องใช้?
Καrτhικ

4
มันค่อนข้างสับสนว่าคุณกำลังใช้ Constructor เพื่อรับอินสแตนซ์ newคำหลักที่แสดงให้เห็นว่าชั้นจะ instantiated ซึ่งมันไม่ได้เป็น ฉันจะใช้วิธีแบบคงที่get()หรือgetInstance()เหมือนที่ฉันทำใน Java
Steven Roose

11
@SethLadd นี่เป็นสิ่งที่ดีมาก แต่ฉันขอแนะนำให้ต้องการคำอธิบายสองสามข้อ มีไวยากรณ์แปลก ๆSingleton._internal();ที่ดูเหมือนว่าการเรียกเมธอดเมื่อนิยามคอนสตรัคเตอร์จริงๆ มี_internalชื่อ และมีจุดออกแบบภาษาที่ดีที่ Dart ให้คุณเริ่มต้น (dart out?) โดยใช้ Constructor ธรรมดาจากนั้นหากจำเป็นให้เปลี่ยนเป็นfactoryวิธีการโดยไม่ต้องเปลี่ยนผู้โทรทั้งหมด
Jerry101

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

วิธีการติดตั้ง

Singletons ด้านบนเป็นอินสแตนซ์นี้:

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

main.dart

import 'package:thing/thing.dart';

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

โปรดทราบว่าซิงเกิลตันจะไม่ถูกสร้างขึ้นจนกว่าจะมีการเรียกใช้ getter เป็นครั้งแรกเนื่องจากการเริ่มต้นที่ขี้เกียจของ Dart

หากคุณต้องการคุณยังสามารถใช้ singletons เป็น static getter ในคลาส singleton กล่าวคือThing.singletonแทนที่จะทะเยอทะยานระดับบนสุด

นอกจากนี้อ่านเอาบ๊อบ Nystrom บนsingletons จากเกมหนังสือรูปแบบการเขียนโปรแกรมของเขา


1
สิ่งนี้ทำให้ฉันเข้าใจได้ง่ายขึ้นขอบคุณ Greg และคุณสมบัติของคุณสมบัติระดับสูงของโผ
Eason PI

นี่ไม่ใช่สำนวน มันเป็นคุณสมบัติในฝันที่มีรูปแบบการสร้างเดี่ยวในภาษาและคุณกำลังขว้างมันออกเพราะคุณไม่คุ้นเคยกับมัน
Arash

1
ทั้งตัวอย่างของเซทและตัวอย่างนี้เป็นรูปแบบซิงเกิล เป็นคำถามเกี่ยวกับไวยากรณ์ "newtonton ()" vs "singleton" ฉันพบว่าหลังชัดเจนกว่า ช่างก่อสร้างโรงงานของ Dart มีประโยชน์ แต่ฉันไม่คิดว่านี่เป็นกรณีที่ใช้งานได้ดีสำหรับพวกเขา ฉันยังคิดว่าการเริ่มต้นขี้เกียจของ Dart เป็นคุณสมบัติที่ยอดเยี่ยมซึ่งใช้งานไม่ได้ อ่านบทความของ Bob ด้านบนด้วย - เขาแนะนำให้ต่อต้านซิงเกิลตันในกรณีส่วนใหญ่
Greg Lowe

ฉันยังแนะนำให้อ่านหัวข้อนี้ในรายชื่อผู้รับจดหมาย groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/…
Greg Lowe

นี่เป็นวิธีที่ดีกว่า คำหลัก "ใหม่" ค่อนข้างหนักหมายถึงการสร้างวัตถุใหม่ ทางออกที่ได้รับการยอมรับรู้สึกผิดอย่างมาก
Sava B.

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 ที่ไม่มีแนวคิดของ globals แต่ดูเหมือนว่าคุณไม่จำเป็นต้องไปไกลใน Dart


5
ตัวแปรระดับบนสุดเท่ห์ อย่างไรก็ตามทุกคนที่สามารถนำเข้า single.dart มีอิสระที่จะสร้าง "ใหม่ Impl ()" คุณสามารถให้ตัวสร้างขีดล่างกับ Impl ได้ แต่จากนั้นโค้ดภายในไลบรารี singleton สามารถเรียกตัวสร้างนั้นได้
เซทแลดด์

และรหัสในการนำไปใช้ของคุณไม่สามารถทำได้? คุณช่วยอธิบายได้ไหมว่าทำไมมันถึงดีกว่าตัวแปรระดับบนสุด?
Jan

2
สวัสดี @Jan มันไม่ได้ดีขึ้นหรือแย่ลงมันแตกต่างกัน ในตัวอย่างของแอนดรูว์ Impl ไม่ใช่คลาสซิงเกิล เขาใช้ตัวแปรระดับบนสุดอย่างถูกต้องSingletonเพื่อให้เข้าถึงได้ง่าย ในตัวอย่างของฉันด้านบนSingletonคลาสเป็นซิงเกิลจริงมีเพียงอินสแตนซ์เดียวเท่านั้นที่Singletonสามารถมีอยู่ในไอโซเลท
เซทแลดด์

1
เซทคุณไม่ถูกต้อง นอกจากนี้ยังไม่มีวิธีการในโผที่จะสร้าง singleton จริงตามที่มีวิธีการของการ จำกัด instantiability ของชั้นไม่มีภายในห้องสมุดประกาศ มันต้องมีระเบียบวินัยจากผู้เขียนห้องสมุดเสมอ ในตัวอย่างของคุณไลบรารีที่ประกาศสามารถเรียกได้new Singleton._internal()บ่อยเท่าที่ต้องการสร้างวัตถุจำนวนมากในSingletonชั้นเรียน หากImplคลาสในตัวอย่างของแอนดรูว์เป็นแบบส่วนตัว ( _Impl) มันจะเหมือนกับตัวอย่างของคุณ ในทางตรงข้ามซิงเกิลตันเป็นปฏิปักษ์และไม่มีใครควรใช้มันอย่างไรก็ตาม
Ladicek

@Ladicek Singelton._internal()คุณไม่ไว้วางใจนักพัฒนาของห้องสมุดจะไม่เรียกใหม่ คุณสามารถยืนยันได้ว่านักพัฒนาของชั้น Singelton สามารถติดตั้งชั้นเรียนหลายครั้งเช่นกัน แน่นอนว่ามี enum singelton แต่สำหรับฉันแล้วมันเป็นเพียงการใช้ตามหลักวิชาเท่านั้น enum เป็น enum ไม่ใช่ singelton ... สำหรับการใช้ตัวแปรระดับบนสุด (@Andrew และ @Seth): ทุกคนไม่สามารถเขียนถึงตัวแปรระดับบนสุดได้หรือไม่ มันไม่ได้รับการปกป้องหรือฉันขาดอะไรไป?
Tobias Ritzau

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

โผ Singleton โดย const คอนสตรัค & โรงงาน

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

Modified @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

ฉันชอบสิ่งนี้. เนื่องจากฉันต้องการเพิ่มบางสิ่งก่อนที่อินสแตนซ์จะถูกส่งกลับและใช้วิธีการอื่น
chitgoks

4

นี่คือคำตอบง่ายๆ:

class Singleton {
  static Singleton _instance;

  Singleton._();

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

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
}

1

สวัสดีแล้วเรื่องแบบนี้ล่ะ การใช้งานที่ง่ายมาก Injector นั้นเป็นซิงเกิลตันและยังเพิ่มคลาสเข้าไปอีกด้วย แน่นอนสามารถขยายได้อย่างง่ายดายมาก หากคุณกำลังมองหาบางสิ่งที่ซับซ้อนยิ่งขึ้นลองตรวจสอบแพ็คเกจนี้: https://pub.dartlang.org/packages/flutter_simple_dependency_inject

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;


}

โปรดอย่าโพสต์คำถามติดตามผลเป็นคำตอบ ปัญหาของรหัสนี้คือมันเป็น verbose เล็กน้อย static GlobalStore get instance => _instance ??= new GlobalStore._();จะทำ. สิ่งที่_(){}ควรทำ ดูเหมือนว่าซ้ำซ้อน
GünterZöchbauer

ขออภัยนั่นเป็นข้อเสนอแนะไม่ใช่คำถามติดตาม _ () {} จะสร้างนวกรรมิกส่วนตัวใช่ไหม
Vilsad PP

ตัวสร้างเริ่มต้นด้วยชื่อคลาส นี่เป็นเพียงวิธีส่วนตัวส่วนตัวปกติโดยไม่ระบุประเภทการคืนสินค้า
GünterZöchbauer

1
ขออภัยสำหรับ downvote แต่ฉันคิดว่าคุณภาพต่ำและไม่เพิ่มคุณค่าใด ๆ นอกเหนือจากคำตอบที่มีอยู่
GünterZöchbauer

2
ในขณะที่รหัสนี้อาจตอบคำถามให้บริบทเพิ่มเติมเกี่ยวกับวิธีการและ / หรือทำไมมันแก้ปัญหาจะปรับปรุงค่าระยะยาวของคำตอบ
Karl Richter

0

เนื่องจากฉันไม่ชอบการใช้newคำหลักหรือตัวสร้างอื่น ๆ เช่นการโทรบนซิงเกิลฉันจึงต้องการใช้ getter แบบสแตติกที่เรียก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));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.