(四)flutter入门之dart中的类操作详解

96
逐梦々少年
1.6 2019.03.27 18:02* 字数 2432

上篇博客我们学习了dart中的集合、动态变量和函数相关的内容,本篇博客将对dart的类操作,异步编程和事件驱动进行学习

dart作为一个纯粹的面向对象的语言,类的概念自然是极为突出,我们所熟悉的java语言是个经典的面向对象编程的语言,dart作为一个优秀的,集百家之长的语言,不仅有java中的类的所有优秀特性,还在此基础上做了加强

构造函数

在java中我们常见的构造分为无参构造和有参构造,并且多个参数的构造可以重载多个提供不同的需求,在参数比较多的场景下,很麻烦,并且增加了代码量,而在dart中由于没有重载的概念(函数也是个类型),所以dart中默认的构造只允许有一个,所以我们按照java的有参构造,dart中我们会按照如下写法:

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母

  Fraction(int numerator,int denominator){
    this.numerator =numerator;
    this.denominator =denominator;
  }
  
}

但是这么写,其实还是复杂了,形参和实际的参数一样的情况下,dart可以进行简化开发,如下:

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母

  Fraction(int numerator,int denominator);//形参和实参一样的,可直接不写方法体和赋值操作
}

这样的构造的确简化了开发,但是我们开发的时候可能存在既有无参构造,又需要有参构造的情况,dart不支持方法重载我们又该如何呢?当然,我们有两种方案实现,还记得我们上篇博客学习了dart的可选命名参数和可选位置参数吗?构造函数完全可以利用该特性完成构造

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母

  Fraction({int numerator,int denominator});//将参数使用{}包裹,这样构造的时候可以选择性的传递,也可以完全不传
}

除了可选参数列表以外,dart还提供了一个新的概念--命名构造函数,即可以指定一个指定名称的函数作为构造,使用如下:

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母

  Fraction.numerator(int numerator);//指定numerator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分子的
  Fraction.denominator(int denominator);//指定denominator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分母的
  _Fraction(){}//还记得刚学dart的时候那几点注意吗?dart中没有公共私有的关键字,如果需要私有化,前面加个_修饰自动编译为私有属性/私有方法,也就是说当前的分数类的默认构造变成私有化,无法直接通过该方法创建
  //可以根据_私有的特性很方便的实现单例模式
  Fraction _newinstance(){
    if(null == _instance){
      _instance = _Fraction();
    }
    return _instance;
  }
}
初始化列表

在dart构造的时候,我们还可以使用dart提供的快捷方式可以在构建的时候初始化变量,这点和c++一样,为:

xxx.函数名():Filed1=xxx,Filed2=xxx.....;

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母
  
  Fraction.numerator(int numerator);//指定numerator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分子的
  Fraction.denominator(int denominator);//指定denominator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分母的
  
  Fraction.init():numerator=1,denominator=0;//在构造函数后直接:拼接属性赋值操作,可以在构造的时候初始化一些参数值,对于一些final修饰的属性初始化以后固定值不会在变,非常合适
}
重定向构造函数

dart中不仅提供了初始化列表的操作,还提供了构造的重定向操作,语法和初始化列表一样,只是将其他的构造当成属性回调

///分数类
class Fraction{
  int numerator;//分子
  int denominator;//分母
  
  Fraction.numerator(int numerator);//指定numerator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分子的
  Fraction.denominator(int denominator);//指定denominator名称的函数为构造,可以很清晰的看出来构造是专门用来设置分母的
  
  Fraction.init():numerator=1,denominator=0;

  Fraction.forword():this.numerator(1);//这里回调当前的分子构造函数,即我们可以公开一个构造方法,内部进行逻辑操作,不同的业务进入不同的内部构造初始化操作
}
常量构造函数

如果你的类的属性都是固定不变的,dart提供了一个常量构造函数,使用const修饰构造,并且所有的属性需要使用final修饰,这样做的好处是,当你调用多个一样参数的常量构造的时候,你获取的是同一个对象,从而节省内存开销和运行效率,需要注意的是,这里的使用常量构造,不允许出现其他的构造,并且只能是公共的默认有参构造,所有的属性都必须是final修饰,并且传递的参数中所有的参数都需要传递

class Fraction{
  final int numerator;//分子
  final int denominator;//分母
  
