Dart入门宝典第三章——面向对象编程之类的介绍

399 阅读1分钟

类与对象

  • 使用关键字class声明一个类
class Person{

}
  • 使用关键字new创建一个对象,new可省略
void main() {
     var p1 = new Person();
     var p2 = Person();
}
  • 所有对象都继承于Object类

属性与方法

  • 属性默认会生成getter和setter方法
void main() {
     var p = new Person();
     p.name = "黄家驹";
     p.age = 31;
     print("name = ${p.name}, age = ${p.age}");
}

class Person{
   String name;
   int age;
}

打印结果:

name = 黄家驹, age = 31

可以看到属性name和age可以被重新赋值,且可以被获取,即默认生成了setter和getter方法

  • 使用final声明的属性只有getter方法
void main() {
     var p = new Person();
    // p.gender = "Female"; 报错提示:'gender' can't be used as a setter because it's final
     print("gender = ${p.gender}");
}

class Person{
   String name;
   int age;
   final String gender = "Male";
}

gender属性使用了final关键字声明,则该属性只有getter方法,不能被重新赋值

  • 属性和方法通过.访问

  • 方法不能被重载

class Person{
   String name;
   int age;
   final String gender = "Male";

   void work(){
     
   }
   
   // void work(int speed){
   //
   // }  此时会报错,提示方法名已经被定义The name 'work' is already defined.
}

在Java中支持方法的重载,上述代码中有两个相同方法名的方法work,但是第二个需要传入speed参数,这个在Java中的合理的,但是Dart不支持方法的重载,即使方法有不同参数,只要方法名定义过,就不能在重载同名的方法了。

类与成员的可见性

  • Dart中的可见性以library(库)为单位
  • 默认情况下,每一个Dart文件就是一个库
  • 使用_表示库的私有性
  • 使用import导入库

将Person类单独写在Person.dart文件中

class Person{
  String name;
  int age;
  final String gender = "Male";

  void work(){

  }
}

在另一个Dart文件中要调用Person类,则需要使用import导入Person.dart

import 'Person.dart';
void main() {
     var p = new Person();
}

当在Person类名前添加_时,该类则不能被其他文件调用

class _Person{
  String name;
  int age;
  final String gender = "Male";

  void work(){

  }
}

当在属性名前添加_时,该属性不能被直接调用

class Person{
  String _name;
  int age;
  final String gender = "Male";

  void work(){

  }
}

此时Person类的name属性不能被直接调用。

计算属性

  • 顾名思义,计算属性的值是通过计算而来,本身不存储值
  • 计算属性赋值,其实是通过计算转换到其他实例变量

计算正方形的面积,在之前我们可以通过定义一个area()方法计算面积,返回边长*边长的值;

void main() {
    var square = Square();
    square.side = 1.5;
    print(square.area());
}

class Square{
     num side;
     num area() => side * side;
}

这里我们可以把面积定义成一个计算属性:

void main() {
  var square = Square();
  square.side = 1.5;
  print(square.area);
}

class Square {
  num side;
  num get area => side * side;
}

此时,area是Square类的一个属性而不是方法了,同理,可以设置计算属性area的值,比如知道面积可以通过面积平方根计算出边长的值:

import 'dart:math';

void main() {
  var square = Square();
  square.side = 1.5;
  print(square.area);

  square.area = 36;
  print(square.side);
}

class Square {
  num side;

  num get area => side * side;
      set area(value){
         side = sqrt(value);
      }
}

打印结果为:

2.25
6.0

构造方法

  • 如果没有定义构造方法,则会有个默认构造方法
  • 如果存在自定义构造方法,则默认构造方法无效
  • 构造方法不能重载

定义Person类,没有自定义的构造方法,则其会有默认构造方法

class Person{
  String name;
  int age;
}

等同于:

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;
  }
}

由于构造函数传入参数赋值的形式很常见,Dart中提供一种语法糖的形式来简化这种操作,改造后如下:

class Person{
  String name;
  int age;

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

命名构造方法

  • 使用命名构造方法,可以实现多个构造方法
  • 使用类名.方法 的形式实现

由于Dart中的方法无法被重载,我们只能通过命名构造方法的形式来实现多个构造方法,如下:

class Person{
  String name;
  int age;

  Person(this.name, this.age);

  Person.withName(this.name);

