【Dart】dart构造函数学习记录(含dart单例模式写法)

普通构造函数

1、不指定构造函数,dart会为你创建一个默认的无参的构造函数

2、如果指定构造函数

和Java类似,dart中可以使用和class名称相同的函数作为其构造函数,例

class Student {
  int age = 0;
  int id = 0;

  Student(int age, int id) {
    this.age = age;
    this.id = id;
  }
}

上面的this表示的是当前类的实例,对dart来说,this是可以忽略的,但是在上面的例子中,因为类变量的名字和构造函数传入的参数的名字是一样的,所以需要加上this来进行区分。

简写方式:

class Student {
  int age = 0;
  int id = 0;

  Student(this.age, this.id);
}
命名构造函数

区别于其他语言,dart还可以使用命名构造函数

格式为

ClassName.identifier

class Student {
  int age = 0;
  int id = 0;

  Student(this.age, this.id);
  
  Student.fromJson(Map data) {
  	print("命名构造函数Student.fromJson");
  }
}

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

构造函数的执行顺序

dart中的类是可以继承的,那么下面我们来探究一下dart的子类的构造函数的执行顺序

如果不给dart类指定构造函数,那么dart会为类自动生成一个无参的构造函数,如果这个类是子类的话,则会自动调用父类的无参构造函数。

对于子类的构造函数来说,初始化的时候有三步:

1、调用初始化列表

2、调用父类的构造函数

3、调用自己的构造函数

在第2步中,如果父类没有默认的无参构造函数,则需要手动指定具体的父类构造函数。怎么调用呢?

可以直接在子类的构造函数后面使用:操作符接上父类的构造函数,例

class Student {
	String? firstName;
	Student.fromJson(Map data) {
		print("in Student");
	}
}

class Jone extends Student {
	Jone.fromJson(Map data) : super.from(data) {
		print("in Jone");
	}
}

理解了父类的构造函数之后,我们再来看一下初始化列表

初始化列表就是在构造函数执行之前执行的代码,和调用父类的构造函数一样,也使用:操作符,例

Point.fromJson(Map<String,double> json) : x = json['x']! , y = json['y'] {
	print('In Point.fromJson(): ($x, $y)');
}

初始化列表很有用,尤其是初始化那些final修饰的成员变量时,因为在方法体中,不能给final修饰的成员变量赋值

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
在构造函数中会被初始赋值的字段不要使用 late 进行修饰
///不推荐
class Test{
    late int x;
    Test(int p){
        x = p + 1;
    }
}
///推荐
class Test{
    int x;
    Test(int p)
    : x = p + 1;
}
重定向构造函数

如果一个构造函数需要调用另一个构造函数,而其本身并不进行任何变动,这可以使用重定向构造函数,重定向构造函数也是用:操作符,后面跟的另外的构造函数,例

class Point {
	double x,y;
	// 主构造函数
	Point(this.x, this.y);
	
	// 重定向构造函数
	Point.alongXAxix(double x) : this(x,0);
}
Constant构造函数

如果对象中的属性在创建之后,是不会变化的,则可以使用Constant构造函数,也就是在构造函数前面加上const修饰符,初始化的所有属性都要以final来修饰:

class ImmutablePoint {
	static const ImmutalbePoint origin = ImmutablePoint(0,0);
	
	final double x, y;
	const Immutable(this.x, this.y)
}
工厂构造函数

默认情况下,dart类中的构造函数返回的是该类的新实例,但是我们在实际的应用开发中可能会对返回的对象做些选择,比如从缓存中返回已经存在的对象,或者返回该类具体的实现子类。

为了实现这样的功能,dart中专门有一个Factory关键字,使用Factory的构造函数叫做工厂构造函数。

class Student {
	final String name;
	
	static final Map<String, Student> _studentMap = <String, Student>{};
	
	factory Student(String name) {
		return _studentMap.putIfAbsent(name, ()=>Student._newStudent(name));
	}
	
	factory Student.fromJson(Map<String, Object> json) {
		return Student(json['name'].toString())
	}
	
	Student._newStudent(this.name);
}

注意1,dart中只能有一个未命名的构造函数,对应命名构造函数来说,名字不能重复,否则会报The default constructor is already defined异常

注意2,工厂构造函数,没有权利访问this

上面的代码中,factory Student是一个未命名构造函数,而factory Student.fromJson则是一个命名构造函数。

所以如果你再给Student类添加一个未命名构造函数,如下

Student(this.name);

则会报错。

那么问题来了,factory构造函数和普通构造函数到底有什么区别呢?

他们最大的区别就是普通构造函数是没有返回值的,而factory构造函数需要一个返回值。

factory用来实现单例,详见 文章下面的单例模式

class Singleton {

  static final Singleton _singleton = Singleton.internal();
  factory Singleton() => _singleton;

  Singleton.internal();
}

