前言
面向对象编程(OOP) 作为编程范式的重要里程碑,彻底改变了我们构建软件的方式。它引入对象的概念,强调以对象为核心,以一种更接近现实世界的方式建模问题域,通过封装、继承、多态等特性,实现了代码的模块化、复用性和灵活性的显著提升。接下来让我们一起在Dart世界中探索OOP的奥秘。
注: 对象是数据和作用于数据的操作的封装体。
一、面向对象编程概述
面向对象编程(OOP)是相对于面向过程来说的,是一种编程范式。它引入了对象的概念,以一种更接近现实世界的方式建模问题域。其核心思想在于封装、继承、多态和抽象。
记忆方法: 一二三四五 二十三
一个类:类的相关概念。
两个世界:现实世界、计算机世界。
三大特性:封装、继承、多态。
四大支柱:封装、继承、多态、抽象。
五大原则:单一职责原则、开放封闭原则、接口隔离原则、依赖倒置原则、里式替换原则。
二十三种设计模式(后续文章介绍)。
二、类
类:创建对象的模版或蓝图。
类实例生成(由构造函数完成)对象,对象抽象为类。
注意: 在设计与实现时,我们首先接触的不是对象而是类和类层次结构。
2.1、类的定义
由class关键字+类名+类体(大括号{})定义。
- 使用
class关键字进行定义。 - 类名:类的唯一标识,遵循标识符命名规则,命名约束一般使用帕斯卡命名法(单词首字母大写)。
- 类体(大括号{}):包含属性、方法、构造函数,同时限定作用域。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name; // 属性
Animal(this.name); // 构造函数
void roar(){ // 方法
print('${this.name} 会叫');
}
}
2.2、继承与类层次结构
继承是子类与父类共享属性与方法的一种构造。一般是用类来体现继承关系。如下图中子类(鸟类、鱼类) 继承自父类(动物类)。
子类与父类的继承关系构成了类层次结构。
当执行子类的实例生成方法时,其执行顺序为:
- 首先在类层次结构中的子类沿继承路径上溯至它的一个基类,然后自顶向下执行该子类所有父类的实例生成方法。
- 当子类的实例消除时,执行顺序恰好相反。
2.3、重置
重置或覆盖:子类重新定义父类中继承过来的方法。其基本思想是动态绑定的支持。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name; // 属性
Animal(this.name); // 构造函数
void skill(){ // 方法
print('${this.name} 会叫!');
}
}
/// 定义一个Bird类继承自Animal类
class Bird extends Animal{
Bird(super.name); // 子类继承父类时,必须实例化父类
@override
void skill(){ // 重置了父类中的skil()方法
print('${this.name} 会飞!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉');
parrot_a.skill();
Bird parrot_b = Bird('鹦鹉');
parrot_b.skill();
}
// 输出:
鹦鹉 会叫!
鹦鹉 会飞!
2.4、setter与getter
Dart 支持显式定义访问或修改私有属性。
- setter:显式修改私有属性。
- getter:显式访问私有属性。
- 注意:Dart中私有属性针对于不同Dart文件来说。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String _name; // 属性
Animal(this._name); // 构造函数
// Getter
String get name => _name;
// Setter
set name(String value) {
if (value.isNotEmpty) {
_name = value;
} else {
throw ArgumentError('Name cannot be empty');
}
}
void skill(){ // 方法
print('${this._name} 会叫!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉');
parrot_a.skill();
parrot_a.name = '喜鹊'; // 修改私有属性
parrot_a.skill();
}
// 输出:
鹦鹉 会叫!
喜鹊 会叫!
2.5、对象自身引用
对象自身引用是编程语言中的一种特有的结构。Dart中使用this关键字。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name); // this.name对象自身引用
void skill(){
print('${this.name}会叫!'); // this.name对象自身引用
}
}
void main() {
Animal parrot_a = Animal('鹦鹉');
parrot_a.skill();
}
// 输出:
鹦鹉会叫!
三、对象
对象是数据和作用于数据的操作的封装体。
3.1、类的实例化
对象是类的实例化。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name);
void skill(){
print('${this.name}会叫!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉'); // 实例化Animal类
parrot_a.skill(); // parrot_a 为实例化的对象。
}
// 输出:
鹦鹉会叫!
3.2、消息传递
对象之间进行通信的构造。通常包含接收者,接收者方法,接收者参数、返回值(可选)。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name);
void skill(){
print('${this.name}会叫!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉'); // 实例化Animal类
parrot_a.skill(); // 消息传递,向parrot_a 对象发送消息
}
// 输出:
鹦鹉会叫!
四、构造函数
4.1、默认构造函数
构造函数名与类名相同。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name); // 构造函数。函数名与类名相同。
}
4.1、命名构造函数
Dart中允许有多个构造函数,并且可以给构造函数起一个别名。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name); // 构造函数。函数名与类名相同。
Animal.getname(this.name); // 命名构造函数,getname为重新命的名。
void skill(){
print('$name会叫');
}
}
void main(){
Animal parrot_a = Animal('鹦鹉');
parrot_a.skill(); // 输出:鹦鹉会叫
Animal parrot_b = Animal.getname('鹦鹉');
parrot_b.skill(); // 输出:鹦鹉会叫
}
在Dart中可以在构造函数后加冒号(:)表示为初始化列表,初始化列表内容可以是表达式或方法调用。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name; // 属性
double wingspan;
double height;
Animal(this.name, this.wingspan, this.height); // 默认构造函数
Animal.getinfo(this.name,this.wingspan):this.height=wingspan + 1; // 命名构造函数
void info(){
print('${this.name}高:${this.height},翅膀长:${this.wingspan}');
}
}
void main(){
Animal parrot_a = Animal('鹦鹉',10,11);
parrot_a.info(); // 输出:鹦鹉高:11.0,翅膀长:10.0
Animal parrot_b = Animal.getinfo('鹦鹉',10);
parrot_b.info(); // 输出:鹦鹉高:11.0,翅膀长:10.0
}
继承时子类调用父类的构造函数。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name);
void skill(){
print('${this.name} 会叫!');
}
}
/// 定义一个Bird类继承自Animal类
class Bird extends Animal{
Bird(super.name); // 调用父类的构造函数
@override
void skill(){
print('${this.name} 会飞!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉');
parrot_a.skill();
Bird parrot_b = Bird('鹦鹉');
parrot_b.skill();
}
// 输出:
鹦鹉 会叫!
鹦鹉 会飞!
4.2、工厂构造函数
工厂构造函数用于返回现有的实例或不同类型的对象,而不必每次都创建新的实例。
4.2.1、工厂构造函数返回现有的实例
使用 factory 关键字
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
static Map<String,Animal> _cahe = <String, Animal>{};
final String name;
Animal._factory(this.name); // 只能使用命名构造函数
factory Animal(String name){ // 工厂构造函数固定写法,函数名与类名相同
if (_cahe.containsKey(name)){
return _cahe[name]!; // 必须断言其不为空,否则编译器报错
}else{
final animal = Animal._factory(name);
_cahe[name] = animal;
return animal;
}
}
void skill(){
print('${this.name} 会叫!');
}
}
void main() {
Animal parrot_a = Animal('鹦鹉');
Animal parrot_b = Animal('鹦鹉');
print(parrot_a == parrot_b); // 输出:true
}
4.2.2、工厂构造函数返回不同类型的对象
通过工厂构造函数可以简洁的返回不同类型的对象。
示例:
/// 使用class关键字定义一个类名为Animal的类。
class Animal{
String name;
Animal(this.name);
void roar(){
print('${this.name} 会叫!');
}
}
/// 定义一个Bird类继承自Animal类
class Bird extends Animal{
Bird(super.name);
@override
void roar(){
print('${this.name}叽叽喳喳!');
}
}
/// 定义一个Dog类继承自Animal类
class Dog extends Animal{
Dog(super.name);
@override
void roar(){
print('${this.name}汪汪!');
}
}
/// 工厂模式的类
class AnimalFactory{
// 静态工厂方法
static Animal createFactory(String name){ // Animal为返回的函数类型
switch(name){
case '小狗':
return Dog(name);
case '小鸟':
return Bird(name);
default:
throw ArgumentError('Unknown Animal name: $name');
}
}
}
void main() {
Animal bird = AnimalFactory.createFactory('小鸟');
bird.roar(); // 输出:小鸟叽叽喳喳!
Animal dog = AnimalFactory.createFactory('小狗');
dog.roar(); // 输出:小狗汪汪!
}
四、总结
本小节聚焦于面向对象方法的应用,首先阐述了面向对象编程的核心理念,随后详细展示了在Dart语言中如何定义类与对象的过程,并最终详细介绍了类定义中不可或缺的构造函数部分。