一、一等方法对象
Dart 是一个真正的面向对象语言,方法也是对象并且具有一种 类型 Function。 这意味着,方法可以赋值给变量,也可以当做其他方法的参数,同时也可以把方法当做参数调用另外一个方法
import 'dart:core';
void main() {
var list = ["黄药师", "郭靖", "小龙女"];
void printElement(element) {
print(element);
}
list.forEach(printElement);
}
在 Java 中如果需要能够通知调用者或者其他地方方法执行过程的各种情况,可能需要指定一个接口,其实就是我们说的回调函数。比如View的onClickListener。而在Dart中,我们可以直接指定一个回调方法给调用的方法,由调用的方法在合适的时机执行这个回调。
void setListener(Function listener){
listener("Success");
}
//或者
void setListener(void listener(String result)){
listener("Success");
}
//两种方式,第一种调用者根本不确定 回调函数的返回值、参数是些什么
//第二种则需要写这么一大段 太麻烦了。
//第三种:类型定义 将返回值为void,参数为一个String的方法定义为一个类型。
typedef void Listener(String result);
void setListener(Listener listener){
listener("Success");
}
在 Dart 中方法可以有两种类型的参数:必需的和可选的。 必需的参数需要在参数列表前面, 后面再定义可选参数。
二、可选命名参数
什么叫可选命名函数,其实说白了就是把方法的参数放到 {} 中就变成了可选命名参数。
import 'dart:core';
void main() {
int add({int i, int j}) {
if (i == null || j == null) {
return 0;
}
return i + j;
}
//全参调用
print("--1--${add(i: 10, j: 20)}"); //输出:30
//不传参数(调用也是正确的)
print("--2--${add()}"); //输出:0
//选择传递参数
print("--3--${add(j: 20)}"); //输出:0
//与位置无关
print("--4--${add(j: 20, i: 10)}"); //输出:30
}
三、可选位置参数
什么叫可选命名函数,其实说白了就是把方法的参数放到 [] 中就变成了可选命名参数。
void main() {
int add([int i, int j]) {
if (i == null || j == null) {
return 0;
}
return i + j;
}
//全参调用
print("--1--${add(10, 20)}"); //输出:30
//不传参数(调用也是正确的)
print("--2--${add()}"); //输出:0
//选择传递参数
print("--3--${add(10)}"); //输出:0
}
四、默认参数
什么叫做默认参数,其实就是在定义方法的时候,可选参数可以使用 = 来定义可选参数的默认值。
import 'dart:core';
void main() {
int sum([int age1 = 1, int age2 = 2]) {
if (age1 == null || age2 == null) {
return 0;
}
return age1 + age2;
}
//全参调用
print("--1--${sum(10, 20)}"); //输出:30
//不传参数(调用也是正确的)
print("--2--${sum()}"); //输出:3
//选择传递参数
print("--3--${sum(20)}"); //输出:22
}
import 'dart:core';
void main(){
int sum({int age1 = 10, int age2 = 12}) {
if (age1 == null || age2 == null) {
return 0;
}
return age1 + age2;
}
print("${sum(age1: 10, age2: 23)}"); //33
print("${sum()}"); //22
print("${sum(age1: 12)}"); //24
}
五、匿名函数
首先什么叫匿名函数,简单来说:
大多数方法都是有名字的,比如 main() 或 printElement()。你可以创建一个没有名字的方法,称之为 匿名函数,或 Lambda 表达式 或 Closure闭包。你可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。
([Type] param1, …) {
codeBlock;
};
匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。 后面大括号中的内容则为函数体:下面代码定义了只有一个参数 item 且没有参数类型的匿名方法。List 中的每个元素都会调用这个函数,打印元素位置和值的字符串:
import 'dart:core';
void main() {
var list = ['黄药师', '杨过', '老顽童'];
list.forEach((item) { //匿名函数
print('${list.indexOf(item)}: $item'); //输出:0: 黄药师 1: 杨过 2: 老顽童
});
// 如果函数体内只有一行语句,你可以使用箭头语法:
// 输出:0: 黄药师 1: 杨过 2: 老顽童
list.forEach((item) => print('${list.indexOf(item)}: $item'));
}
至此在开发 Flutter 过程中常用的方法知识点我们讲完了啊,其实方法内容不止这些,接下来的小伙伴可以自行挖掘:比如方法作用域等。
接下来我们进入类讲解
六、类
Dart 是一个面向对象编程语言。 每个对象都是一个类的实例,所有的类都继承于 Object。同时也支持面向对象的特性,比如:类、接口、抽象等。
使用 class 关键字声明一个 dart类,后面跟类名,并且由一对花括号包围的类体,所有类都有同一个基类 Object,dart的继承机制使用了Mixin;
//伪代码
class class_name {
<fields> //字段,类中声明任何变量、常量;
<getters/setters> // 如果对象为final,或const,只有一个getter方法 这个等会我们会有实例产生
<constructors> // 构造函数,为类的对象分配内存
<functions> //函数,也叫方法,对象的操作;
}
上面我们提到方法,其实每个实例变量都会自动生成一个 getter 方法(隐含的)。 非 final 实例变量还会自动生成一个 setter 方法。
class User {
var name;
var age;
User(this.name, this.age);
}
void main(){
var user =User("黄药师",50);
var _name = user.name;
var _age = user.age;
print("-----$_name$_age"); //输出:黄药师 50
}
七、类--构造函数
Dart构造函数有种实现方式:
- 默认构造方法
- 命名构造方法 Class.name(var param)
- 调用父类构造方法
- 不可变对象,定义编译时常量对象,构造函数前加const
- 工厂构造函数:factory
默认构造函数往往也是我们最常见的也是最简单的如下
class User {
var name;
var age;
User(this.name, this.age); //默认构造函数
}
命名构造函数
Dart 并不支持构造函数的重载,而采用了命名构造函数为一个类实现多个构造函数:
class User {
var name;
var age;
User(this.name, this.age); //默认构造函数
//User(this.name); ///错误,因为不准许重载
User.age(this.age) {
name = "欧阳锋";
}
}
void main() {
var user = User.age(50);
print("----${user.name}${user.age}"); //输出:欧阳锋 50
}
import 'dart:core';
class Person {
final String name;
Person.jack() : name = "lucky";
Person.rose() : name = "rose";
}
import 'dart:core';
main(List<String> args) {
var jack = Person.jack(); //创建一个变量名为jack的Person类型的对象
var rose = Person.rose(); //创建一个变量名为rose的Person类型的对象
print("${jack.name} ${rose.name}"); //输出 lucky rose
}
重定向构造函数
有时候一个构造函数会调动类中的其他构造函数(在Java中就是 this(...))。 一个重定向构造函数是没有代码的,在构造函数声明后,使用冒号调用其他构造函数。
class User {
var name;
var age;
User(this.name, this.age); //默认构造函数
User.user(name, age) : this(name, age);
}
void main() {
var user = User.user("黄药师", 50);
print("----${user.name}${user.age}"); //输出:黄药师50
}
常量构造函数
如果你的类提供一个状态不变的对象,你可以把这些对象定义为编译时常量。要实现这个功能,需要定义一个 const 构造函数, 并且声明所有类的变量为 final。
class User {
final String name;
final int age;
const User(this.name, this.age); //默认构造函数
}
void main() {
var user = User("黄药师", 50);
print("----${user.name}${user.age}"); //输出:黄药师50
}
工厂构造函数
当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,一个工厂构造函数可能从缓存中获取一个实例并返回,或者返回一个子类型的实例。(工厂构造函数无法访问 this)
//工厂构造方法
//如果一个构造方法并不总是返回一个新的对象,这个时候可以使用factory来定义这个构造方法。
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
//调用
void main() {
//工厂
var logger = new Logger('UI');
logger.log('Button clicked');
}
要注意的是工厂构造方法时没法访问this关键字的,所以上面就有了在类的内部这么调用构造方法的代码:final logger = new Logger._internal(name); 在上面工厂构造方法中,如果缓存中存在传入的name的key值,则取出缓存中的对应value返回。 如果缓存中没找到,就会通过命名构造方法来新建一个对象,缓存起来后返回。
补充说明:借助工厂构造函数能够实现单例:(实用场景:flutter 网络请求Dio应用)
import 'dart:core';
import 'package:dio/dio.dart';
//使用工厂构造实现单例
//懒汉式
class DioUtil {
//单例公开访问点
factory DioUtil() => _getInstance();
//私有成员,没有初始化
static DioUtil _instance;
Dio _dio;
//私有构造方法
DioUtil._() {
//具体初始化代码
_dio = Dio();
}
//静态、同步、私有访问点
static DioUtil _getInstance() {
if (_instance == null) {
_instance = DioUtil._();
}
return _instance;
}
}
//饿汉式
//在加载时就创建好实例,不是在运行时创建好实例
class SomeSharedInstance {
//单例公开访问点
factory SomeSharedInstance() => _sharedInstance();
//静态私有成员
static SomeSharedInstance _instance = SomeSharedInstance._();
//私有构造函数
SomeSharedInstance._() {
//具体初始化代码
}
static SomeSharedInstance _sharedInstance() {
return _instance;
}
}
var instance1 = DioUtil();
var instance2 = DioUtil();
var result = instance1 == instance2;
print("result is $result"); //true
var someSharedInstance1 = SomeSharedInstance();
var someSharedInstance2 = SomeSharedInstance();
print("result2 is ${someSharedInstance1 == someSharedInstance2}"); //true
在使用单例的时候,直接使用构造方法即可,通过证明可以看到利用工厂构造确实可以避免创建多个对象。
八、方法
上面是方法:方法是对象提供行为的函数。
Getters 和 Setters
Dart 中每个实例变量都隐含的具有一个 getter, 如果变量不是 final 的则还有一个 setter。可以通过实现 getter 和 setter 来创建新的属性, 使用 get 和 set 关键字定义 getter 和 setter:
class Rect {
num left;
num top;
num width;
num height;
Rect(this.left, this.top, this.width, this.height);
//使用 get定义了一个 right 属性
num get right => left + width;
set right(num value) => left = value - width;
}
void main() {
var rect = Rect(0, 0, 10, 10);
print(rect.right); //10
rect.right = 15;
print(rect.left); //5
}
使用 Getter 和 Setter 的好处是,你可以先使用你的实例变量,过一段时间过再将它们包裹成方法且不需要改动任何代码,即先定义后更改且不影响原有逻辑。
九、抽象类、抽象方法、还有继承
实例方法、Getter 方法以及 Setter 方法都可以是抽象的,定义一个接口方法而不去做具体的实现,让实现它的类去实现该方法,抽象方法只能存在于抽象类中,抽象类的定义跟Java的抽象类类似,就不单独介绍了。
abstract class User {
void say(); //定义一个抽象方法
}
class Person extends User{
@override
void say() {
// 提供一个实现,所以在这里该方法不再是抽象的……
}
}
说明:抽象类不能被实例化,除非定义工厂方法并返回子类。
abstract class User {
String name;
//默认构造方法
User(this.name);
//工厂方法返回Child实例
factory User.test(String name){
return new Child(name);
}
void printName();
}
// extends 继承抽象类
class Child extends User{
Child(String name) : super(name);
@override
void printName() {
print(name);
}
}
void main() {
var p = User.test("黄药师");
print(p.runtimeType); //输出实际类型 Child
p.printName();//输出实际类型 黄药师
}
十、接口
Dart 没有像 Java 用单独的关键字 interface 来定义接口,普通用 class 声明的类就可以是接口,可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:
// Person 类的隐式接口中包含 greet() 方法。
class User {
// _name 变量同样包含在接口中,但它只是库内可见的。
final _name;
// 构造函数不在接口中。
User(this._name);
// greet() 方法在接口中。
String greet(String who) => '你好,$who。我是$_name。';
}
// Person 接口的一个实现。
class Impostor implements User {
get _name => '';
String greet(String who) => '你好$who。你知道我是谁吗?';
}
String greetBob(User person) => person.greet('黄药师');
void main() {
print(greetBob(User('欧阳锋'))); //输出:你好,黄药师。我是欧阳锋。
print(greetBob(Impostor())); //输出:你好黄药师。你知道我是谁吗?
}
这时疑问来了,接口跟继承有什么区别,不就是多继承吗? 接口的实现则意味着,子类获取到的仅仅是接口的成员变量符号和方法符号,需要重新实现成员变量,以及方法的声明和初始化,否则编译器会报错。而继承可以选择不重新实现,这是最大的区别。
可能小伙伴觉得有点绕:那我们总结一下。其实就两句话:
- 单继承,多实现。
- 继承可以有选择的重写父类方法并且可以使用super,实现强制重新定义接口所有成员。
十一、可调用的类:
如果 Dart 类实现了 call() 函数,则可以当做方法来调用。
class User {
call(String name, int age) => '$name $age!';
}
main() {
var c = new User();
var out = c("黄药师",50);
print(out); //输出:黄药师 50!
}
十二、混合Mixins
在面向对象的世界中,我们最熟悉的莫过于 class、 abstract class和interface。Dart作为一门现代面向对象编程语音,在原有的特性基础上,新增了一些新的特性如:Mixins
什么是Mixins:
简单的理解,就是用来复用多个类之间的代码,减少耦合。我们直接来看一个例子。
我们在没有使用Mixins的从前:
假设,我们现在正在开发一个动物大全App,我们需要创建一个Duck类。作为一个有丰富面向对象编程经验的开发者,你自然的将所有和Duck有相似特征的抽取成一个abstract class。
/// Bird
abstract class Bird {
void shout() {
println('shouting');
}
}
/// WaterborneBird
abstract class WaterborneBird extends Bird {
void swim() {
println('swimming');
}
}
/// Duck
class Duck extends WaterborneBird {
void doDuckThings() {
shout();
swim();
println('quack quack quack!')
}
}
很好,我们清楚的将鸭子归入水中生活的鸟类,加入其它的鸟类也变得非常容易。但是,现在我们需要加入金鱼了,于是我们和上面一样编写代码。
/// Fish
abstract class Fish {
void swim() {
println("swimming")
}
}
/// GoldFish
class GoldFish extends Fish {
void doGoldFishThings() {
swim();
pringln('zzz...');
}
}
这是我们发现金鱼和鸭子一样拥有 swim 的特性,在这个例子中是非常简单的,但是如果我们有复杂的行为需要赋予给一个新的类,我们就要大量编写重复的代码了。
使用Mixins
我们声明一个Swimming的mixin:
mixin Swimming {
void swim() {
println('swimming')
}
}
我们可以使用 with 关键字将 mixin 加入到 class中,其实看到这里你可能已经回想到我们其实可能已经用过这个with关键字了。接下来,我们就可以对上面的代码进行改造了:
/// Bird
abstract class Bird {
void shout() {
println('游泳');
}
}
/// Duck
class Duck extends Bird with Swimming {
void doDuckThings() {
shout();
swim();
println('跳跃!')
}
}
/// Fish
abstract class Fish {
}
/// GoldFish
class GoldFish extends Fish with Swimming {
void doGoldFishThings() {
swim();
pringln('zzz...');
}
}
mixins 弥补了接口和继承的不足,继承只能单继承,而接口无法复用实现,mixins 却可以多混入并且能利用到混入类。
我们在来看一个例子做比较:
abstract class Swimming{
void swimming(){
print("游泳");
}
}
abstract class Jump{
void jump(){
print("跳跃");
}
}
//只能单继承,如果需要Jump,只能以implements的形式
class HuangYaoShi extends Swimming implements Jump{
//实现接口
void jump(){
print("跳跃");
}
}
//但是实际上,我们经常不需要重新实现Jump方法,复用Jump所实现的jump方法就可以了
//这时使用混合能够更加方便
class HuangYaoShi with Swimming, Jump {}
关于 Mixins,还有很多需要注意的事情,我们虽然可以使用Mixins对代码进行一些简化,但是要建立在对需求和类之间的关系准确理解的基础上。建议多去看看Flutter中使用Mixins实现的一些源码,从里面吸取一些正确的经验。
参考:上面动物例子转载了该博主的文章:http://深入理解Dart之Mixins