库可见性
libraries and visibility
理论
在Dart中,库是组织代码的基本单位。一个Dart程序由一个或多个库组成。库可见性机制主要控制着一个库中的成员(如类、函数、变量等)在其他库中是否可以被访问。
Dart的库可见性规则非常简洁,主要依赖于一个关键字: _(下划线)。
核心规则:
- 公共(Public)成员:
- 任何不以
_开头的顶级(Top-level)声明(变量、函数、类)和类成员(实例变量、方法)都是公共的。 - 公共成员可以在任何导入了该库的其他库中被访问。
- 这是Dart的默认可见性。
- 私有(Private)成员:
- 在同一个库文件内,任何以
_开头的顶级声明或类成员都是私有的。 - 关键点: 这里的“私有”是**库级别(Library-level)**的私有,而不是像Java/C++等语言那样的类级别私有。
- 这意味着,一个库文件中的私有成员,在同一个库文件内部可以被自由访问,但不能被其他导入了该库的库文件直接访问。
为什么需要库可见性?
库可见性是实现封装的重要手段之一。它有以下几个主要目的:
- 隐藏实现细节: 允许库的作者隐藏内部的实现细节,只暴露公共API。这样可以降低库的使用者理解和维护的复杂性。
- 防止误用: 限制对内部状态或方法的直接访问,避免使用者不小心破坏库的内部一致性。
- API稳定性: 库的内部实现可能会随着时间而改变,但只要公共API保持稳定,使用者就不需要修改他们的代码。私有成员可以随时重构,而不必担心影响外部使用者。
- 提高可维护性: 明确哪些是公共接口,哪些是内部实现,有助于团队协作和代码维护。
示例
下面通过一系列可运行的代码示例来演示库可见性。
SDK版本和运行环境要求: 所有示例均适用于 Dart SDK 2.x 及更高版本,可以在任何支持Dart的IDE或命令行中运行。
示例1: 公共成员与私有成员在同一库文件中
// my_library.dart
// 公共顶级变量
String publicMessage = "这是一个公共的顶级变量";
// 私有顶级函数
String _privateHelperFunction() {
return "这是一个私有的辅助函数,只能在 my_library.dart 中使用。";
}
// 公共类
class MyClass {
// 公共实例变量
String publicInstanceVar = "这是一个公共实例变量";
// 私有实例变量
String _privateInstanceVar = "这是一个私有实例变量";
// 公共方法
void publicMethod() {
print("MyClass: 公共方法被调用。");
// 在同一个库文件内,可以访问私有成员
print("MyClass: 访问私有实例变量: $_privateInstanceVar");
print("MyClass: 调用私有辅助函数: ${_privateHelperFunction()}");
}
// 私有方法
void _privateMethod() {
print("MyClass: 私有方法被调用。");
}
// 在公共方法中调用私有方法
void callPrivateMethod() {
_privateMethod(); // 在同一个库内可以调用私有方法
}
}
// 另一个文件中的公共类,用于演示库级别的私有性
class AnotherPublicClassInSameLibrary {
void demo() {
// 即使是不同的类,只要在同一个库文件内,也可以访问该库的顶级私有成员
print("AnotherPublicClassInSameLibrary: 访问私有辅助函数: ${_privateHelperFunction()}");
}
}
void main() {
// 在同一个库文件内,可以访问其所有成员,包括私有成员
print(publicMessage);
print(_privateHelperFunction());
var myObject = MyClass();
print(myObject.publicInstanceVar);
print(myObject._privateInstanceVar); // 可以在此处直接访问私有实例变量
myObject.publicMethod();
myObject._privateMethod(); // 可以在此处直接调用私有方法
AnotherPublicClassInSameLibrary().demo();
}
输出
这是一个公共的顶级变量
这是一个私有的辅助函数,只能在 my_library.dart 中使用。
这是一个公共实例变量
这是一个私有实例变量
MyClass: 公共方法被调用。
MyClass: 访问私有实例变量: 这是一个私有实例变量
MyClass: 调用私有辅助函数: 这是一个私有的辅助函数,只能在 my_library.dart 中使用。
MyClass: 私有方法被调用。
AnotherPublicClassInSameLibrary: 访问私有辅助函数: 这是一个私有的辅助函数,只能在 my_library.dart 中使用。
示例2: 跨库文件的可见性
// main.dart
// 导入 my_library.dart 库
// import 'my_library.dart'; // 假设my_library.dart和main.dart在同一目录下
// 如果 my_library.dart 在 lib 目录下,可以这样导入:
// import 'package:your_project_name/my_library.dart';
// 这里为了简化,假设它们在同一个文件夹下
import 'my_library.dart';
void main() {
print("--- 在 main.dart 中访问 my_library.dart ---");
// 访问公共顶级变量 (✔ 允许)
print("访问公共变量: $publicMessage");
// 尝试访问私有顶级函数 (❌ 编译错误)
// print(_privateHelperFunction()); // 编译错误:The function '_privateHelperFunction' isn't defined.
var myObject = MyClass();
// 访问公共实例变量 (✔ 允许)
print("访问公共实例变量: ${myObject.publicInstanceVar}");
myObject.publicMethod();
// 尝试访问私有实例变量 (❌ 编译错误)
// print(myObject._privateInstanceVar); // 编译错误:The setter '_privateInstanceVar' isn't defined for type 'MyClass'.
// 尝试调用私有方法 (❌ 编译错误)
// myObject._privateMethod(); // 编译错误:The method '_privateMethod' isn't defined for type 'MyClass'.
// 可以通过公共方法间接调用私有方法
myObject.callPrivateMethod();
}
运行代码会报错,因为尝试访问私有成员。预期编译错误信息类似:
Error compiling to kernel:
lib/main.dart:18:9: Error: Undefined name '_privateHelperFunction'.
print(_privateHelperFunction()); // 编译错误:The function '_privateHelperFunction' isn't defined.
^^^^^^^^^^^^^^^^^^^^
lib/main.dart:24:19: Error: The getter '_privateInstanceVar' isn't defined for the class 'MyClass'.
print(myObject._privateInstanceVar); // 编译错误:The setter '_privateInstanceVar' isn't defined for type 'MyClass'.
^^^^^^^^^^^^^^^^^
lib/main.dart:27:12: Error: The method '_privateMethod' isn't defined for the class 'MyClass'.
myObject._privateMethod(); // 编译错误:The method '_privateMethod' isn't defined for type 'MyClass'.
^^^^^^^^^^^^^
示例3: 限制导入只暴露特定成员(show和hide)
Dart提供了show和hide关键字,可以在导入库时进一步控制可见性,这并不是改变库本身的私有性,而是限制当前文件从导入库中可以看到有哪些东西。
// another_library.dart
String publicVar = "Another Library Public Var";
void publicFunction() {
print("Another Library Public Function");
}
class PublicClass {
void method() => print("Another Library Public Class Method");
}
// main_limited.dart
// 导入 another_library.dart,但只暴露 publicFunction 和 PublicClass
import 'another_library.dart' show publicFunction, PublicClass;
// 导入 my_library.dart,隐藏 publicMessage
// import 'my_library.dart' hide publicMessage; // 这里为了演示,我们先不使用 my_library
void main() {
print("--- 在 main_limited.dart 中使用 show/hide ---");
// 访问 publicFunction (✔ 允许, 因为 show 了它)
publicFunction();
// 访问 PublicClass (✔ 允许, 因为 show 了它)
var obj = PublicClass();
obj.method();
// 尝试访问 publicVar (❌ 编译错误, 因为没有 show 它)
// print(publicVar); // 编译错误:Undefined name 'publicVar'.
}
运行main_limited.dart会报错,预期编译错误信息:
Error compiling to kernel:
lib/main_limited.dart:21:9: Error: Undefined name 'publicVar'.
print(publicVar); // 编译错误, 因为没有 show 它
^^^^^^^^^
解析: show和hide关键字用于在导入时,对公共成员进行过滤。它们不影响库内私有成员的可见性。
- show: 只导入列出的成员。
- hide: 导入除了列出的成员之外的所有公共成员。
这在避免命名冲突或只引进所需部分时非常有用。
注意事项
- 库级别私有性:
- 注意: Dart的私有性是基于库(文件)而不是类。同一个Dart文件中的所有代码都属于同一个库,因此即使是不同的类,它们仍然可以互相访问彼此的以
_开头的“私有”成员。这是初学者常混淆的地方。 - 如果你想实现像Java/C++中严格的类级别私有(一个类的私有成员只能被该类本身访问),你需要自行设计API,例如提供getter/setter,或者将相关的操作封装在公共方法中。
_前缀的严格性:
- 注意: 只有在顶级声明(
Top-level declaration: 在任何类、函数之外,直接定义在文件中的变量、函数、类)或类成员(Class member: 类的实例变量、静态变量、实例方法、静态方法)上使用_前缀才代表私有。 - 局部变量(在函数或方法内部定义的变量)即使以
_开头,也仅仅是一个合法的变量名,没有任何私有化的含义。它们的可见性仅限于其作用域。
- 部分导入(
show和hide):
show和hide只影响从外部库导入的公共成员的可见性。它们不能用于使得私有成员在导入时变为可见,也不能用于隐藏库文件内部的私有成员。- 注意: 过度使用
show和hide可能会使代码理解起来变得复杂,尤其是在导入多个库时。通常,最佳实践是只暴露必要的公共API,减少对这些修饰符的依赖。
- Flutter中的应用:
- 在Flutter开发中,你会经常看到以
_开头的State类成员(如_counter、_buildColumn等)。这是为了明确这些成员是StatefulWidget内部实现的一部分,不应该被外部直接访问。