  Person.withAge(this.age);
}

定义了三个构造方法,其中有两个属于命名构造方法,使用的是 类名.方法 的形式, 创建实例的时候也是类似于构造方法的创建:

void main() {
    var p1 = Person("黄家驹", 31);
    var p2 = Person.withName("黄家驹");
    var p3 = Person.withAge(31);
}

常量构造方法

  • 如果类是不可变状态,可以把对象定义为编译时常量
  • 使用const声明构造方法,并且所有变量都为final
  • 使用const声明对象,可以省略
void main() {
    const p = Person("张三", 18);
  //p = Person("李四", 20);  此时会报错: Constant variables can't be assigned a value
}

class Person{
  final String name;
  final int age;

  const Person(this.name, this.age);
}

工厂构造方法

  • 工厂构造方法类似于设计模式中的工厂模式
  • 在构造方法前添加关键字factory实现一个工厂构造方法
  • 在工厂构造方法中可返回对象
void main() {
  var logger = Logger("tag1");
  logger.log("this is a log");
}

class Logger {
  final String name;

  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name){
    if(_cache.containsKey(name)){
      return _cache[name];
    }else{
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg){
    print(msg);
  }
}

可以看到Logger中包含一个工厂构造方法,通过判断一个缓存Map中是否包含指定name的Logger对象,如果有则直接取出并返回,没有则调用私用的命名构造方法internal()创建,然后存进map并返回该对象。

初始化列表

  • 初始化列表会在构造方法体执行之前执行
  • 使用逗号分隔初始化表达式
  • 初始化列表常用于设置final变量的值
void main() {
    var p = Person.withName("张三");
    print("namee = ${p.name}, age = ${p.age}, gender = ${p.gender}");
}

class Person{
  String name;
  int age;
  final String gender;

  Person.withName(this.name): age = 20, gender = "female";
}

打印结果为:

namee = 张三, age = 20, gender = female

可以看到age和gender属性被赋值了,而gender属于final,还是可以被赋值,说明初始化列表在构造方法体执行之前执行

静态成员

  • 使用static关键字来实现类级别的变量和函数
  • 静态成员不能访问非静态成员,非静态成员可以访问静态成员
  • 类中的常量需要使用static const声明
void main() {
  Person.growUp();
  Person.growUp();
  Person.growUp();
}

class Person{
    static const int maxAge = 100;
    static int age = 1;

    static void growUp(){
       age ++;
       print("growUp age = $age");
    }
}

打印结果为:

growUp age = 2
growUp age = 3
growUp age = 4

和Java类似,使用static关键字修饰,growUp为静态方法,可以通过类名.方法名直接调用

对象操作符

下面讲到的对象操作符和Kotlin中的差不多,有Kotlin经验的小伙伴已经get到手了。

  • 条件成员访问 ?.
void main() {
   Person person;
   person?.work();
}

class Person{
  void work(){
    print("work...");
  }
}

?.调用方法是在对象不为null的时候才会执行,由于person变量并没有赋值,此时运行没有输出任何语句。

  • 类型转换 as
void main() {
    dynamic a = "I Love Beyond";
    (a as String).split(" ")
}

声明一个dynamic类型的a变量,赋值为字符串,但是不能直接调用到split()方法,因为split()是属于String类的方法,这里需要将其转换为String类型,再调用。

  • 是否是指定类型 is、is!
void main() {
  dynamic a = 1;
  
  if(a is! bool){
    print("a is not bool");
  }
  
  a = true;
  
  if(a is bool){
    print("a is bool");
  }
}

声明一个dynamic类型的a变量,赋值为1,1处判断其a是否不是布尔值类型,由于a是int类型,满足条件,所以会打印"a is not bool",然后再将a赋值为true,此时a为bool值,所以2处的条件语句会满足,会打印 "a is bool"。

对象call方法

如果类实现了call()方法,则该类的对象可以作为方法使用

void main() {
   var person = Person();
   person("黄家驹", 31);
}

class Person{
  String name;
  int age;

  void call(String name, int age){
     print("name = $name, age= $age");
  }
}

打印结果为:

name = 黄家驹, age= 31

由于实现了call()方法,所以可以直接把对象作为方法来调用,call()方法也可以定义为有返回值:

void main() {
   var person = Person();
   print(person("黄家驹", 31));
}

class Person{
  String name;
  int age;

  String call(String name, int age) => "name = $name, age= $age";
}

本章已介绍完毕,感兴趣的小伙伴可以继续查看第四章:

Dart入门宝典第四章——面向对象编程之继承、抽象类、接口、Mixins的介绍