Dart语法精讲二:面向对象

568 阅读4分钟

类与对象

  • 类是通过class声明的代码段,包含属性和方法。
    • 属性:用来描述类的变量
    • 方法:类中的函数称为类的方法
  • 对象是类的实例化结果(var obj = new MyClass()) new的过程就是实例化

通俗点讲类与对象的关系就有点像图纸和房子的区别,类是图纸,对象就是造的房子。

image.png

class Person{
  //声明类的属性
  String name = '张三';
  //类的方法
  void getInfo(){ //类的方法可以不跟类型
    print('我是$name'); 
  }
}

void main(List<String> args) {
  Person p = Person();
  print(p.name);
  p.getInfo();
}

要注意一点,Dart中所有的内容都是对象,对象的属性在vscode中是用一个🔧来标识,而方法是通过立方体来标识的。

image.png

image.png

构造器(构造函数)

  • 默认构造函数
    • 与类同名的函数,在实例化,自动被调用
  • 命名构造函数
    • 在类中使用命名构造器(类名.函数名)实现多个构造器,可以提供额外的清晰度
  • 常量构造函数
    • 如果类生成的对象不会改变,可以通过常量构造函数使这些对象成为编译时常量
  • 工厂构造函数
    • 通过factory声明,工厂函数不会自动生成实例,而是通过代码来决定返回的实例

代码1 默认构造函数

class Point{
  num x;
  num y;

  Point(this.x,this.y);
}

void main(List<String> args) {
  var p = Point(3, 4);
  print(p.y); //4
}

代码2 命名构造函数

命名构造函数可以让一个类有多个构造函数,以此增加类的清晰度

class Point{
  num x,y;
    
  //默认坐标
  Point.origin(){
    x = 0;
    y = 0;
  }
    
  //手动设置坐标
  Point.fromJson({x:0,y:0}){
    this.x = x;
    this.y = y;
  }
}

void main(List<String> args) {
  Point p1 = new Point.origin();
  print(p1.x);

  Point p2 = new Point.fromJson(x:6,y:6);
  print(p2.x);

}

代码3 常量构造函数

class Point{
  num x;
  num y;
  Point(this.x,this.y);
}

class ImmutablePoint{
  //属性必须通过final来申明
  final num x;
  final num y;

  //常量构造函数必须通过const来声明
  const ImmutablePoint(this.x,this.y); //常量构造函数不能有函数体
}


void main(List<String> args) {
  var p1 = new Point(1,2);
  var p2 = new Point(1, 2);
  print(p1 == p2); //false

  //常量构造函数可以当做普通构造函数使用,一旦new了,它就失去了它的意义变成一个普通的构造函数了
  var p3 = new ImmutablePoint(1, 2);
  var p4 = new ImmutablePoint(1, 2);
  print(p3 == p4); //false

  // 正确的方式是通过const (声明不可变对象必须通过const关键字)
  var p5 = const ImmutablePoint(1, 2);
  var p6 = const ImmutablePoint(1, 2); //这个在flutter性能优化中使用的特别多 这里const不能省略,省略了就相当于new
  print(p5 == p6); //true

}

代码4 工厂构造函数

class Person{
  String name;

  static Person instance;

  //声明一个工厂构造函数
  factory Person([String name = '刘备']){
    // 工厂构造函数中不能使用this关键字
    // print(this.name);
    if(Person.instance == null){
      //第一次实例化
      Person.instance = new Person.newSelf(name);
    }
    //非第一次实例化
    return Person.instance;
  }
  //命名构造函数
  Person.newSelf(this.name);
}

void main(List<String> args) {
  //实例化操作
  Person p1 = new Person('关羽');
  print(p1.name); //关羽
  Person p2 = new Person('张飞');
  print(p2.name); //关羽
}

访问修饰符

  • Dart与TS不同,没有访问修饰符(public、protected、private)
  • Dart类中,默认的访问修饰符是公开的(即public)
  • 如果属性或方法以_(下划线开头),则表示私有的(private)
  • 只有把类单独抽离出去,私有属性和方法才起作用
//Person.dart
class Person{
  String name;
  //声明私有属性
  num _money = 100;

  Person(this.name);

  num getMoney(){
    return this._money;
  }

  //声明私有方法
  void _wife(){
    print('我是$name的老婆');
  }

}
//private.dart
import 'lib/Person.dart';
void main(){
  var p = new Person('张三');
  print(p.name); //张三

  //访问私有属性
  // print(p._money); //与main函数在同一个作用域所以能访问,这个的话跟java差不多.

  print(p.getMoney());
  // print(p._wife());  报错,私有方法
}

