封装与可见性

封装、继承与多态是面向对象编程(Object-oritented programming, OOP)的三大特性,而封装是其中最为基础与关键的特性。Go语言向我们证明了继承对于OOP是可选的,而多态通过接口来实现。这些基本概念将在后续章节陆续介绍,本章节重点说明封装。

封装

很多时候我们想要隐藏代码的实现细节,而对外暴露的只是API(应用编程接口),这便是针对接口(而非实现)编程,要实现这一点就要用到封装。接口用于定义API,而类就像一个容器主要用于封装。那么类究竟封装了什么?

// ch0401_1.dart
class Dog {
  const Dog(this.name, this.age); // 3
  final String name; // 1
  final int age; // 1a

  void say() {  //2
    print('$name: Woof woof');
  }

  void eat() { //2a
    print("$name: I'd like to have some bones");
  }
}
  1. 此例中Dog类封装了其属性(即字段 nameage),以及
  2. 行为,即方法 say()eat();方法(method)即在类中定义的函数;
  3. 这是Dog的构造函数。

类的实例化与成员

下面这行代码使用构造函数,创建(实例化)了Dog类的一个实例(instance):

  var dog = Dog('Spots', 6);

我们说dogDog类的一个实例或对象。

NOTE: 类是对象的模板,对象是类的实例。

类的成员包括实例变量、实例方法、静态变量、静态方法。

Dog类中的nameage是实例变量,像这样引用:dog.name

Dog类中的say()eat()是实例方法,像下面这样调用:

  dog.say();

实例变量与实例方法是与类的实例关联(绑定)的,而静态(static)变量与静态方法则与类关联,例如:

class Dog {
  // .... (omit for brevity)
  static const ponyo = Dog('Ponyo', 5);
  static void ponyoSay() => ponyo.say();
}  

可见性

Dart 没有使用 public, protectedprivate 关键字来表达成员的可见性,而是使用特殊的标记即下划线(_)来表达private(私有)的概念。

class Dog {
  // .... (omit for brevity)
  final _secret = "I won't tell you this";
  void _tellSecret() => print(_secret);
}  

_secret字段与_tellSecret() 方法对于外部文件是不可见的,换言之,它只能在当前文件(定义它的文件)中使用。

Libraries(库)

Dart里的每个.dart文件(加上它的子文件,即parts)都是一个libary。使用import关键字导入libary中的代码。这种机制使得代码可以被模块化以及可以被复用。

file lib/hellodart.dart:

// lib/hellodart.dart
int calculate() { // 1
  return 6 * 7;
}

file bin/hellodart.dart:

// bin/hellodart.dart
import 'package:hellodart/hellodart.dart' as hellodart; // 2

// ch0101
void main(List<String> arguments) {
  print('Hello world: ${hellodart.calculate()}!'); // 3
}
  1. lib/hellodart.dart 中定义函数calculate();
  2. 这行代码使用关键字 import 导入 lib/hellodart.dart ,并使用 as 关键字给此library取名hellodart,将其中的标识符置于hellodart命名空间里;
  3. hellodart.calculate()表示调用hellodart library 中的calculate()函数。

NOTE:

  1. 第2行import一句中的 package:hellodart 表示 hellodart这个package(包),package的名称与 pubspec.yaml 文件中 name: hellodart 是一致的;回想一下,我们在第1章Hello Dart: 搭建开发环境一节中创建了helloword 这个package。
  2. 如果第2行代码不使用 as 关键字给 hellodart.dart 取名,表示将此libary导入全局命名空间,就直接使用calculate()来调用该函数。

我们在 lib/hellodart.dart 中定义另一个函数:

int _calculate() => 7 * 8;

然后在其它文件中(如 bin/hellodart.dart)调用它,将引起编译错误:

bin/hellodart.dart:6:35: Error: Method not found: '_calculate'.
  print('Hello world: ${hellodart._calculate()}!'); // 3
                                  ^^^^^^^^^^