 const Fraction(this.numerator,this.denominator);
}
工厂构造函数

在dart中除了以上几种构造以外,还提供了一种工厂构造函数,使用工厂构造函数的好处是在这里不必创建新的实例,可以选择从缓存中获取一个实例并且返回,甚至可以返回一个指定的子类实例,这里可以理解为java设计模式中的工厂设计模式的变种升级版本,但是需要注意的是使用工厂构造函数,必须使用factory关键字修饰构造方法,并且在工厂构造中不允许使用this关键字

class Fraction{
  static final Map<String,Fraction> cacheMap = new Map<String,Fraction>();
  factory Fraction(String name){
    if(cacheMap.containsKey(name)){
      return cacheMap[name];
    }else{
      cacheMap[name] = Fraction._newInstance();
      return cacheMap[name];
    }
  }
  Fraction._newInstance();
}
Get/Set

在java中,我们一般会对实体进行封装,提供set和get方法,在dart中同样支持get/set,但是需要注意的是如果我们的属性是公开的,默认调用xxx.Filed = xxx或者var xxx = xxx.Filed这样的获取值和设置值的操作的时候,调用的就是dart默认的get和set方法,也就是说在dart中,默认是有get和set的,我们可以不用重写方法,但是如果我们将所有的属性置为_修饰的私有化,这个时候,因为类外无法访问到这个属性,默认的get和set就失效了,我们就需要手动设置方法去提供对应的set/get方法(由于在flutter中,禁止使用反射,所以目前为止不会出现和java一样通用方法名getxxx,setXXX的情况,dart的get/set方法名可以任意取,但是博主建议还是按照规范的习惯来开发),需要注意的一点是在set和get方法中不要调用自身方法,dart中有层级树的概念,递归调用会导致Stack Overflow异常

class Fraction{
  int _numerator;
  int _denominator;

  void setNumerator(int numerator){
     this._numerator = numerator;
  }
}

//在别的dart文件中引用,当前文件引用肯定是可以找到私有属性的,下面是其他dart文件的代码
import 'class_test.dart';
void main(){
  Fraction fraction = new Fraction();
  //fraction.numerator = 1;//找不到默认的set方法了,这里会报错
  fraction.setNumerator(1);//只能使用自己设置的set
}
可复写操作符运算

在dart中,类不仅有基本的概念和简化的写法,并且提供了一些增强的属性,比如在dart中,我们可以复写基本的运算符运算规则(不能改写运算符自身的优先级),使得我们可以做到任意类之间可以进行基本运算符操作,比如两个对象之间进行加减乘除运算,再比如我们常用的list内部就重写了[]运算符,目前为止,dart支持可以重写的运算符如下:

| < | + | | | [] |
| ---- | ---- | ---- | ----- |
| > | / | ^ | []= |
| <= | ~/ | & | ~ |
| >= | * | << | == |
| | % | >> | |

接下来我们来通过重写基本的运算符,根据我们的案例分数类,实现一个基本的分子分母相加相乘并且尽量简化输出结果的案例代码:

分数类fraction_class.dart的代码如下:

/**
 * 分数类
 */
class Fraction{
  num numerator;//分子
  num denominator;

  static const ISNULL_EXCEPTION = "异常:分子/分母不可以为null";
  static const ISNOTZERO_EXCEPTION = "异常:分母不可以为0";
  static const NULL_OPERATION_EXCEPTION = "异常:不可以和null进行运算";

  Fraction(num numerator,num denominator){
    if(null == numerator || null == denominator){
      throw ISNULL_EXCEPTION;
    }
    if(denominator == 0){
      throw ISNOTZERO_EXCEPTION;
    }
    this.numerator = numerator;
    this.denominator = denominator;
  }


  @override
  String toString() {
    return "分子为:${numerator},分母为:${denominator}";
  }

  /**
   * 分数相加方法:首先先找分母之间的最小公倍数,计算出分子转换成最小公倍数以后相加的结果,最后将最后的结果进行约分计算
   */
  Object operator +(Fraction fraction){
    if(null == fraction){
      throw NULL_OPERATION_EXCEPTION;
    }
    //加法运算需要找分母的最小公倍数转换后进行计算
    FracionCompute fractionCompute = new FracionCompute();
    int minCommonMultiple = fractionCompute.minCommonMultiple(this.denominator,fraction.denominator);
    //计算一下分子分别需要乘以多少倍
    int newNumerator =  ((minCommonMultiple / (denominator.toInt())).toInt()) * (numerator.toInt());
    //传递来进行计算的分数分子分母转换为最小公倍数
    int newNumerator2 = ((minCommonMultiple / (fraction.denominator.toInt())).toInt()) * (fraction.numerator.toInt());
    //分子相加
    int plusNumerator = (newNumerator + newNumerator2);
    //约分操作
    return fractionCompute.reductionOfFraction(plusNumerator, minCommonMultiple);
  }