getter与setter

这里与java是类似的,但略有所不同,具体可以看如下的代码

class Circle{
  final double PI = 3.1415926;
  num r;
  Circle(this.r);

  //使用get声明的方法不能有(),get加载类型后,名称前
  num get area{
    return this.PI * this.r * this.r;
  }

  set setR(value){
    this.r = value;
  }
}

void main(List<String> args) {
  var c = Circle(10);
  print(c.area); //声明了get以后,访问方法要像访问属性一样了,不能加()

  //通过Setter修改属性
  c.setR = 20;
  print(c.area);
}

初始化列表

  • 作用:在构造函数中设置属性的默认值

我们想在类中给所有创建的对象一个默认值,可以按照下面的代码来操作

class Rect{
  int height;
  int width;

  Rect([int height = 2,int width = 10]){
    this.height =height;
    this.width = width;
  }
}


void main(List<String> args) {
  var r = Rect();
  print(r.height);
}

Dart给我们提供了一个语法糖

class Rect{
  int height;
  int width;

  // Rect([int height = 2,int width = 10]){
  //   this.height =height;
  //   this.width = width;
  // }
  Rect():height = 2, width = 10{
    
  }
}


void main(List<String> args) {
  var r = Rect();
  print(r.height);
}

初始化列表的特殊用法(重定向构造函数)

class Point{
  double x,y,z;
  Point(this.x,this.y,this.z);

  Point.twoD(double x, double y) : this(x,y,0);
}
void main(List<String> args) {
    //实例化点
  var p = Point(1,2,3);
  print(p.z); //3.0

  var p2 = Point.twoD(4,5);
  print(p2.z); //0.0
}

static关键字

  • static关键字用来指定静态成员

    • 通过static 修饰的属性是静态属性
    • 通过static修饰的方法 是静态方法
  • 静态成员可以通过类名称直接访问(不需要实例化)

    • 实例化是比较消耗资源的,声明静态成员,可以提高程序性能
  • 静态方法不能访问非静态成员,非静态方法可以访问静态成员

    • 静态方法中不能使用this
    • 不能使用this关键字,访问静态属性
class Person{
 static String name = '张三';
  static  void printInfo(){
    int age = 18; //静态方法不能访问非静态属性
    // print(this.name); //不能通过this访问静态属性
    print(name);
    print(age); //int age = 18; //静态方法不能访问非静态属性
  }

  printUserInfo(){
    //非静态方法,可以访问静态属性
    print(name);
  }
}

void main(List<String> args) {
  //静态成员,可以通过类名称直接访问
  print(Person.name);
  Person.printInfo();
}

metadata元数据

  • 元数据以@开头,可以给代码标记一些额外的信息
    • 元数据可以用来重写库,类,构造器,字段,参数等
  • @override(重写)
    • 某方法添加注释后,表示重写了父类中的同名方法
  • @required(必填)
    • 可以通过@ required来注解Dart中的命名参数,用来表示它是必填参数
  • @deprecated(弃用)
    • 若某类或某方法加上该注解之后,表示此方法或类不再建议使用 演示deprecated
class Phone {
  //这是旧版本中的开机方法,会在将来的版本中移除
  @deprecated
  activate(){
    turnOn();  //提升用户的体验
  }
  turnOn(){
    print('开机');
  }
}

void main(List<String> args) {
  var p = new Phone();
  p.activate();
  p.turnOn();
}

面向对象的继承

  • 子类通过extends关键字来继承父类
    • 继承后,子类可以使用父类中,可见的内容(属性或方法)
  • 子类中,可以通过@ override 元数据来标记“覆写”方法
    • 覆写方法:子类中与父类同名的方法
  • 子类中,可以通过super关键字来引用父类中可见(私有的不行)的内容,如属性,方法,普通构造函数,命名构造函数。
  • 子类若想访问父类的方法,需要通过get与set的形式。
class Father{
  String name = '刘备';
  num money = 10000;

  say(){
    print('我是$name');
  }
}

class Son extends Father{
  @override //并不会对程序有任何影响,只是做一个标记,官方建议我们写上 
  //重写父方法
  say(){
    print('我是刘禅');
  }
}


