Flutter开发之旅:从源码赏析Dart面向对象

2023年 7月 13日 50.9k 0

1.面向对象的条件

1.1:三大特性

首先房子、车子、票子要有的吧,不然还面个什么对象?其次面向对象思想要到位,准备的三大件:封装、继承、多态

1.1.1:封装的思想

下面这东西大家应该见过,是一个电子元件,它能很好地说明封装的特性。每个电子元件都有暴露在外的引脚。这些脚分为输入和输出。使用一个电子元件时,我们只是关心输入和输出的情况,也就是它的真值表,对他内部的构成是不关心的。 所以一个电子元件对硬件组装师而言,就是一个有引脚的黑块,一个能够完成特定功能的逻辑单元。类比一下三方类库,在引入之后,不需要知道库的具体实现逻辑,只要按照暴露的API(真值表),你进行一个API的调用(输入),就会完成特定的功能(输出)。

类也是这样,最终目的是要实现逻辑的复用,让其成为一个可独立存在的逻辑单元,从而和其他类共同运作。但是现在与上面所不同的是:我们不单单是一个使用者,更多角色的是它的设计者。所以需要考虑的要点不仅是此类和其他类如何契合运作,更重要的是它的内部构成。 要知道,使用一个电子元件,和设计制造一个电子元件是完全不同的,而程序设计师显然是后者。

1.1.2:继承的思想

一个人的出生并非是一无所有,它享有着父母的资产,人脉,地位。这些都是他可以使用的资源。 这就无需艰苦奋斗来达到当前的境地,从而能够在未来的发展中更上一层。当然,类也是这样,子类通过继承可以享受到父类所带来的'天赋'。

这就涉及到了一个概念,叫抽象。抽象并非随便抽的,不以解决问题为前提的抽象都是耍流氓,通过抽象来提取对象的公共特征,形成基类。这相当于是父母在努力打拼,想为孩子打造一个适宜的生存环境。

还有一点就是:孩子不一定继承父亲的一切,有些东西是父亲不想给孩子的,这就涉及到继承的访问限制。

1.1.3:类的多态

一个人在社会中可以拥有多个角色,比如捷特在学校是一个学生、在公司是程序员、在周末是一个男朋友、在旅行中是一个游客,这就是一个对象的不同状态,简称:多态。 多态有什么优势,比如:有人喊,来个学生搬桌子,捷特就可以以学生的身份过去;周末妹子约,捷特就可以男朋友的身份过去;有人喊,来个程序员敲改bug,捷特也能跑过去。 下面写段伪代码,这样的优势在于使用对象的那个方法只需要针对接口即可,并不需要传入Me对象。如果pickDesk,date,fixBug全部传入Me对象,虽然能运行,但是不利于拓展和维护。

class Me implements Pickable,Kissable,Codeable{

}

pickDesk(Pickable student){
  student.pick(this);
}

date(Kissable boyfriend){
  if(boyfriend.getId == this.id){
    boyfriend.kiss(this);
  }
}

fixBug(Codeable coder){
  coder.debug(this);
}
复制代码

好了,罗里吧嗦一堆,下面开始进入正题,给你介绍Dart的面向对象,一般说面向对象都是个Person,在加个Student什么的。 虽说没什么不好,但感觉很平淡无奇,既然咱们都是有面向对象经验的人,直接去源码里找对象面面呗,这样才惊险,刺激。

2.从Size一族开始说起

我一直在想通过那个类的源码开始说比较好,最好不要太长,也不要太难,Size就比较完美。

2.1.类的定义

class 关键字定义类,没毛病,extend 继承关键字,也很OK。

---->[sky_engine/lib/ui/geometry.dart:347]-------
class Size extends OffsetBase {
复制代码
2.2.抽象类的定义

Size继承自OffsetBase,那就来看一下OffsetBase。首先它是一个抽象类,使用的关键字也是abstract。 其中有一个构造方法,传入两个参数,分别是水平和竖直的值。注意构造方法的书写形式是和Java有所区别的。 其中封装了两个私有属性:_dx_dy

---->[sky_engine/lib/ui/geometry.dart:9]-------
abstract class OffsetBase {
  const OffsetBase(this._dx, this._dy);//构造函数

