Flutter入门系列-40分钟快速入门Dart基础(中)

一、一等方法对象

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 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值