void main(List<String> args) {
  var f = new Father();
  print(f.name);

  var s = new Son();
  print(s.name);
  print(s.money);
  s.say();
}
class Father{
  String name = '刘备';
  num money = 10000;
  int _age = 55; //private私有变量不能被继承
  
  String job;
  Father(this.job);

  say(){
    print('我是$name');
  }
}

class Son extends Father{
  @override //并不会对程序有任何影响,只是做一个标记,官方建议我们写上 
  //重写父方法
  say(){
    print('我是刘禅,我继承了${super.money} 那么多钱'); //通过super调用父类的属性
  }

  Son(String job):super(job); //子类可以继承构造函数
}

void main(List<String> args) {
  var son1 = Son('皇帝');
  son1.say();
}

抽象类

  • 抽象类是用abstract关键字修饰的类。
  • 抽象类的作用是充当普通类的模板,约定一些必要的属性和方法。
  • 抽象方法是指没有方法体的方法-
    • 抽象类中一般都有抽象方法,也可以没有抽象方法
    • 普通类中,不能有抽象方法
  • 抽象类不能被实例化(不能被new)
  • 抽象类可以被普通类继承(extends)
    • 如果普通类继承抽象类,必须实现抽象类中的所有抽象方法
  • 抽象类还可以充当接口被实现(implements)
    • 如果抽象类当做接口实现的话,普通类必须得实现抽象类里面定义的所有属性和方法。
abstract class Phone{
  //
  //声明抽象方法
  void processor(); //标记为手机的处理器
  void camera(); //标记为手机的摄像头
  void info(){
    print('我是抽象类中的一个普通方法');
  }
}

class Xiaomi extends Phone{
  // 普通类继承了抽象类,就必须实现抽象类中所有的抽象方法  (实现就类似重写的意思)
  @override
  void processor(){
    print('晓龙888处理器');
  }

  @override
  void camera(){
    print('三星摄像头');
  }
}


class Huawei extends Phone{
  // 普通类继承了抽象类,就必须实现抽象类中所有的抽象方法  (实现就类似重写的意思)
  @override
  void processor(){
    print('麒麟990');
  }

  @override
  void camera(){
    print('莱卡摄像头');
  }
  // void aaa(); //普通类是不能这样只给方法名不给方法体的
}

void main(List<String> args) {
  //抽象类,不能被实例化
  // var p1 = new Phone();  直接报错

  Xiaomi m = new Xiaomi();
  m.processor();
  m.camera();
  m.info();

  Huawei h = new Huawei();
  h.camera();
}

dart中的接口

  • 接口在Dart中就是一个类(只是用法不同)
    • 与java不同,java中的接口需要用interface关键字声明,dart中不需要
    • 接口可以是任意类,但一般使用抽象类做接口
  • 一个类可以实现(implements)多个接口,多个接口用逗号分隔。
    • class MyClass implements interface1,interface22{...}
    • 接口可以看成一个一个小零件。类实现接口就相当于组装零件
  • 普通类实现接口后,必须重写接口中所有的属性和方法。
//手机的处理器
abstract class Processor {
  String cores; //内核:双核,4核
  arch(String name); //芯片的制程:7mm,

}
abstract class Camera{
  String resolution; //摄像头的分辨率:1KW 3KW 1E
  brand(String name); //品牌:三星,莱卡,蔡司
}

//通过普通类来实现接口
class Phone implements Processor,Camera{ //必须实现接口中所有的属性和方法
  @override
  String cores;
  @override
  String resolution;
  Phone(this.cores,this.resolution);

  @override
  arch(String name) {
    print('芯片制程 $name ');
  }
  @override
  brand(String name) {
    print('相机品牌 $name');
  }
}

void main(List<String> args) {
  Phone p = new Phone('双核', '1kW'); //相机品牌 三星
  p.brand('三星');
  print(p.cores); //双核
}

mixin混入

  • 混入(Mixin)是一段公共代码(我的理解就是实现多继承)。混入有两种声明方式:

    • 将类当作混入class MixinA{...}
    • 作为Mixin的类只能继承自Object,不能继承其他类
    • 作为Mixin的类不能有构造函数
  • 使用mixin关键字声明mixin MixinB{...}

  • 混入(Mixin)可以提高代码的复用效率(Dart是单继承),普通类可以通过with来使用混入

    • class MyClass with MinxinA,MixinB{...}
  • 使用多个混入时,后引入的混入会覆盖之前混入中的重复的内容

    • MixinA和MixinB中都有hello()方法,MyClass会使用MixinB中的