  final double _dx;
  final double _dy;
}
复制代码
2.3:方法

不知大家看到下面的代码感觉如何,反正我感觉挺别扭,也许不太熟悉吧。

//是否无法衡量的
bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity;
//是否有限
bool get isFinite => _dx.isFinite && _dy.isFinite;

看着有点吓人,不过才刚开始,可以慢慢分析,上面这句如果看得眼花缭乱,
我改写了一下,下面的应该可以看懂吧,意思就是如果_dx和_dy有一个超过double的范围就返回true

bool isInfinite() {
    return _dx >=double.infinity || _dy >= double.infinity;
}

注意一点:在Java中常用isXXX,Dart 里的get关键字可以让调用简洁,使用如下
var size= Size(30,40);
print(size.isInfinite);//false
复制代码
2.4:运算符重载

这段代码看了三秒钟,箭头太多,愣是没反应过来,仔细一想,这不会是运算符重载吧,和C++的有点神似。下面是分别对=、==的运算符重载。


bool operator  _dx < other._dx && _dy  other._dx && _dy > other._dy;
bool operator >=(OffsetBase other) => _dx >= other._dx && _dy >= other._dy;

@override
bool operator ==(dynamic other) {
  if (other is! OffsetBase)//如果传入对象不是OffsetBase
    return false;//直接滚
  final OffsetBase typedOther = other;
  return _dx == typedOther._dx &&//判断是否相等
         _dy == typedOther._dy;
}

使用方式:
var size= Size(30,40);
var size2= Size(10,20);
print(size>size2);//true
复制代码
2.5:哈希值和toString

和Java比较像,没啥好说的。这里要插一句,看源码的时候,最好自己留心一下源码中的书写风格,毕竟和大佬看起是很必要的,比如命名的风格,注释的风格等。

@override
int get hashCode => hashValues(_dx, _dy);

@override
String toString() => '$runtimeType(${_dx?.toStringAsFixed(1)}, ${_dy?.toStringAsFixed(1)})';
复制代码

OK,这样一个OffsetBase对象就面完了,紧不紧张,刺不刺激。下面继续看Size

2.6:构造Size对象方法

注意了,这里圈起来,要考的。使用父类的构造方法来完成本类的构造个语法格式:类名(参数,...):super(参数,...)

  
class Size extends OffsetBase {
  /// 根据给定的宽高创建Size对象
  const Size(double width, double height) : super(width, height);
复制代码

在联想一下初始项目中的让人蒙圈的这句话,是不是豁然开朗。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
复制代码

然后就是一堆构造Size对象的方法,总的来说就是,不管怎样,你给我弄两个值来当宽高

  Size.copy(Size source) : super(source.width, source.height);
  const Size.square(double dimension) : super(dimension, dimension);
  const Size.fromWidth(double width) : super(width, double.infinity);
  const Size.fromHeight(double height) : super(double.infinity, height);
  const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0);

  static const Size zero = const Size(0.0, 0.0);
  static const Size infinite = const Size(double.infinity, double.infinity);
复制代码
2.7:Size中的属性封装

有了老爹之后,Size类就可以使用老爹的“财富”,这里Size为了形象,将老爹的东西名字都给改了。 类存在的价值之一在于封装属性及调动属性之间的关系完成特定功能,比如aspectRatio可以获取宽高比。 对于任意Size对象,在任意时间,任意空间,都可以调用aspectRatio方法获取宽高比,这是面向过程所不能及的。

  double get width => _dx;
  double get height => _dy;

  double get aspectRatio {
    if (height != 0.0)
      return width / height;
    if (width > 0.0)
      return double.infinity;
    if (width < 0.0)
      return double.negativeInfinity;
    return 0.0;
  }
复制代码
2.8:继承的优势

上面OffsetBase说到运算符重载,在Size类中也有运算符重载,这些是尺寸的四则运算,然而Size依旧可以使用OffsetBase中重载过的运算符,这就是有老爹的优势。

OffsetBase operator -(OffsetBase other) {
  if (other is Size)
    return new Offset(width - other.width, height - other.height);
  if (other is Offset)
    return new Size(width - other.dx, height - other.dy);
  throw new ArgumentError(other);
}
Size operator +(Offset other) => new Size(width + other.dx, height + other.dy);
Size operator *(double operand) => new Size(width * operand, height * operand);
Size operator /(double operand) => new Size(width / operand, height / operand);
Size operator ~/(double operand) => new Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble());
Size operator %(double operand) => new Size(width % operand, height % operand);
复制代码
2.9.看Offset类

