2.3、Dart语言基础:面向对象

学习笔记,旨在于快速入门和学习Dart,其中可能会有理解错误,请指出,一起学习。

系列文章

2.1、Dart语言基础:变量、运算符
2.2、Dart语言基础:函数与闭包
2.3、Dart语言基础:面向对象
2.4、Dart语言基础:异步
2.5、Dart语言基础:库与包
...

零、概述

Dart语言在面向对象编程提供了类Class、继承Inheritance、混合Mixins、接口和抽象类Interfaces and abstract classes,还有泛化generics等。

泛化在业务开发层面用到的比较少,本篇章介绍前面几个概念,后续专门一章节介绍泛化。

包括以下内容:
1、类的定义 和 继承,成员变量、成员方法,构造器。
2、支持类的静态化,即支持类方法和类变量。
3、支持抽象类Abstract Class。
4、支持实现Implements、扩展Extension、混淆mixins。


一、类 与 继承

  • Dart中万物皆为对象,所有的类都继承于 Object基类(null除除外)。
  • 类声明关键字class,继承关键字extends
  • 关键字this,指向当前对象。
    Dart规范是忽略this的,只有在存在命名冲突的情况下才需要用this。
  • 关键字super,指向父类对象,显式的调用父类的方法。

1、类的声明 和 成员变量的访问

class Pet {
  String? name;
  int weight = 0;
  void saySomething() {
    print('name is $name');
  }
}

  // 0、默认的无参数构造器
  var pet = Pet();
  pet.name = "gigi";

  // 1、访问实例属性:gigi
  print(pet.name);
  // 2、调用实例方法:name is gigi; weight = 0
  pet.saySomething();
  // 3、对象的类型是:Pet
  print('对象的类型是:${pet.runtimeType}');
  • 语法格式:关键字class
  • 使用 操作符. 访问 成员变量 和 成员方法。
  • .runtimeType 获取对象类型。
  var p2;
  // 1、null,p2为空则返回null
  print(p2?.name);
  • 为了避免左操作符为空导致的异常,可以使用操作符?.

2、成员变量

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}
  • 未初始化的成员变量,默认为null。
  • 非延迟的成员变量 在类对象被创建的时候,已经赋值。
2.1、final 成员变量
  • 不管任何场景下,只会被初始化一次。
  • 使用构造器或者初始化列表初始化final成员变量
class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

3、构造函数和析构函数

  • 由于Dart支持自动垃圾回收机制,因此无需手动写析构函数。

3.1、未命名构造器 和 命名构造器

class Point {
  double? x;
  double? y;
}
  
  // 1、null,null
  var p4 = new Point();
  print("${p4.x}, ${p4.y}"); 
  • 如果没有提供构造器,则会提供没有参数的默认未命名构造器。
class Point {
  double? x;
  double? y;

  // 1、带参数的未命名构造器
  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  // 语法糖,等价于上面语法
  Point(this.x, this.y);
  
  // 2、命名构造器ClassName.identifier
  Point.fromJson({x=0, y=0}) {
    this.x = x;
    this.y = y;
  }
}

void class_demo_2() {
  var p1 = new Point(6, 8);
  print("${p1.x}, ${p1.y}");

  var p2 = new Point.fromJson(x:10.0, y: 10.0);
  print("${p2.x}, ${p2.y}"); 
}
  • 构造函数的名称 可以是 类名本身,或者 命名构造器类名.方法名
    Constructor names can be either ClassName or ClassName.identifier
class Point {
  double? x;
  double? y;
  
  // 声明带参数的未命名构造器
  Point(this.x, this.y);

  // Error: 'Point' is already declared in this scope.
  Point();
}
class Rect extends Point {
}

// 2、构造器无法被继承。Error: Too many positional arguments: 0 allowed, but 2 found.
var p3 = Rect(6, 8);
  • 未命名构造器,即ClassName,有且只有一个,不管参数多少。
    否则编译报错Error: 'Point' is already declared in this scope.
  • 父类构造器无法被子类继承。
    如上Point(this.x, this.y); Rect无法使用。
  • 如果父类定义了 带参数的未命名构造器,则子类无法使用默认构造器。
    否则编译报错Error: The superclass, 'Point', has no unnamed constructor that takes no arguments.

