喜迎
春节

面向对象


Dart面向对象编程(OOP)是一种将代码组织成对象的编程范式,每个对象都包含数据和行为,类似于现实世界中的实体。

1 面向对象

  • 面向对象编程OOP的三个基本特征
  • 封装:把客观事物封装成抽象的类,并把自己的部分属性和方法提供给其他对象。
  • 继承:可使用现有类的功能,并在无需重新编写原来类的情况下对功能进行扩展。
  • 多态:允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。

2 类的创建

  • Dart中一切皆对象,所有的对象都继承自Object类,一个类通常由属性和方法组成。
// Person类的创建
class Person {
  // 类的属性
  String name = '张三';
  int age = 21;

  // 类的方法
  void getInfo() {
    print('$name --- $age');
    print('${this.name} --- ${this.age}');
  }

  void setInfo(int age) {
    this.age = age;
  }
}

void main() {
  // 实例化Person类
  var p1 = new Person();
  // 调用属性
  print(p1.name);
  // 调用方法
  p1.getInfo();

  Person p2 = new Person();
  // print(p2.name);
  p2.setInfo(22);
  p2.getInfo();
}

3 构造函数

  • Dart中的4种构造函数
  • 普通构造函数:ClassName(...),分为无参构造函数和有参构造函数。
  • 命名构造函数:ClassName.identifier(...),支持一个类实现多个构造函数,不能重载。
  • 常量构造函数:const ClassName(...),如果生成类的对象不变,可以定义常量构造函数。
  • 工厂构造函数:factory ClassName(...),factory关键字可以放在类名函数和命名函数前。

3-1 无参构造函数

  • 如果不声明构造函数,则Dart会提供一个默认的无参构造函数。
  • 若自定义有参构造函数,那么默认的无参构造函数就不存在了。
class Person {
  String name = '张三';
  int age = 21;

  // 构造函数,该方法在实例化时触发
  Person() {
    print('无参构造函数');
  }

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

void main() {
  // 实例化
  Person p = new Person();
  // p.printInfo();
}

3-2 有参构造函数

  • 实现的功能:在构造函数中,可以动态地给类指定属性。
class Person {
  // 有参构造函数,提供语法糖简化
  String name = '';
  int age = 0;

  // 自定义构造函数
  // Person(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }

  // 构造函数的简写
  Person(this.name, this.age);

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

void main() {
  // 实例化
  Person p1 = new Person('张三', 21);
  p1.printInfo();

  Person p2 = new Person('李四', 22);
  p2.printInfo();
}

3-3 命名构造函数

class Person {
  // 空安全机制,默认情况下所有变量都必须在声明时被初始化,不能为null
  String name = '';
  int age = 0;

  // 命名构造函数
  Person.now() {
    print('命名构造函数');
  }

