您如何在Dart中建立Singleton?


202

单例模式可确保仅创建一个类的实例。如何在Dart中构建它?


我在下面看到了几个答案,这些答案描述了制作类单例的几种方法。所以我在考虑为什么我们不喜欢这个class_name对象;if(object == null)return object = new class_name; 其他返回对象
Avnish kumar,

Answers:


320

多亏了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对象。在现实生活中,您可能不会连续执行两次:)我不想抛出异常,每次我说“ new Singleton()”时,我只想要同一个单例实例。我承认,这有点令人困惑…… new在这里并不意味着“构造一个新的”,而只是说“运行构造器”。
塞思·拉德

1
factory关键字究竟在这里提供什么服务?它纯粹是在注释实现。为什么需要它?
Καrτhικ

4
您正在使用构造函数获取实例有点令人困惑。该new关键字表明,类实例化,它不是。我会选择静态方法,get()或者getInstance()像在Java中那样。
史蒂文·罗斯2014年

11
@SethLadd这是非常好的,但是我建议需要一些解释。Singleton._internal();当它实际上是构造函数定义时,有一种看起来像方法调用的奇怪语法。有_internal名字 还有一个很不错的语言设计要点,即Dart允许您使用普通的构造函数开始(dart out?),然后在需要时将其更改为factory方法,而无需更改所有调用方。
Jerry101

169

这是在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

40

我觉得阅读不是很直观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();
}

请注意,由于Dart的延迟初始化,直到第一次调用getter时才创建单例。

如果愿意,还可以在singleton类上将singletons实现为静态getter。即Thing.singleton,而不是顶级吸气剂。

还可以从他的《游戏编程模式》一书中阅读鲍勃·尼斯特罗姆(Bob Nystrom)对单例的看法。


1
这对我来说更有意义,这要归功于Greg和dart顶级属性的功能。
Eason PI

这不是习惯用法。在语言中构建单例模式是一个梦想中的功能,并且由于不习惯而将其丢弃。
阿鲁什

1
塞思的示例和本示例都是单例模式。这实际上是语法“ new Singleton()”和“ singleton”的问题。我发现后者更加清楚。Dart的工厂构造函数很有用,但我认为这对他们来说不是一个好用例。我还认为Dart的惰性初始化是一个很好的功能,没有得到充分利用。另请阅读Bob的上述文章-在大多数情况下,他建议不要使用单例。
Greg Lowe

我还建议阅读邮件列表中的该线程。groups.google.com/a/dartlang.org/d/msg/misc/9dFnchCT4kA/...
格雷格·罗威

这样更好。“ new”关键字在很大程度上暗示了新对象的构造。公认的解决方案确实感觉不对。
SavaB。19年

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中,单例模式是必需的,但似乎您不需要在Dart中走很长一段路。


5
顶级变量很酷。但是,任何可以导入single.dart的人都可以自由构造“新的Impl()”。你可以给一个下划线构造函数默认地将Impl,但随后的代码的单身库可以调用构造函数。
塞斯·拉德

而且实现中的代码不能吗?您能否在答案中解释为什么它比顶级变量更好?
1

2
嗨,@ Jan,这不是好坏,只是有所不同。在安德鲁的示例中,Impl不是单例类。他确实使用了顶级变量来使实例Singleton易于访问。在上面的示例中,Singleton该类是一个真正的单例,Singleton隔离中只能存在一个实例。
塞斯·拉德

1
赛斯,你说的不对。有没有在飞镖的方式来建立一个真正的单身人士,因为没有限制的一类instantiability的方式申报库。它总是需要图书馆作者的纪律。在您的示例中,声明库可以new Singleton._internal()根据需要调用任意多次,从而创建了Singleton该类的许多对象。如果Impl安德鲁示例中的类为private(_Impl),则该示例与您的示例相同。另一方面,单例是反模式,任何人都不应使用它。
Ladicek 2012年

@Ladicek,难道您不相信图书馆的开发人员不要称呼new Singelton._internal()。您可以辩称,singelton类的开发人员也可以使该类实例化几次。当然有枚举singelton,但对我来说,这仅是理论上的用途。一个枚举是一个枚举,而不是一个singelton ...至于顶级变量(@Andrew和@Seth)的使用:没有人可以写顶级变量吗?它绝不是受保护的,还是我丢失了什么?
Tobias Ritzau,2012年

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

Dart Singleton通过const Constructor&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

我喜欢这个。因为我想在返回实例和使用其他方法之前添加一些东西。
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_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._();会做。什么是_(){}应该做的?这似乎是多余的。
君特Zöchbauer

抱歉,这是一个建议,而不是后续问题,_(){}将创建私有构造函数,对吗?
Vilsad PP '18

构造函数以类名开头。这只是一个普通的私有实例方法,没有指定返回类型。
君特Zöchbauer

1
对不起,我对此感到抱歉,但我认为它的质量很差,除了现有的答案之外,没有增加任何价值。
君特Zöchbauer

2
尽管此代码可以回答问题,但提供有关如何和/或为什么解决问题的其他上下文将提高​​答案的长期价值。
卡尔·里希特

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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.