继承会让孩子具有先天优势,那么两个孩子岂不是更加物尽其用。定义一个父类就是为了能够更好的拓展,OffsetBase自然也不例外。 Size对象描述了一个类似框框的对象,那么Offset描述的便是位移。两者有一个共同的特点,那就是有两个数值类的属性。这也是OffsetBase被抽象出来的原因。

class Offset extends OffsetBase {
  //构造方法
  const Offset(double dx, double dy) : super(dx, dy);
  factory Offset.fromDirection(double direction, [ double distance = 1.0 ]) {
    return new Offset(distance * math.cos(direction), distance * math.sin(direction));
  }
  
  static const Offset zero = const Offset(0.0, 0.0);
  static const Offset infinite = const Offset(double.infinity, double.infinity);

  double get dx => _dx; //水平位移
  double get dy => _dy; //数值位移
  
  //方法:属性的处理
  double get distance => math.sqrt(dx * dx + dy * dy);//移动距离
  double get distanceSquared => dx * dx + dy * dy;//移动距离的平方
  Offset scale(double scaleX, double scaleY) => new Offset(dx * scaleX, dy * scaleY);//缩放
  Offset translate(double translateX, double translateY) => new Offset(dx + translateX, dy + translateY);//移动
  
 //运算符重载
  Offset operator -() => new Offset(-dx, -dy);
  Offset operator -(Offset other) => new Offset(dx - other.dx, dy - other.dy);
  Offset operator +(Offset other) => new Offset(dx + other.dx, dy + other.dy);
  Offset operator *(double operand) => new Offset(dx * operand, dy * operand);
  Offset operator /(double operand) => new Offset(dx / operand, dy / operand);
  Offset operator %(double operand) => new Offset(dx % operand, dy % operand);

  Rect operator &(Size other) => new Rect.fromLTWH(dx, dy, other.width, other.height);

  //略....

  @override
  int get hashCode => hashValues(dx, dy);

  @override
  String toString() => 'Offset(${dx?.toStringAsFixed(1)}, ${dy?.toStringAsFixed(1)})';
复制代码
2.10:小结

这是三个类比较简单,很适合刚入门Dart的伙伴读读,关系简单画一下。 现在你应该对Dart中类的创建,属性,方法的书写以及类的继承有所理解了吧。

3.Dart中的接口与枚举

与Java不同,Dart中的接口定义依然是abstract关键字,接口和抽象类本质上并没有区别,都是对一类事物的抽象,只不过接口更倾向于提取事物的能力。比如Comparable接口和Pattern接口。Flutter开发之旅:从源码赏析Dart面向对象

3.1 :接口的定义

Comparable定义了一个抽象方法compareTo,用来和另一个对象进行比较,也就是这个接口的功能是作比较。 Pattern接口的功能是:匹配字符串,获得一个可迭代的匹配结果集。

---->[sky_engine/lib/core/comparable.dart:72]----------
abstract class Comparable {

  int compareTo(T other);
  
  static int compare(Comparable a, Comparable b) => a.compareTo(b);
}

---->[sky_engine/lib/core/pattern.dart:72]----------
abstract class Pattern {
  Iterable allMatches(String string, [int start = 0]);
  Match matchAsPrefix(String string, [int start = 0]);
}
复制代码
3.2:Dart中的接口

实现接口的类拥有该接口的功能。可以看出num是实现了Comparable接口的,可以说明它有作比较的能力。 接口的实现和Java一样,是关键字implements。

abstract class num implements Comparable {

复制代码
3.3:实现多个接口

Dart中的接口也是支持多实现的,用逗号隔开。比如String即实现Comparable,也实现了Pattern, 说明String同时可具有这两种功能。

abstract class String implements Comparable, Pattern {
复制代码
3.4:枚举类型

枚举通常用来表示相同类型的一组常量,用关键字enum来表示。 枚举对象可以结合switch做分支处理。 另外Dart中的枚举元素具有索引,从0开始,依次计数,用index属性访问。 说到枚举,我首先想到的就是Paint的头,就用这个类来说明一下:

Flutter开发之旅:从源码赏析Dart面向对象

---->[sky_engine/lib/ui/painting.dart:833]----------
enum StrokeCap {
  butt,//无头(默认)
  round,//圆型
  square,//方形
}

使用:
var paint= Paint();
paint.strokeCap=StrokeCap.round;//圆型
print(StrokeCap.round.index);//1
复制代码

看来源码中的几个类,面向对象也基本上能有个认识。下面来自定义个二位向量总结一下。

4.自定义向量类Vector2

4.1:定义Vector2类与使用

这里定义了Vector2类,包含两个数值分别是横纵坐标,构造函数用最原始的方式

class Vector2{
  num x;
  num y;

