Dart_04_类

127 阅读8分钟

在Dart中,只要class修饰的,都为类,一个普通的类系统都会给一个默认的构造函数

class Person{
  String name;
  int age;

}

我们声明了一个类person,但是并没有写他的构造方法,但是我们可以通过Person()来实例化这个对象,这个就是系统默认给的方法

除了系统给的默认构造方法,我们可以自定义构造方法, 一个类只能有一个构造方法,当我们自定义构造方法时候,系统默认的构造方法就不能使用


class Person{
  String name;
  int age;

  // Person(String name, int age){
  //   this.name = name;
  //   this.age = age;
  // }
  Person(this.name, this.age);//上面等同于这里写法
}

这里我们重写了构造方法

Person(this.name, this.age);
所以调用的时候就需要调用

var p = Person("lilei",18);
方法来实例化该对象,不能再调用系统默认的Person()构造方法来实现,会报错
但是日常开发中,我们可能会出现一个类,后期添加了一个新的属性方法,然后我们希望构造的时候传入新的属性的值

class Person{
  String name;
  int age;
  double height;//假如我后期新加了一个height;
  Person2(this.name, this.age);//这里之前已经申明过构造函数了,这时候不能重新写新的构造方法
}

这里我们添加了height,我希望新的初始化对象里传入height的值,这时候我们可以在以前的构造函数里添加一个可选参数height来解决这个需求,也可以通过 .+构造方法的形式,来创建新的构造函数

class Person2{
  String name;
  int age;
  double height;//假如我后期新加了一个height;
  Person2(this.name, this.age);//这里之前已经申明过构造函数了,这时候不能重新写新的构造方法
  // Person2(this.name, this.age , this height);//因为上面已经有构造函数了,所以这里不能再写,因为Dart不支持重载
  
  // Person2(this.name, this.age,{this.height});//可以通过添加可选参数形式来不改变旧的代码情况下支持新的参数

  //假如在不改变上面构造函数的情况下声明一个必须包含height新的构造函数,mustNeedNameAgeHeight是函数名,自定义
  Person2.mustNeedNameAgeHeight(this.name,this.age,this.height);
  
  //声明一个map,指定类型key为string,value为dynamic
  Person2.getInfoFromMap(Map <String,dynamic>map){
    this.name = map["name"];
    this.age = map["age"];
    this.height = map["height"];
  }
}

调用的时候


//类的构造函数
  var p2 = Person2.mustNeedNameAgeHeight("lilei1", 18, 68.8)
                  ..log();

  var p3 = Person2.getInfoFromMap(
                                {
                                 "name":"lilei2",
                                 "age":19,
                                 "height":68.5
                                 }
                                 );

..log()中..表示一种链式调用,可以通过..来直接调用类的多个方法

类的初始化列表

声明一个类,可以在初始化时候直接给一个默认值

class Person{
  String name;
  int age = 10;//初始化值

  Person(String name){
    this.name = name;
    this.age = 10;//这样和上面都表示默认值为10
  }

}

但是当我们的参数是经过final修饰的话,就不能2次修改,通过上面方式给一个初始值的话,就不能从外面赋值,当我们希望根据构造时候传入的参数来判断,有值的话赋值,没有的话给一个默认值,就需要用到下面的构造方法

class Person1{
  //这样写也表示默认值,但是这种方式就固定了name age的值,即使外面传入新的值依然赋值不上会报错
  // final String name = "lilei";
  // final int age = 10;

//final修饰是不可变的,所以这里必须声明时候赋值
  final String name ;
  final int age ;

//这样写法我就可以在当用户不传入age时候,给个默认值,传入就取传入的值
  //大括号里面是在person已经 初始化完成以后调用的,但是final修饰的要求初始化时候就需要赋值,所在在小括号后面直接冒号修饰,后面的内容就表示初始化时候做的事情,age就在初始化时候默认给值18,多个初始化默认值用逗号隔开
  Person1 (this.name,{int age}): this.age = age??18;


  // Person1 (this.name,{this.age = 10});//这种方式跟上面方式一样,但是区别是这里给的默认值必须是一个确定的值,而上面可以通过表达式获取值,例如:age从int.parse(name)获取,上面可以这么些,这里就不行
}

在构造方法后面直接用:跟上代码。这里的代码就是在初始化完成时候调用,这里判断假如可选参数age没有传递的话,就会给一个默认值,flutter中系统大量使用了这种写法

image

重定向构造函数函数

通过上面我们知道给函数创建新的构造方法,以及给默认值的方式,知道了在构造函数后面调用:就是在初始化时候调用的方法

那么现在我们可以将两种方式结合起来,来给对象一个默认初始值


class Person{
 final String name;
 final int age;