3.2、继承链中的构造器

  • 继承链中,构造器的调用顺序
    1、初始化列表,initializer list
    2、父类的未命名无参数构造器,superclass’s no-arg constructor
    3、子类的未命名无参数构造器,main class’s no-arg constructor

  • 子类 需要手动调用 父类构造器。

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

  Point.fromMap(Map data) : x=data['x'], y=data['y'] {

  }
}

class Rect extends Point {
  Rect(double x, double y) : super(x, y);

  Rect.fromMap(Map data) : super.fromMap(data) {

  }
}

void class_demo_2() {
  var p3 = Rect(10, 10);
  print("${p3.x}, ${p3.y}");

  var p5 = Rect.fromMap({'x':20.0, 'y':20.0});
  print("${p5.x}, ${p5.y}");
}
  • 初始化列表作用:调用其他构造器、简单语句执行等
class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(this.x, this.y);
  
  // 1、简单的计算语句
  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
  // 2、简单的判断语句
  Point.withAssert(this.x, this.y) : assert(x >= 0) {
  }
  // 3、简单的赋值语句
  Point.fromJson(Map json) : x = json['x']!, y = json['y']! {
  }
  
  // 4、重定向构造器
  Point.alongXAxis(double x) : this(x, 0);
}

3.3、常量构造器,constant constructors

  • 常量构造器在编译期已决议,并且所有的变量必须为final类型。
class ImmutablePoint {
  final double x, y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
  const ImmutablePoint(this.x, this.y);
}
// 实例化
var p = const ImmutablePoint(2, 2);
  • 构建两个完全相同的常量类,只会创建一个常量类,多次调用值是相同的。
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

// They are the same instance!
assert(identical(a, b)); 

常量构造器的初始化

常量构造器并不总是创建常量对象。 Constant constructors don’t always create constants.

  • 常量构造器 调用前 必须使用const,才能得到常量对象。
    If a constant constructor is outside of a constant context and is invoked without const, it creates a non-constant object:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!
  • 在 "常量上下文" 中,在构造器或字面量前面可以忽略const关键字。
    Within a constant context, you can omit the const before a constructor or literal.
// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

// 等价于
// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

3.4、工厂构造器(Factory constructors),关键字factory

主要用途:
  • 1、创建 不返回新实例对象的构造器。例如,内存缓存中的实例对象、返回对象的子类型。
    Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class.

  • 2、初始化类的 final 属性变量 。

class Logger {
  final String name;
  bool mute = false;

  // 内部存储
  static final Map _cache = {};

  // 内部 命名构造器
  Logger._internal(this.name);

  factory Logger(String name) {
    // 存在key就获取值,不存在则添加到map, 然后返回值
    // 1、在构造器函数内,初始化final成员变量。
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map json) {
    return Logger(json['name'].toString());
  }

  void log(String msg) {
    if (!mute) print(msg);
  }
}

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
  • 在构造器函数内,初始化final成员变量。
    If you need to assign the value of a final instance variable after the constructor body starts.
  factory Logger(String name) {
    // 存在key就获取值,不存在则添加到map, 然后返回值
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }
  • 工厂构造器无法访问this关键字。
    Factory constructors have no access to this.

4、成员方法

  • 4.1、运算符方法,语法:operator 运算符
    运算符 是特殊名称的实例方法。Operators are instance methods with special names.
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
  • 4.2、Getters and setters方法,关键字get\set
    提供简单便捷的方式,支持简单计算,类似Swift的计算属性功能。
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

5、类继承

5.1、关键字extendssuper
class Television {
  void turnOn() {

  }
}

class SmartTelevision extends Television {
  void turnOn() {
    // 调用父类方法
    super.turnOn();
  }
}
5.2、重写方法,关键字@override
class SmartTelevision extends Television {
  @override
  void turnOn() {
    ...
  }
}
  • 可以重写的方法,包括实例方法、操作符方法、getter/setter方法。
  • 重写的方法必须满足一下几点
    1、重写方法的返回值的类型,必须和原方法一样(或是其类型的子类)。
    2、重写方法的参数类型,必须和原方法一样(或是其类型的子类)。
    3、重写方法的位置参数个数,必须和原方法的位置参数个数一样。
    4、泛型方法和非泛型的方法无法相互转化,必须同为泛型方法或者都不是。
  • 捕获调用类中不存在的方法或者变量,可以重写void noSuchMethod(Invocation invocation)方法。