  Person.setInfo(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

void main() {
  Person p1 = new Person.now();
  Person p2 = new Person.setInfo('张三', 21);
  p2.printInfo();

  // 实例化DateTime调用它的命名构造函数
  var d = new DateTime.now();
  print(d);
}

4 类模块化

  • 将创建的类放在单独的文件夹下,例如:Person类,放在/lib/Person.dart中。
class Person {
  String name = '';
  int age = 0;

  Person(this.name, this.age);

  Person.now() {
    print('命名构造函数');
  }

  Person.setInfo(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}
  • 在另一个文件中调用该Person类,则需要导入该类的类文件名。
import './lib/Person.dart';

void main() {
  Person p = new Person.setInfo('张三', 21);
  p.printInfo();
}

4-1 私有属性

  • 在Dart中没有public、private protected等访问修饰符,但可以使用_将属性和方法私有化。
  • 自定义私有属性,或自定义私有方法时,必须将其单独抽类到一个文件中,这样才有效果。
  • 例如:Animal类,定义name为私有属性,将创建的Animal类放入/lib/AnimalProperties.dart。
class Animal {
  // 私有属性name
  String _name;
  int age;
  
  Animal(this._name, this.age);
  
  void printInfo() {
    print('${this._name} --- ${this.age}');
  }
  
  // 公有方法
  String getName() {
    // 私有属性name只在当前类中使用
    return this._name;
  }
}
  • name私有属性只在当前的Animal类中使用,此时main()函数无法访问Animal类中的私有属性name。
  • name私有属性在Animal类的getName()公有方法中,可通过访问公有方法间接地使用私有属性name。
import './lib/AnimalProperties.dart';

void main() {
  Animal a = new Animal('小狗', 100);
  // 无法访问私有属性
  // print(a._name);
  print(a.age);

  // 访问公有方法来访问私有属性
  print(a.getName());
}

4-2 私有方法

  • 例如:Animal类,定义run()为私有方法,将创建的Animal类放在/lib/AnimalMethods.dart中。
class Animal {
  String name;
  int age;

  Animal(this.name, this.age);

  // 私有方法
  void _run() {
    print('私有方法');
  }

  // 公有方法
  execRun() {
    // 类里面方法的相互调用
    this._run();
  }
}
  • run()私有方法在Animal类的execRun()公有方法中,可通过访问公有方法间接地访问私有方法run()。
import './lib/AnimalMethods.dart';

void main() {
  Animal a = new Animal('小狗', 100);
  // 间接地调用私有方法
  a.execRun();
}

4-3 getter与setter

class Rect {
  num height;
  num width;

  Rect(this.height, this.width);

  get area {
    return this.height * this.width;
  }

  set areaHeight(value) {
    this.height = value;
  }
}

void main() {
  Rect r = new Rect(10, 3);
  // 通过访问属性来访问area
  print('面积:${r.area}');

  r.areaHeight = 6;
  print('面积:${r.area}');
}

4-4 类的初始化列表

  • 可以在构造函数体运行之前初始化实例变量。
class Rect {
  int height;
  int width;

  // 初始化实例变量
  Rect(): height = 30, width = 10 {
    print('长为:${this.height}\n宽为:${this.width}');
  }

  getArea() {
    return this.height * this.width;
  }
}

void main() {
  Rect r = new Rect();
  print('面积:${r.getArea()}');
}

4-5 类中的静态成员

  • 通过使用static关键字,来实现类级别的变量以及函数。
  • 静态属性与静态方法通过类名访问,而非类的实例访问。
class Person {
  static String name = '张三';
  static void show() {
    print(name);
  }
}

main() {
  // var p = new Person();
  // p.show();
  print(Person.name);
  // 调用静态方法
  Person.show();
}

4-6 静态非静态比较

  • 非静态方法可以直接访问静态成员、静态方法,以及非静态成员。
  • 静态方法能访问静态成员和静态方法,不能访问非静态成员和非静态方法。
class Person {
  static String name = '张三';
  int age = 21;

  static void show() {
    print(name);
  }

  void printInfo() {
    // 访问非静态属性
    print(this.age);
    // 访问静态属性
    print(name);
    // 调用静态方法
    show();
  }

  static void printUserInfo() {
    // 访问静态属性
    print(name);
    // 调用静态方法
    show();
    // 无法访问非静态属性
    // print(age);
    // print(this.age);
    // 无法访问非静态方法
    // printInfo();
    // this.printInfo();
  }
}

main() {
  Person p = new Person();
  p.printInfo();

  Person.printUserInfo();
}

5 类的继承

  • 子类使用extends关键词来继承父类,子类能复写父类的方法。
  • 子类会继承父类里面可见的属性和方法,但不会继承构造函数。
class Person {
  String name = '张三';
  num age = 21;
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

// 可以访问Person中共有的属性和方法
class Web extends Person {}

main() {
  // 实例化Web类
  Web w = new Web();
  print(w.name);
  w.printInfo();
}

5-1 super关键词

class Person {
  String name;
  num age;
  Person(this.name, this.age);
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class Web extends Person {
  // super表示实例化子类时,将传过来的name和age参数赋值给父类Person
  Web(String name, num age): super(name, age) {}
}

main() {
  Web w = new Web('张三', 21);
  w.printInfo();
}

5-2 子类扩展属性和方法

class Person {
  String name;
  num age;
  Person(this.name, this.age);
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class Web extends Person {
  // 定义子类自己的属性
  String sex;
  Web(String name, num age, this.sex): super(name, age);

  // 定义子类自己的方法
  run() {
    print('${this.name} --- ${this.age} --- ${this.sex}');
  }
}

main() {
  Web w = new Web('张三', 21, '男');
  w.printInfo();
  w.run();
}

5-3 给命名构造函数传参

class Person {
  String name;
  num age;
  Person(this.name, this.age);
  // 命名构造函数
  Person.construct(this.name, this.age);
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class Web extends Person {
  String sex;
  Web(String name, num age, this.sex): super.construct(name, age);
  
  run() {
    print('${this.name} --- ${this.age} --- ${this.sex}');
  }
}

main() {
  Web w = new Web('张三', 21, '男');
  w.printInfo();
  w.run();
}

5-4 子类覆写父类的方法

class Person {
  String name;
  num age;
  Person(this.name, this.age);
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }

  work() {
    print('${this.name}在工作');
  }
}

class Web extends Person {
  Web(String name, num age): super(name, age);
  // 子类中扩展方法
  run() {
    print('run');
  }

  // 覆写父类的方法,不加@override也可以,建议加上
  @override
  void printInfo() {
    print('姓名:${this.name} --- 年龄:${this.age}');
  }

  @override
  work() {
    print('姓名:${this.name} --- 工作:写代码');
  }
}

main() {
  // 实例化子类
  Web w = new Web('张三', 21);
  w.printInfo();
  w.work();
}

5-5 子类中调用父类方法

class Person {
  String name;
  num age;
  Person(this.name, this.age);
  void printInfo() {
    print('${this.name} --- ${this.age}');
  }

  work() {
    print('${this.name}:工作 --- ');
  }
}

class Web extends Person {
  Web(String name, num age): super(name, age);
  // 子类中扩展方法
  run() {
    print('程序:执行 --- ');
    // 子类调用父类方法
    super.work();
  }

  @override
  void printInfo() {
    print('姓名:${this.name} --- 年龄:${this.age}');
  }
}

main() {
  // 实例化子类
  Web w = new Web('张三', 21);
  w.printInfo();
  w.run();
}

6 抽象类与泛型

  • 抽象类主要通过abstract关键词用于定义标准,子类可继承抽象类,也可实现抽象类接口。
    • 抽象类不能被实例化,只有继承它的子类可以。
    • 如果子类继承抽象类,必须实现它的抽象方法。
    • 没有方法体的方法称之为抽象方法,不能用abstract关键词来声明。
    • 若把抽象类当接口实现,须先实现抽象类中定义的所有属性和方法。
  • extends抽象类和implements的区别
    • 如果只是把抽象类当做标准,那么就用implements关键词来实现抽象类。
    • 若要复用抽象类中的方法且用抽象方法约束子类,用extends继承抽象类。
// 定义一个Animal类,要求它的子类必须包含eat方法
abstract class Animal {
  // 抽象方法
  eat();
  // 非抽象方法
  printInfo() {
    print('抽象类中的普通方法');
  }
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃美味的骨头');
  }
}

main() {
  Dog d = new Dog();
  d.eat();
  d.printInfo();

  // 抽象类没法直接被实例化,只有继承它的子类才能被实例化
  // var a = new Animal();
}

6-1 多态

  • 多态就是父类定义一个方法不去实现,让继承它的子类去实现,每个子类有不同的表现。
abstract class Animal {
  // 抽象方法
  eat();
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃美味的骨头');
  }
}

class Cat extends Animal {
  @override
  eat() {
    print('小猫在吃可爱的老鼠');
  }
}

main() {
  Dog d = new Dog();
  d.eat();
  Cat c = new Cat();
  c.eat();
}

6-2 赋值

  • 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
abstract class Animal {
  // 抽象方法
  eat();
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃美味的骨头');
  }

  run() {
    print('程序正在执行过程中');
  }
}

class Cat extends Animal {
  @override
  eat() {
    print('小猫在吃可爱的老鼠');
  }

  run() {
    print('程序正在执行过程中');
  }
}

main() {
  // 子类的实例赋值给父类的引用
  Animal d = new Dog();
  d.eat();
  Animal c = new Cat();
  c.eat();
}

6-3 接口

  • Dart的接口没用interface关键字定义,普通类或抽象类都可作为接口,使用implements关键字实现。
    • 如果实现接口的类是普通类,那么Dart会将普通类和抽象类里面属性的方法全部覆写一遍。
    • 抽象类可定义抽象方法,普通类不可,要实现类似于Java接口的方式,一般都会用抽象类。
  • 建议使用抽象类定义接口。
// 接口,即约定或叫规范,定义一个DB库,支持MySQL
abstract class DB {
  // 数据库的链接地址
  String uri = '';
  add(String data);
  edit();
  delete();
}

class MySQL implements DB {
  @override
  String uri;
  MySQL(this.uri);

  @override
  add(String data) {
    print('这是MySQL的add方法:$data');
  }

  @override
  edit() {
    return null;
  }

  @override
  delete() {
    return null;
  }
}

main() {
  MySQL mysql = new MySQL('127.0.0.1');
  mysql.add('123456789');
}

6-4 抽类

  • 抽类步骤一:lib目录新建一个DB.dart文件。
// 接口,即约定或叫规范,DB.dart文件
abstract class DB {
  // 数据库的链接地址
  String uri = '';
  add(String data);
  edit();
  delete();
}
  • 抽类步骤二:lib目录新建一个MySQL.dart文件,引入DB.dart文件。
import 'DB.dart';

// MySQL.dart文件
class MySQL implements DB {
  @override
  String uri;
  MySQL(this.uri);

  @override
  add(String data) {
    print('这是MySQL的add方法:$data');
  }

  @override
  edit() {
    return null;
  }

  @override
  delete() {
    return null;
  }
}
  • 抽类步骤三:创建一个Test.dart文件,用于引入MySQL.dart文件。
import 'lib/MySQL.dart';

// Test.dart文件
main() {
  MySQL mysql = new MySQL('127.0.0.1');
  mysql.add('123456789');
}

6-5 多接口

// 一个类实现多个接口
abstract class A {
  String name = '';
  printA();
}

abstract class B {
  printB();
}

class C implements A, B {
  @override
  String name = '';

  @override
  printA() {
    print('打印A');
  }

  @override
  printB() {
    print('打印B');
  }
}

void main() {
  C c = new C();
  c.printA();
  c.printB();
}

6-6 mixins

  • mixins即混入,指在类中混入其他功能,在Dart中可以使用mixins实现类似多继承的功能。
  • 使用的条件伴随着Dart版本的变化一直在改变,这里用的是Dart 2.x中使用mixins的条件。
  • 一个类可以mixins多个mixins类,mixins绝不是继承,也不是接口,而是一种全新的特性。
class A {
  String info = '这是A';
  void printA() {
    print('A');
  }
}

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

class C with A, B {}

void main() {
  var c = new C();
  c.printA();
  c.printB();
  print(c.info);
}

(1) 继承自Object

  • 作为mixins的类只能继承自Object,不能继承其他类。
class Person {
  printInfo() {
    print('Person类');
  }
}

class A extends Person {
  String info = '这是A';
  void printA() {
    print('A');
  }
}

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

// 这里A类继承了Person类,C类mixinsA类时会报错
// class C with A, B {}

void main() {}

(2) 不能有构造函数

  • 作为mixins的类不能有构造函数。
class A {
  String info = '这是A';
  void printA() {
    print('A');
  }
}

class B {
  // 写构造函数会报错
  // B() {};
  void printB() {
    print('B');
  }
}

class C with A, B {}

void main() {
  var c = new C();
  c.printA();
  c.printB();
  print(c.info);
}

(3) 继承与mixins一起

  • 继承与mixins一起写的方法。
class Person {
  String name;
  Object age;
  Person(this.name, this.age);
  printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class A {
  String info = '这是A';
  void printA() {
    print('A');
  }
}

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

// C类具有Person类、A类和B类的功能
// class C with Person, A, B {}
// 此时Person类中可以有构造函数
class C extends Person with A, B {
  C(String name, Object age): super(name, age);
}

void main() {
  var c = new C('张三', 21);
  c.printInfo();
}

(4) 顺序不同结果不同

  • 注意with A, Bwith B, A的顺序,打印出来的内容不一样,后面替换前面。
class Person {
  String name;
  Object age;
  Person(this.name, this.age);
  printInfo() {
    print('${this.name} --- ${this.age}');
  }

  // 继承与mixins中有同样的方法,with A, B或with B, A的顺序同下
  void run() {
    print('Person类执行中');
  }
}

class A {
  String info = '这是A';
  void printA() {
    print('A');
  }

  void run() {
    print('A执行中');
  }
}

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

  void run() {
    print('B执行中');
  }
}

// 注意with A, B或with B, A的顺序,打印内容不一样,后面替换前面
class C extends Person with A, B {
  C(String name, Object age): super(name, age);
}

void main() {
  var c = new C('张三', 21);
  c.run();
}

(5) mixins的实例类型

  • mixins的实例类型就是其超类的子类型。
// ignore_for_file: unnecessary_type_check

class A {
  String info = '这是A';
  void printA() {
    print('A');
  }
}

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

class C with A, B {}

void main() {
  var c = new C();
  print(c is A);
  print(c is B);
  print(c is C);

  var a = new A();
  print(a is Object);
}

6-7 泛型方法

  • 泛型即解决类、接口、方法的复用性,以及对不特定数据类型的支持(类型校验)。
// 只能返回String类型的数据
String getData1(String value) {
  return value;
}

// 同时支持返回String类型和int类型,代码冗余
String getData2(String value) {
  return value;
}

int getData3(int value) {
  return value;
}

// 同时返回String类型和number类型,不指定类型可以解决问题
getData4(value) {
  return value;
}

// 不指定类型放弃了类型检查,需要实现的是传入什么就返回什么
T getData5<T>(T value) {
  return value;
}

void main() {
  print(getData5(21));
  print(getData5('张三'));
  
  getData5<int>(22);
  getData5<String>('李四');
  getData5<int>(23);
  getData5<String>('王五');
}

6-8 泛型类定义

class MyList1 {
  List list = <int>[];
  void add(int value) {
    this.list.add(value);
  }

  List getList() {
    return list;
  }
}

// 定义泛型类
class MyList2<T> {
  List list = <T>[];
  void add(T value) {
    this.list.add(value);
  }

  List getList() {
    return list;
  }
}

main() {
  // 创建固定长度的List,这里使用到了泛型
  List l1 = new List<String>.filled(2, '');
  l1[0] = '121';
  l1[1] = '张三';
  print(l1);

  // new关键词可以省略
  MyList1 l2 = MyList1();
  l2.add(123);
  l2.add(321);
  print(l2.getList());

  MyList2 l3 = MyList2();
  l3.add(125);
  l3.add('李四');
  print(l3.getList());

  MyList2 l4 = MyList2<String>();
  l4.add('王五');
  // 传int会报错
  l4.add('135');
  print(l4.getList());
}

6-9 泛型接口定义

  • 实现数据缓存的功能:文件缓存和内存缓存,这两种功能按接口约束实现。
  • 定义一个泛型接口
    • 约束实现子类必须有getByKey(Key)setByKey(key, value)
    • 要求setByKey的value类型和实例化子类时指定的类型需保持一致。
// 定义一个接口
abstract class Cache<T> {
  // 定义两个抽象方法
  getByKey(String key);
  void setByKey(String key, T value);
}

// 定义两个类FileCache和MemoryCache
class FileCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    return null;
  }

  @override
  void setByKey(String key, T value) {
    print('我是文件缓存,把key=${key}、value=${value}的数据写入到了文件中');
  }
}

class MemoryCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    return null;
  }

  @override
  void setByKey(String key, T value) {
    print('我是内存缓存,把key=${key}、value=${value}的数据写入到了内存中');
  }
}

void main() {
  MemoryCache m1 = new MemoryCache<String>();
  m1.setByKey('index', '{姓名: 李四, 工作: 测试}');
  MemoryCache m2 = new MemoryCache<Map>();
  m2.setByKey('index', {'姓名': '张三', '工作': '码农'});
}

文章作者: bsf
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 bsf !
评 论
  目录