  /**
   * 分数相乘方法:
   * 代码计算的话就不需要先约分,计算完毕再去约分(只有人计算的时候会先把分子分母约分再去算乘法)
   */
  Object operator *(Fraction fraction){
    if(null == fraction){
      throw NULL_OPERATION_EXCEPTION;
    }
    //获取第一个分子和第二个分母的最大公约数
    FracionCompute fractionCompute = new FracionCompute();
    //转换后的结果再去约分一次,防止相乘的两个分数为5/5  * 6/6这种无聊的分数,结果为整数应该返回整数
    return fractionCompute.reductionOfFraction((numerator * fraction.numerator).toInt(), (denominator * fraction.denominator).toInt());
  }
}

用来计算函数的FracionCompute类--fracion_compute.dart文件代码如下:

import 'fraction_class.dart';
/**
 * 分数计算类
 */
class FracionCompute{

  /**
   * 求最大公约数 先比较两个数是不是一样的,一样的直接返回,不一样的话,比较大的分母是否为另一个的倍数,如果是那么返回最小的那个值,
   * 如果都不是,就使用穷举找到两个都能整除的最大公约数,需要注意1和这这两个值中相对较小的值本身
   * 如果比较小的值直接就能满足,那么就是比较小的那个值为最大公约数,如果一直穷举到2还不满足,那么他们就是没有公约数,返回1
   */
  int maxCommonMeasure(num number,num number2){
    if(number == number2){
      return number;
    }
    int max = number.toInt();
    if(number.toInt() > number2.toInt()){
      max = number2.toInt();
    }
    for(int i = max;i > 1;i --){
      if(number.toInt() % i == 0 && number2.toInt() % i == 0){
        return i;
      }
    }
    return 1;
  }


  /**
   *求最小公倍数 先比较两个数是不是一样的,一样的直接返回,不一样的话,判断是否存在0,
   *比较大的分母是否为另一个的倍数,如果是那么返回最大那个值,如果都不是,就使用穷举找到两个都能整除的最小公倍数,
   *这个值从比较大的值开始,到两个值的乘积结束,
   *每次递加比较大的那个值,可以最快求出来两者公倍数
   */
  int minCommonMultiple(num number,num number2){
    if(number.toInt() == number2.toInt()){
      return number.toInt();
    }
    int temp = number.toInt();
    if(number.toInt() < number2.toInt()){
      temp = number2.toInt();
    }
    for(int i = temp;i <= number.toInt() * number2.toInt();i = i + temp){
      if(i % number.toInt() == 0 && i % number2.toInt() == 0){
        return i;
      }
    }
  }

  /**
   * 约分方法,根据规则计算,如果分子/分母刚好整除那么就是返回int类型的结果,如果不是整除那么就返回分数类型的最简单约分模式
   */
  Object reductionOfFraction(int numerator,int denominator){
    if(numerator == denominator){
      return 1;
    }
    //如果分子大于分母
    if(numerator > denominator){
      if(numerator % denominator == 0){
        return (numerator / denominator).toInt();
      }
    }else{
      //分子小于分母,约分计算
      if(denominator % numerator == 0){
        return new Fraction(1,(denominator / numerator).toInt());
      }
    }
    //还要考虑分子和分母有公约数的情况,需要计算一次最大公约数,然后除以最大公约数
    int max = maxCommonMeasure(numerator,denominator);
    return new Fraction((numerator / max).toInt(), (denominator / max).toInt());
  }
}

接下来就是测试的main方法了:

void main(){
  try{
    Fraction fraction = new Fraction(5,15);
    Fraction fraction2 = new Fraction(2,6);
    var object = fraction + fraction2;
    print(object); //输出-->分子为:2,分母为:3
    //分数相乘
    var object2 = fraction * fraction2;
    print(object2);//输出-->分子为:1,分母为:9
    //创建个异常的分数,分母为0
    Fraction fraction3 = new Fraction(0,0);
  }catch(e){
    print(e);//输出-->异常:分母不可以为0
  }
}
抽象类和接口