class HelloClass {
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ${invocation.memberName}');
  }
}

二、类方法和类变量(Class variables and methods)

  • 声明类方法和类变量,必须在前面使用关键字static

1、类变量(静态变量)

// 类变量
class Queue {
  static const initialCapacity = 16;
}
void main() {
  assert(Queue.initialCapacity == 16);
}
  • 类变量:适用于 类范围(class-wide) 内的状态和常量。
  • 类变量 是在第一次被使用的时候 才进行初始化。

2、类方法(静态方法)

  • 静态方法无法访问this指针,不能访问实例变量;可以访问类变量(static variables)
import 'dart:math';
class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

三、抽象类

1、抽象类 和 抽象方法,关键字abstract

  • 抽象方法不能被实例化,主要用来抽象通用的逻辑。
abstract class Doer {
  void doSomething(); 
}

class EffectiveDoer extends Doer {
  void doSomething() {
  }
}
  • 抽象方法:实例方法、getter/setter方法,都可以声明为抽象方法。

  • If you want your abstract class to appear to be instantiable, define a factory constructor.



四、实现,implements

1、类的隐式接口

  • 任何的类 都隐式定义 一个接口,这个接口包含了 类全部的实例成员(变量和方法,但不包括构造函数) 及 类实现的所有接口。
    Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements.
// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';
  
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

2、类 支持多个 接口实现。

class Point implements Comparable, Location {...}

五、扩展,extension

主要用来为 现有库(library)和APIs 动态增加新的功能。

1、扩展的定义

extension  on  {
  ()*
}
  • 扩展可以有名字,如下NumberParsing
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
  
  double parseDouble() {
    return double.parse(this);
  }
}
  • 可以扩展的方法,包括操作符方法、getter/setter方法、成员方法、静态方法。
  • 支持 私有(本地)扩展,下划线_,该扩展只在当前文件或者库内部可见。

2、支持泛化扩展

例如:下面是 List 的扩展 MyFancyList

extension MyFancyList on List {
  int get doubleLength => length * 2;
  List operator -() => reversed.toList();
  List> split(int at) => [sublist(0, at), sublist(at)];
}

3、命名冲突,API conflicts

  • 方法1:导入import的时候,显示声明显示/隐藏(show/hide) 某个方法。
// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
  • 方法2:显示定义为不同的扩展名称。
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.

// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
  • 方法3:前缀调用,即导入import的时候重命名,关键字as
    类似于JavaScript的import-as功能。
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // Doesn't work.

// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

六、混淆,mixins

  • 主要用处是实现多继承,复用多个现有类的代码。

1、语法格式:

  • 关键字mixin,一般来说,混淆是一个无构造函数代码集合。
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    ...
  }
}
  • 关键字 'on' 用来 指定 混淆 只有被 特定的类或其子类 使用。
    Sometimes you might want to restrict the types that can use a mixin.
class Musician {
}

// 只能被Musician及其子类使用
mixin MusicalPerformer on Musician {
}

class SingerDancer extends Musician with MusicalPerformer {
}

2、混淆的使用

关键字with 用来声明类 遵循 一个或者多个混淆。

class Musician extends Performer with Musical {
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
  }
}

七、枚举

1、枚举的约束

  • 枚举不支持继承subclass、不支持mix、不支持implement。
  • 枚举无法实例化。

2、语法格式

  • 关键字enum
enum Color { red, green, blue }
// .index 枚举值
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

// .values 获取所有的枚举值
List colors = Color.values;
assert(colors[2] == Color.blue);

// 结合switch
var aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: 
    print(aColor); // 'Color.blue'
}
  • 每一个枚举值都包含index的getter方法。
  • 获取所有的枚举值列表,.values
  • 配合 switch 可以实现多分支流程控制。

你可能感兴趣的:(2.3、Dart语言基础:面向对象)