  Vector2(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

main(){//使用
  var v1=Vector2(3,4);
  print('(${v1.x},${v1.y})');//(3,4)
}
复制代码
4.2:构造函数的简写

通过简写,可以使构造函数简洁明了,同时也能实现等价功能。

class Vector2{
  num x;
  num y;
  Vector2(this.x,this.y);
}
复制代码
4.3:命名构造函数

源码中经常会出现XXX.formXXX来构造对象

class Vector2{
  num x;
  num y;
  Vector2.fromMap(Map point){
    this.x = point['x'];
    this.y = point['y'];
  }
}

main(){//使用
  var v2= Vector2.fromMap({'x':5,'y':6});
  print('(${v2.x},${v2.y})');//(5,6)
}
复制代码
4.4:定义方法
  double get distance => sqrt(x * x + y * y); //向量的模
  double get rad => atan2(y, x); //与x正方向夹角(弧度制)
  double get angle => 180 / pi * atan2(y, x); //与x正方向夹角(角度制)
复制代码
4.5:定义一个操作接口
abstract class Operable{
  void reflex();//反向
  void reflexX();//X反向
  void reflexY();//Y反向

  void scale(num xRate,num yRate);//缩放
  void translate(num dx,num dy);//平移
  void rotate(num deg,[isAnticlockwise=true]);//旋转
}
复制代码
4.6:继承接口,实现方法
class Vector2 implements Operable{
  num x;
  num y;
  Vector2(this.x,this.y);

  Vector2.fromMap(Map point){
    this.x = point['x'];
    this.y = point['y'];
  }

  @override
  void reflex() {
    this.x=-x;
    this.y=-y;
  }

  @override
  void reflexX() {
    this.x=-x;
  }

  @override
  void reflexY() {
    this.y=-y;
  }

  @override
  void rotate(num deg, [isAnticlockwise = true]) {
    var curRad = rad + deg*pi/180;
    this.x=distance*cos(curRad);
    this.y=distance*sin(curRad);
  }

  @override
  void scale(num xRate, num yRate) {
    this.x *=xRate;
    this.y *=yRate;
  }

  @override
  void translate(num dx, num dy) {
    this.x +=dx;
    this.y +=dy;
  }
  
  @override
  String toString() {
    return '(${this.x},${this.y})';
  }
}
复制代码
4.7:运算符重载

现学现卖,运算符重载一下

//运算符重载
Vector2 operator +(Vector2 other) => Vector2(x + other.x, y + other.y);
Vector2 operator -(Vector2 other) => Vector2(x - other.x, y - other.y);
num operator *(Vector2 other) => x * other.x + y * other.y;
复制代码

这个类就先这样,以后有需要继续改进。

4.8:测试
main() {
  var v1 = Vector2(3, 4);
  print(v1); //(3,4)
  print(v1.distance); //5.0
  print(v1.angle); //53.13010235415598
  v1.rotate(37);
  print(v1); //(-0.011353562466313798,4.0000058005648444)
  
  var v2 = Vector2.fromMap({'x': 5, 'y': 6});
  print(v2); //(5,6)
  v2.reflex();
  print(v2);//(-5,-6)
  
  var v3 = Vector2(2, 2);
  var v4 = Vector2(3, 2);
  print(v4 - v3); //(1,0)
  print(v4 + v3); //(5,4)
  print(v4 * v3); //10
}

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论