做过java开发的都知道,java基于c++的面向对象进行了细分,有了抽象类和接口的概念,dart语言同样也支持这些特性,当然需要注意的一点是dart中不存在接口的概念,在dart中只有类,没有接口,但是dart给了实现类的方法,用来替代接口

抽象类

在dart的类定义中可以使用abstract来修饰class,这样的类即为抽象类,同样,dart的抽象类可以抽象方法和普通实现方法共存,但是我们需要注意的一点是,dart中的抽象方法没有abstract关键字修饰,仅仅是无方法体的方法

abstract class Parent {
  String name;
  void printName(); //抽象方法,不需要在方法前声明 abstract
}

同样的dart中的抽象类和java一样,不可以直接实例化,但是在dart中你可以给抽象类提供一个工厂方法,按照指定的业务逻辑返回对应的子类的实例

abstract class Parent {
  String name;
  //默认构造方法
  Parent(this.name);
  //工厂方法返回Child实例
  factory Parent.test(String name){
    return new Child(name);
  }
  void printName();
}
// extends 继承抽象类
class Child extends Parent{
  Child(String name) : super(name);

  @override
  void printName() {
    print(name);
  }
}

void main() {
  var p = Parent.test("Lance");
  print(p.runtimeType); //输出实际类型 Child,runtimeType属性可以输出对应的class类型
  p.printName();        
}
接口与实现

熟悉java开发的都知道,在java中有接口这个概念,并且和申明class的方式一样,只是区别在于修饰的关键字为interface,并且在接口中只能有抽象的方法,不允许出现任何方法体的普通实现方法,而在dart中并没有interface关键字,Dart中每个类都隐式的定义了一个包含所有实例成员的接口,并且这个类实现了这个接口,如果我们需要A类支持B类的方法的话,我们可以选择继承,但是我们可能不想继承B类,或者已经继承了其他类,我们可以选择实现B类隐式接口

class Listener{
  void onComplete(){}
  void onFailure(){}
}

class MyListsner implements Listener{
  MyListsner(){

  }
  @override
  void onComplete() {
      //代码体实现
  }

  @override
  void onFailure() {
      //代码体实现
  }
}

与继承的区别在于:

1、单继承,多实现(这点和java一样,和c++的多继承不一样)。

2、继承可以有选择的重写父类方法并且可以使用super,实现强制重新定义接口所有成员

默认可调用的类(彩蛋特性)

在dart新版本中,加入了一个很有趣的彩蛋式的特性,即如果某个类中实现了call()函数,我们可以直接使用,不需要调用这个函数名,默认就是调用call函数,使用如下:

class Listener{
  void call(){
    print('默认调用了call函数!!!');
  }
}

void main(){
  Listener listener = new Listener();
  listener();//这里不仅不会报错,反而输出了call的结果-->默认调用了call函数!!!
}
混入Mixins

dart中新支持了一个类的特性,即为mixins(混入),按照dart官方的说明,这个混入的特性,可以同时混入多个类的属性和方法,(博主这里测试了很多次,特性和继承没什么区别,也可以使用super.xxx方法调用父类的方法,需要注意的一点是,继承和混入是可以兼容同时存在的,但是实现和混入却不可以同时出现,可以看出来混入的特性是用来弥补接口和继承的不足,继承只能单继承,而接口无法复用实现,mixins却可以多混入并且能利用到混入类的具体实现),使用混入只需要在类上使用with关键字:

abstract class A{
  void open(){
    print('默认调用了A的open!!!');
  }
}

abstract class B{
  void close(){
    print("调用了B的close");
  }
}

//接口类
class Listener{
  void onListener(){}
}

//这里implements和with同时出现的时候,编译器就会报错,明确告知使用with以后不能存在implements关键字
class C extends A implements Listener with B{
     
}

//使用混入就可以随便使用继承,并且默认实现其他类,不需要重写该方法
class D extends A with B,Listener{}

class E extends A implements B{
  @override
  void close() {
    //这里我们可能也只是想默认实现B的,并不需要复写,但是只是继承和实现的话,我们只能选择默认再去实现一次
    //print("调用了B的close");
  }
}