 //当我调用这个构造函数时候,我想给age赋一个默认值,上面的方式以外,还可以通过重定向构造函数来实现,通过在初始化时候调用setNameAge构造函数,给age传入默认值0,主力这里修饰name不能直接用this.name,因为当前函数重定向到setNameAge里面了,所以这里构造函数还没有实例化完成,不能使用this.name修饰
 Person(String name):this.setNameAge(name,0);

 Person.setNameAge(this.name,this.age);

我们可以在:后面调用类的另外一个构造方法,来实现我们所要实现的需求,这种方式就叫做

重定向构造函数函数

常量构造函数

在Dart中,我们可以通过const来声明一个函数,这样创造的函数,当传入相同的值时候,系统会帮我们返回一个相同的对象。(mark后期查询资料为什么)

//常量构造函数
class ConstPerson{
 final String name;
 final age = 19;
 //常量构造函数只有一个构造方法,如果有多个参数,其余参数要有默认值
 const ConstPerson(this.name);
}

当我们调用时候

 const p1 = ConstPerson("1");
 const p2 = ConstPerson("1");
 print(identical(p1,p2));

打印结果为ture,可以知道p1 p2为同一个对象,这样当我们可以通过同样的值来获取到已有对象,节省内存,但是这样写的弊端非常明显,构造函数只能有一个参数,当多个参数时候,系统会报错

那么有没有一种更加自由的,可以通过同样的值来获取到相同的对象呢,这时候工厂方法就出现了

工厂构造函数

在Dart中用factory修饰的构造函数都是工厂构造函数,工厂构造函数要求我们必须返回一个当前类的实例对象,否则会报错


class Person {
   String name;
   String color;

   //声明2个map,一个以name为key来存储Person,一个以color为key来存储Person
   static final Map<String, Person> _nameCache = {};
   static final Map<String, Person> _colorCache = {};

  //工厂构造方法要求我们必须手动返回当前生成的对象
   factory Person.withNameOrColor({String name,String color}){
      print("name $name color$color");

      if ((name ==null||name.length ==0) &&(color == null||color.length == 0)) {
        return null;
      }

      if (_nameCache.containsKey(name)&&_colorCache.containsKey(color)) {
        //当缓存里包含同样名字或者颜色的person时候,返回缓存里的
        var namePerson = _nameCache[name];
        var colorPerson = _colorCache[color];
        return namePerson??colorPerson;

      }else{
        //当缓存里都不包含同样名字或者颜色的时候生成一个新的person
        //前面《01_类》篇章我们已经说过当重写构造函数的时候,原来默认的构造函数就会失效,所以FactoryPerson()构造函数无法使用,需要我们自定义一个构造函数
        final person =  Person(name:name,color: color);
        if (name!=null&&name.length>0) {
          _nameCache[name] = person;
        }
        if (color!=null&&color.length>0) {
          _colorCache[color] = person;
        }
        return person;
      }

   }

    FactoryPerson({String name, String color}){
      this.name = name;
      this.color = color;
    }

 }

在该类中,我们通过创建2个静态的Map来存储以name和color作为值的person对象,当我们传入值时候,首先判断2个map里是否包含同样name以及color的person,当有的话,返回该对象,没有的话创建新的person,添加到map里,并且返回回去,因为factory要求我们必须返回当前类的实例,所以我们需要在添加一个新的构造函数FactoryPerson({String name, String color})来实例化

setter和getter实现

在Dart中某个对象直接调用他的某个参数赋值,获取读取他的某个参数,就是调用的系统默认的setter getter方法,当我们希望在setter和getter调用过程中实现其他的事情的话就需要重写类的setter getter方法

 class Person{
   //可以在声明时候前面加下划线 _name,但是这样就不能在外面访问该属性
   String name;

   //setter 
   set setName(String name){
     print("setname");
     this.name = name;
   }

   //getter
  //  String get getName{
  //    print("getName");
  //    return name;
  //  }
    //跟上面一样,但是意义不大,因为这里就跟系统默认的setget方法一样了,无法扩展,上面方法可以在大括号里做set get触发的一些事项
    //setter同样可以通过箭头写法
   String get getName =>this.name;
 }

当我们声明setter getter方法时候,需要在前面加上set和get修饰,这样我们外部直接可以通过set get方法赋值和取值

   p.setName = "xiaoming";
   print(p.getName);

打印结果

setname
xiaoming

类的继承

Dart中在类的声明时候通过 extends+类名 的方式,可以继承于某个类

class Person{
 String name;
 Person(this.name);
}
//student继承于person
class Student extends Person{
 int age;
 //因为继承于person,所以在student的构造函数里需要先调用person的构造函数
 Student(String name,this.age):super(name);

 @override
 String toString() {
   // TODO: implement toString
   return "name :$name\nage :$age";
 }
}

当继承于某个类的话,就同时拥有父类的属性,但是在内部实现父类属性的调用的话,需要通过super来实现