Flutter 的单例模式

57 min read

单例模式常见应用

  • 全局日志的 Logger 类、应用全局的配置数据对象类,单业务管理类。
  • 创建实例时占用资源较多,或实例化耗时较长的类。
  • 等等…

通过 getter 实现单例

class Singleton {
  static Singleton _instance;
  static get instance {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
  
  Singleton._internal();
}

Dart 的 getter 的使用方式与普通方法大致相同,只是调用者不再需要使用括号

final singleton = Singleton.instance;

通过工厂构造函数实现单例

工厂构造函数(factory constructor)也原生具备了 不必每次都去创建新的类实例 的特性

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

  factory Singleton() {
    return _singleton;
  }

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

接受构造函数变量

class Singleton {
  int x =0 ;
  int y  =0 ;
  static final Singleton _singleton = Singleton._internal();

  factory Singleton(int x ,int y) {
    return _singleton;
  }

  Singleton._internal(){
    x =1;
    y =2;
  }
}
void main() {
  var s1 = Singleton(1,2);
  var s2 = Singleton(1,2);
  print(identical(s1, s2)); // true
  print(s1.x);
  print(s2.y);
}

Flutter 单例模式的懒汉模式

被标记为 late 的变量 _instance 的初始化操作将会延迟到字段首次被访问时执行,而不是在类加载时就初始化

class Singleton {
  Singleton._internal();
  
  factory Singleton() => _instance;
  
  static late final Singleton _instance = Singleton._internal();
}

factory工厂构造函数的使用场景

使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数

这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。

例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。

注意: 在工厂构造函数中无法访问 this。

如果想让抽象类同时可被实例化,可以为其定义 工厂构造函数。

场景1. 避免创建过多的重复实例,如果已创建该实例,则从缓存中拿出来。

class Logger {
  final String name;
  bool mute = false;

  // _cache 变量是库私有的,因为在其名字前面有下划线。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

场景2. 调用子类的构造函数,这是一种常见的设计模式

abstract class Animal {
  String? name;
  void getNoise();
  factory Animal(String type, String name) {
    switch (type) {
      case "cat":
        return new Cat(name);
      case "dog":
        return new Dog(name);
      default:
        throw "The '$type' is not an animal";
    }
  }
}

class Cat implements Animal {
  String? name;
  Cat(this.name);

  @override
  void getNoise() {
    print("${this.name}: 喵~");
  }
}

class Dog implements Animal {
  String? name;
  Dog(this.name);

  @override
  void getNoise() {
    print("${this.name}: 旺~");
  }
}
void main() {
  var cat = new Animal("cat", "花花");
  var dog = new Animal("dog", "小黑");
  cat.getNoise(); // 花花:  喵~
  dog.getNoise(); // 小黑: 旺~
}

场景3. 单例模式

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

  factory Singleton() {
    return _singleton;
  }

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

Flutter map的put和putIfAbsent使用

源码如下:

  default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
 
        return v;
}

源码中传入key和value,根据key获取看是否存在value,如果value==null,然后调用put方法把传入的key和value put进map,返回根据key获取的老value

意思就是:putIfAbsent 如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null

Dart 的几种构造函数

Dart构造函数有4种格式:

  • ClassName(...) //普通构造函数
  • Classname.identifier(...) //命名构造函数
  • const ClassName(...) //常量构造函数
  • factroy ClassName(...) //工厂构造函数
class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造函数,新增代码
  Point.origin() {
    x = 0;
    y = 0;
  }
}

命名构造函数方法体对对象进行初始化返回该类的实例

命名构造函数不可继承,如果子类想要有 和父类一样的命名构造函数,那就写个同名的(通常也会在子类的命名构造函数里,调用父类的同名命名构造函数)