Dart基础(六)-面向对象和类
1.简介:
Dart是面向对象的语言,具有面向对象的三个基本特征,即封装、继承和多态。
- 封装(
Encapsulation
):把客观事物封装成抽象的类,类中屏蔽内部的实现细节,只提供部分的属性和方法供外界访问。 - 继承(
Inheritance
):子类自动共享父类的属性和方法,这是类之间的一种关系。子类可以对父类进行重写可扩展。 - 多态(
Polymorphism
):允许将子类的指针赋值给父类类型的指针,同一个父类函数在不同子类中的行为不同。
2.Dart面向对象独有的特征:
- Dart中一切皆对象,每个对象都是一个类的实例,包括数值类型;
- 除了
Null
类之外,所有的类都继承自Object
; - Dart类中可以基于
implement
实现接口,且可以实现多个接口; - Dart类中可以基于
mixin
继承,且可以实现多继承; - Dart可以用
Extension
实现在不更改类或创建子类的情况下对类进行扩展; - Dart子类可以重写父类方法;
- Dart方法不可以重栽,但是通过函数的参数的可选达到了方法重写的效果。
3.类的创建和使用:
类中包含实例成员(对象成员)和静态成员,实例成员包含实例变量和实例方法,静态成员包含静态变量可静态方法。对象(也叫实例)是由函数functions
(也叫做方法methods
)和数据data
(也叫做实例变量instance variables
、成员变量members variables
、属性properties
、字段fields
)组成的;类的对象访问成员变量或者方法需要.
语法,可选用?.
语法。
类中静态成员可以通过类名加Type.
语法进行访问。
3.1实例变量(instance variables
):
实例变量需要注意的点:
- 所有未初始化的实例变量的值都为
null
; - 所有实例变量都生成隐式getter方法,包括非
final
实例变量和没有初始化式的late final
实例变量也会生成隐式setter方法;
声明实例变量的关键字:
关键字 | 例子 | 意义 |
---|---|---|
Type | int a | 表明实例变量的类型,有构造函数,必须在构造函数中初始化,没有构造函数必须提供默认值;初始化赋值后,后续可以继续改变其值 |
? | int? a | 表明实例变量可以为null值,初始化时不是必传参数 ;初始化赋值后,后续可以继续改变其值 |
final | final int a | 表明实例变量只能初始化赋值一次;有构造函数,必须在构造函数中初始化,没有构造函数必须提供默认值;初始化赋值后,后续无法修改其值 |
late | late int a | 表明实例变量可以延时初始化,及可以在构造函数外进行初始化赋值 |
注意:有构造函数表示的是自定义的构造函数,不包括默认构造函数。
3.2构造函数(Constructors
):
构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与
new
一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
构造函数需要注意的点:
- 类中提供类名同名的默认无参构造函数,且默认调用超类中的无实参构造函数;
- 类自定义构造函数后,覆盖默认构造函数;
- 构造函数无法继承,默认构造函数除外;
- 构造函数和其它实例方法中
this
代表当前创建的实例,且只在名称冲突时使用。否则,Dart风格会省略this; - 构造函数和其它实例函数中
super
代表当前实例的父类指针; - 命名构造函数,用
Type.functionName()
语法定义的构造函数; - 命名构造函数与返回当前类实例的静态函数等同;
-
:
可以重定向构造函数或者父类构造函数。
3.3构造函数调用顺序:
默认情况下,子类中的默认构造函数调用超类的默认构造函数。
父类的构造函数在子类构造函数体的开头被调用;如果初始化列表Initializer list
也被使用,它会在调用超类之前执行。综上所述,执行顺序如下::
- 初始化列表;
- 父类的无参数构造函数的;
- 子类的无参数构造函数.
如果父类有自定义构造函数(后续都成为构造函数),子类构造函数则必须手动调用父类中的一个构造函数;在冒号:
之后指定父类构造函数;其含义是在调用子类的该构造函数之前,先执行父类的构造函数。
class Duck extends Bird {
// 子类构造函数重定向父类构造函数
Duck(String name, String wing) : super(name, wing);
}
class Point {
double x;
late final double y;
late double? z;
final double a;
Point(this.x, this.y, this.z, this.a);
// 命名构造函数,重定向构造函数
Point.zero() : this(0, 0, 0, 0);
static Point zero2() {
final zero = Point(0, 0, 0, 0);
return zero;
}
}
3.4初始化列表Initializer list
:
子类构造函数除了调用超类构造函数外,还可以在构造函数体运行之前初始化实例变量;用逗号分隔初始化式。
// 在调用构造函数前初始化实例变量
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
}
3.5常量构造函数Constant constructors
:
如果类生成的对象永远不会改变,那么可以将这些对象设置为编译时常量。为此,定义一个const构造函数,并确保所有实例变量都是final变量。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
3.6工厂构造函数Factory constructors
:
当实现不总是创建类的新实例的构造函数时,请使用factory
关键字。例如,工厂构造函数可以从缓存返回一个实例,也可以返回一个子类型的实例。工厂构造函数的另一个用例是使用无法在初始化列表中处理的final
修饰的变量;另一种解决方法是late final
修饰变量。工厂方法中无法使用this
关键字代表当前创建实例。
在下面的例子中,Logger工厂构造函数从缓存中返回对象,Logger. fromjson工厂构造函数从JSON对象中初始化final变量。
//
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
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);
3.7方法methods
:
Dart中,方法一般是指对象方法(或称实例方法),即对象能够调用的函数;方法是一种特殊的函数,即只能被对象调用的函数。
对象上的实例方法可以通过this
(可省略)访问对象的属性和方法,可以通过ClassType.
语法访问静态成员(包含静态变量、静态常量、静态函数等)。
class MethodPoint {
static const String className = "MethodPoint";
static String star = "123";
double x = 0;
double y = 0;
MethodPoint(this.x, this.y);
void show() {
print(MethodPoint.className);
print(MethodPoint.star);
}
double distanceTo(MethodPoint other) {
var dx = x - other.x; // 等价于:var dx = this.x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
3.8操作符方法:
操作符可以作为特殊名称的实例方法。Dart允许你用以下名称定义操作符方法:
< | + | | | >>> |
> | / | ^ | [] |
<= | ~/ | & | []= |
>= | * | << | ~ |
– | % | >> | == |
注意:您可能已经注意到一些操作符,比如!=,不在名称列表中。那是因为它们只是语法糖。例如,表达式e1 != e2是!(e1 == e2)的语法糖。
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);
}
// 调用
final v = Vector(2, 3);
final w = Vector(2, 2);
print(v + w == Vector(4, 5));
print(v - w == Vector(0, 1));
3.9获取getters
和设置方法setters
:
getter
和setter
是提供对对象属性的读写访问的特殊方法。每个实例变量都有一个隐式getter,如果合适的话还有一个setter。你可以通过实现getter和setter来创建额外的属性,使用get
和set
关键字:
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;
}
// 调用
var rect = Rectangle(3, 4, 20, 15);
print(rect.left == 3);
rect.right = 12;
print(rect.left == -8);
4.重写实例成员Overriding members
:
子类可以覆盖实例方法(包括操作符方法)、getter和setter。你可以使用@override
注释来表示你有意重写一个成员。
重写实例方法的几个原则:
- 返回类型必须与被重写方法的返回类型相同(或其子类型);
- 参数类型必须与被重写方法的参数类型相同(或其超类型);
- 如果重写的方法接受n个位置参数,那么重写的方法也必须接受n个位置参数;
- 泛型方法不能覆盖非泛型方法,非泛型方法也不能覆盖泛型方法;
- 重写
==
等价方法,必须要重写Object
的hashCode
的getter
方法。
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
5.noSuchMethod():
当对象访问不存在的方法或实例变量时,你可以重写noSuchMethod()来检测或做出反应。
未实现的方法一般情况下是不能调用的,以下是能够调用的情况:
- 接收器具有静态类型dynamic。
- 接收方有一个静态类型,它定义了未实现的方法(抽象就可以了),而接收方的动态类型有一个noSuchMethod()的实现,它与Object类中的实现不同。
有关更多信息,请参阅非正式的noSuchMethod转发规范。
class A {
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: '
'${invocation.memberName}');
}
}
6.枚举类型Enumerated types
:
枚举类型是一种特殊的类,用于表示固定数量的常量值。
枚举类型注意的点:
- 声明枚举的用
enum Type
语法; - 枚举中的每个值都有一个
index
的getter
方法,它返回枚举声明中值的从0开始的索引位置;例如,第一个值的索引为0,第二个值的索引为1; - 可以通过
Type.values
获取所有枚举值的列表; - 可以用
switch
语句枚举所有的值,如果你没有处理枚举的所有值,你会得到一个警告。
枚举的限制: - 枚举类型不可继承
extends
、混入mixins
和实现implement
; - 枚举无法实例化。
// 定义枚举类型
enum Color { red, green, blue }
// 获取枚举类枚举值的索引值
print(Color.red.index == 0);
print(Color.green.index == 1);
print(Color.blue.index == 2);
// 获取枚举类的所有枚举值
List<Color> colors = Color.values;
print(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: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
7.类成员(静态成员):
Dart中,静态成员static members
也称为类成员,包含静态变量和静态方法(或类变量和类方法)。
类成员只能通过Type.
语法访问。类方法不操作实例,因此不能访问实例成员;但是类方法可以访问类变量;类方法可以作为编译时常量作为参数传递给常量构造函数。类变量在被使用之前不会被初始化。
class Person {
static String className = 'Person';
late final String name;
late final int age;
void show() {
print(name);
}
static void showClassName() {
print(className);
}
}