工厂构造函数可以与命名构造函数常量构造函数结合使用

///常量构造函数
const TestModule(this.x);

///工厂构造函数 + 命名构造函数
factory TestModule.fromJson(Map map){
    ...省略
    return TestModule(map['x']);
}
私有构造函数

使用_下划线进行私有声明

class TestModule {
    //私有构造函数
    TestModule._origin();
}

总结,Dart构造函数有4中格式:

ClassName(...) // 普通构造函数
ClassName.identifier(...) // 命名构造函数
const ClassName(...) // 常量构造函数
factory ClassName(...) // 工厂构造函数

参考:

https://www.jianshu.com/p/24d0927f7625
https://juejin.cn/post/6844903902563794958
https://segmentfault.com/a/1190000040956384
https://dart.cn/guides/language/language-tour#constructors

单例模式

一般来说,要在代码中使用单例模式,结构上会有下面这些约定俗成的要求:

  • 单例类(Singleton)中包含一个引用自身类的静态属性实例(instance),且能自行创建这个实例。
  • 该实例只能通过静态方法 getInstance() 访问。
  • 类构造函数通常没有参数,且被标记为私有,确保不能从类外部实例化该类。

单例设计模式 UML 图,图源:https://www.uml-diagrams.org/class-reference.html

遵循以上这些要求,我们就不难能用 Dart 写出一个普通的单例模式:

class Singleton {
  static Singleton _instance;
  
  // 私有的命名构造函数
  Singleton._internal();
  
  static Singleton getInstance() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

同时,在实现单例模式时,也需要考虑如下几点,以防在使用过程中出现问题:

  • 是否需要懒加载,即类实例只在第一次需要时创建。
  • 是否线程安全,在 Java、C++ 等多线程语言中需要考虑到多线程的并发问题。由于 Dart 是单线程模型的语言,所有的代码通常都运行在同一个 isolate 中,因此不需要考虑线程安全的问题。
  • 在某些情况下,单例模式会被认为是一种 反模式,因为它违反了 SOLID 原则中的单一责任原则,单例类自己控制了自己的创建和生命周期,且单例模式一般没有接口,扩展困难。
  • 单例模式的使用会影响到代码的可测试性。如果单例类依赖比较重的外部资源,比如 DB,我们在写单元测试的时候,希望能通过 mock 的方式将它替换掉。而单例类这种硬编码式的使用方式,导致无法实现 mock 替换。

在实际编码过程中,单例模式常见应用有:

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

如上文所说的,Dart 语言作为单线程模型的语言,实现单例模式时,我们本身已经可以不用再去考虑 线程安全 的问题了。Dart 的很多其他特性也依然可以帮助到我们实现更加 Dart 化的单例。

使用 getter 操作符,可以打破单例模式中既定的,一定要写一个 getInstance() 静态方法的规则,简化我们必须要写的模版化代码,如下的 get instance:

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

Dart 的 getter 的使用方式与普通方法大致相同,只是调用者不再需要使用括号,这样,我们在使用时就可以直接使用如下方式拿到这个单例对象:

final singleton = Singleton.instance;

而 Dart 中特有的 工厂构造函数(factory constructor)也原生具备了 不必每次都去创建新的类实例 的特性,将这个特性利用起来,我们就可以写出更优雅的 Dart(able) 单例模式了,如下:

class Singleton {
  static Singleton _instance;
  
  Singleton._internal();
  
  // 工厂构造函数
  factory Singleton() {
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    
    return _instance;
  }
}

这里我们不再使用 getter 操作符额外提供一个函数,而是将单例对象的生成交给工厂构造函数,此时,工厂构造函数仅在第一次需要时创建 _instance,并之后每次返回相同的实例。这时,我们就可以像下面这样使用普通构造函数的方式获取到单例了:

final singleton = Singleton();

如果你还掌握了 Dart 空安全及箭头函数等特性,那么还可以使用另一种方式进一步精简代码,写出像下面这样 Dart 风味十足的代码:

class Singleton {
  static Singleton _instance;

  Singleton._internal() {
    _instance = this;
  }

  factory Singleton() => _instance ?? Singleton._internal();
}

这里,使用 ?? 作为 _instance 实例的判空操作符,如果为空则调用构造函数实例化否则直接返回,也可以达到单例的效果。

以上,Dart 单例中懒加载的无不是使用判空来实现的(if (_instance == null)??),但是在 Dart 空安全特性里还有一个非常重要的操作符 late ,它在语言层面就实现了实例的懒加载,如下面这个例子:

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

被标记为 late 的变量 _instance 的初始化操作将会延迟到字段首次被访问时执行,而不是在类加载时就初始化。这样,Dart 语言特有的单例模式的实现方式就这么产生了。

单例参考:
https://flutter.cn/community/tutorials/singleton-pattern-in-flutter-n-dart

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值