class Father{

}
class MixinA{
  String name = 'MixinA';
  
  void printA(){
    print('A');
  }
  //一旦用了引入就不能用构造函数
  // MixinA() 用作混入的类,不能拥有构造函数
}

class MixinB{
  String name = 'MixinB';

  void printB(){
    print('B');
  }

}

class MyClass with MixinA,MixinB{ //Mixin中的类不能继承除了Object以外别的类,不然会报错.

}

void main(List<String> args) {
  var c = new MyClass();
  c.printA();
  c.printB();

  //后引入的混入,会覆盖之前引入的混入中重复的内容
  print(c.name); //MixinB  方法也是一样这里我就不演示了.
}

泛型

  • 泛型是在函数、类、接口中制定宽泛数据类型的语法。通常,在尖括号中,使用一个字母来代表类型,例如E,T,S,K和V等。使用泛型可以帮助我们减少重复的代码。

泛型函数

// String getData(String value){
//   return value;
// }

//泛型函数
// T getData<T>(T value){
//   return value;
// }

//只约定参数类型,不约定函数返回值的类型
getData<T>(T value){
  return value;
}

void main(List<String> args) {
  // print(getData("hello"));
  print(getData<int>(29));
  print(getData<String>('hello'));
}

泛型类

class CommonClass{
  Set s = new Set<int>();
  void add(int value){
    this.s.add(value);
  }
  void info(){
    print(this.s);
  }
}
//泛型类
class GenericClass<T>{
  Set s = new Set<T>();
  void add(T value){ //泛型放在实参这
    this.s.add(value);
  }
  void info(){
    print(this.s);
  }
}


void main(List<String> args) {
  CommonClass c = new CommonClass();
  c.add(1);
  c.add(2); //如果我这波新增 '2'就会报错
  c.info(); //{1, 2}

  GenericClass g = new GenericClass<int>();
  g.add(1);
  g.add(2);//如果我这波新增 '2'就会报错
  g.info();
  //字面量形式的泛型
  Set s2 = <int>{};
  s2.add(1);
  print(s2);
}

泛型接口

//泛型接口
abstract class ObjectCache{
  getByKey(String key);
  void setByKey(String key,Object value);
}

abstract class StringCache{
  getByKey(String key);
  void setByKey(String key,String value);
}

//泛型接口
abstract class Cache<T>{
  getByKey(String key);
  void setByKey(String key,T value);
}

class FileCache<T> implements Cache<T>{ //光标移动到FileCache上,快速修复
  @override
  getByKey(String key) {
    // TODO: implement getByKey
    throw UnimplementedError();
  }

  @override
  void setByKey(String key, T value) {
    // TODO: implement setByKey
    print('文件缓存: key=${key} value=${value}');
  }
}

void main(List<String> args) {
  //文件缓存 - 缓存字符串
  // FileCache fc = new FileCache<String>();
  // fc.setByKey('foo', 'bar');

  //文件缓存 - 缓存 Map
  FileCache fc = new FileCache<Map>();
  fc.setByKey('index', {"name":"zsf","age":18});
}

泛型类型限制

class SomeBaseClass{
  //... 
  
}
class Foo<T extends SomeBaseClass>{
  String toString() => "Instance of 'Foo<$T>'";

}

class extender extends SomeBaseClass{
  //...
}

class AnotherClass{
  //...
}

void main(List<String> args) {
  var SomeBaseClassFoo = Foo<SomeBaseClass>();
  print(SomeBaseClassFoo);

//   var f = Foo<AnotherClass>(); //类型不对直接报错
//   print(f); 
var f = Foo<extender>();
print(f); //不报错
}

枚举

  • 枚举是数量固定的常量值,通过enum关键字声明
    • enum Color {red,green,blue}
  • 枚举的values常量,可以获取所有枚举值列表
List <Color> colors = Color.values
enum Color {red,green,blue} //不加分号
void main(List<String> args) {
  //通过 index 返回枚举中具体常量的值
  print(Color.green.index); //1

  //通过values返回常量列表
  print(Color.values); //[Color.red, Color.green, Color.blue]
  List <Color> colors = Color.values;
  print(colors); //[Color.red, Color.green, Color.blue]

  //通过下标,访问列表中的内容
  print(colors[0]); //Color.red
  //通过forEach,去遍历列表的内容
  colors.forEach((element) {
    print('value $element,index ${element.index}');
  });

}