方法
方法是对象提供行为的函数。
Getter 和 Setter
Getter 和 Setter 是一对用来读写对象属性的特殊方法,上面说过实例对象的每一个属性都有一个隐式的 Getter 方法,如果为非 final 属性的话还会有一个 Setter 方法,你可以使用 get 和 set 关键字为额外的属性添加 Getter 和 Setter 方法:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算产生的属性:right 和 bottom。
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
使用 Getter 和 Setter 的好处是,你可以先使用你的实例变量,过一段时间过再将它们包裹成方法且不需要改动任何代码,即先定义后更改且不影响原有逻辑。
备忘📝:
像自增(++)这样的操作符不管是否定义了 Getter 方法都会正确地执行。为了避免一些不必要的异常情况,运算符只会调用 Getter 一次,然后将其值存储在一个临时变量中。
抽象方法
实例方法、Getter 方法以及 Setter 方法都可以是抽象的,定义一个接口方法而不去做具体的实现让实现它的类去实现该方法,抽象方法只能存在于抽象类中,抽象类的定义跟Java的抽象类类似,就不单独介绍了。
abstract class Doer {
// 定义实例变量和方法等等……
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供一个实现,所以在这里该方法不再是抽象的……
}
}
接口
Dart 没有像 Java 用单独的关键字 interface 来定义接口,普通用 class 声明的类就可以是接口,可以通过关键字 implements
来实现一个或多个接口并实现每个接口定义的 API:
// A person. The implicit interface contains greet().
// Person 类的隐式接口中包含 greet() 方法。
class Person {
// _name 变量同样包含在接口中,但它只是库内可见的。
final _name;
// 构造函数不在接口中。
Person(this._name);
// greet() 方法在接口中。
String greet(String who) => '你好,$who。我是$_name。';
}
// Person 接口的一个实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => '你好$who。你知道我是谁吗?';
}
String greetBob(Person person) => person.greet('小芳');
void main() {
print(greetBob(Person('小芸')));
print(greetBob(Impostor()));
}
这时疑问来了,接口跟继承有什么区别,不就是多继承吗?
接口的实现则意味着,子类获取到的仅仅是接口的成员变量符号和方法符号,需要重新实现成员变量,以及方法的声明和初始化,否则编译器会报错。而继承可以选择不重新实现,这是最大的区别。
看以下的例子:
class Point {
num x = 0, y = 0;
void printInfo() => print('($x,$y)');
}
//Vector继承自Point
class Vector extends Point{
num z = 0;
@override
void printInfo() => print('($x,$y,$z)'); //覆写了printInfo实现
}
//Coordinate是对Point的接口实现
class Coordinate implements Point {
num x = 0, y = 0; //成员变量需要重新声明
void printInfo() => print('($x,$y)'); //成员函数需要重新声明实现
}
var xxx = Vector();
xxx
..x = 1
..y = 2
..z = 3; //级联运算符,等同于xxx.x=1; xxx.y=2;xxx.z=3;
xxx.printInfo(); //输出(1,2,3)
var yyy = Coordinate();
yyy
..x = 1
..y = 2; //级联运算符,等同于yyy.x=1; yyy.y=2;
yyy.printInfo(); //输出(1,2)
print (yyy is Point); //true
print(yyy is Coordinate); //true
可以看出,子类 Coordinate 采用接口实现的方式,仅仅是获取到了父类 Point 的一个“空壳子”,只能从语义层面当成接口 Point 来用,但并不能复用 Point 的原有实现。
继承
使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类,这跟在其他编程语言的使用类似,就不细说了,主要强调下重写操作符。
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);
// 运算符 == 和 hashCode 的实现未在这里展示,详情请查看下方说明。
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
如果重写 == 操作符,必须也同时重写对象 hashCode 的 Getter 方法。
noSuchMethod()
如果调用了对象上不存在的方法或实例变量将会触发 noSuchMethod 方法,你可以重写 noSuchMethod 方法来追踪和记录这一行为:
class A {
// 除非你重写 noSuchMethod,否则调用一个不存在的成员会导致 NoSuchMethodError。
@override
void noSuchMethod(Invocation invocation) {
print('你尝试使用一个不存在的成员:' +
'${invocation.memberName}');
}
}
你不能调用一个未实现的方法除非下面其中的一个条件成立:
- 接收方是静态的 dynamic 类型。
- 接收方具有静态类型,定义了未实现的方法(抽象亦可),并且接收方的动态类型实现了 noSuchMethod 方法且具体的实现与 Object 中的不同。
网友评论