现在位置: 首页 > Dart 教程 > 正文

Dart 继承与多态

继承是面向对象编程的三大特性之一,它允许一个类基于另一个类进行扩展。

本章介绍 Dart 中的 extends 单继承、方法重写、super 关键字和抽象类。


extends 单继承

Dart 只支持单继承——每个类只能有一个直接父类。

但继承链可以很长:子类继承父类,父类再继承祖父类,层层传递。

实例

// 父类(基类、超类)
class Animal {
  String name;

  Animal(this.name);

  void eat() {
    print('$name 在吃东西');
  }

  void sleep() {
    print('$name 在睡觉');
  }
}

// 子类:使用 extends 继承 Animal
class Dog extends Animal {
  String breed;  // 品种

  // 子类构造函数需要调用父类构造函数
  Dog(String name, this.breed) : super(name);

  // 子类新增的方法
  void bark() {
    print('$name($breed)在汪汪叫!');
  }
}

void main() {
  var dog = Dog('旺财', '金毛');

  // 调用从父类继承的方法
  dog.eat();
  dog.sleep();

  // 调用子类自己的方法
  dog.bark();

  // 子类对象也是父类类型
  Animal animal = dog;  // Dog 是 Animal,可以向上转型
  animal.eat();
}
旺财 在吃东西
旺财 在睡觉
旺财(金毛)在汪汪叫!
旺财 在吃东西

子类可以访问父类中所有非私有的成员变量和方法。

私有成员(以下划线开头)不会被继承。

Dart 中没有 public、protected、private 关键字。以下划线(_)开头的成员是库级私有(library-private),只能在同一文件中访问。这与 Java/C++ 的访问控制不同。


方法重写 @override

子类可以重写(override)父类的方法,提供自己的实现。

使用 @override 注解明确表示重写意图,编译器会帮你检查是否正确重写。

实例

class Animal {
  String name;
  Animal(this.name);

  // 父类的方法
  void makeSound() {
    print('$name 发出了一些声音');
  }

  @override
  String toString() {
    return '动物: $name';
  }
}

class Cat extends Animal {
  Cat(String name) : super(name);

  // @override 注解:告诉编译器我是在重写父类方法
  @override
  void makeSound() {
    print('$name 喵喵叫~');
  }
}

class Duck extends Animal {
  Duck(String name) : super(name);

  @override
  void makeSound() {
    print('$name 嘎嘎叫!');
  }
}

void main() {
  var cat = Cat('小花');
  var duck = Duck('唐老鸭');

  cat.makeSound();
  duck.makeSound();

  // 多态:同一个方法,不同对象有不同表现
  List<Animal> animals = [
    Cat('咪咪'),
    Duck('鸭鸭'),
    Cat('球球'),
  ];

  print('--- RUNOOB 动物园 ---');
  for (var animal in animals) {
    animal.makeSound();  // 运行时会调用实际类型的方法
  }
}
小花 喵喵叫~
唐老鸭 嘎嘎叫!
--- RUNOOB 动物园 ---
咪咪 喵喵叫~
鸭鸭 嘎嘎叫!
球球 喵喵叫~

上面这个例子展示了多态(Polymorphism):同一个方法调用 animal.makeSound(),实际执行的行为取决于对象的真实类型。

使用 @override 注解有两个好处:一是让代码意图更清晰,二是如果父类没有同名方法(比如拼写错误),编译器会报错提醒。建议始终加 @override。


super 关键字

super 代表父类对象,子类通过 super 来调用父类的构造函数、方法和属性。

实例

class Vehicle {
  String brand;
  int year;

  Vehicle(this.brand, this.year);

  void start() {
    print('$brand 车辆启动了');
  }

  void displayInfo() {
    print('品牌: $brand, 年份: $year');
  }
}

class ElectricCar extends Vehicle {
  int batteryLevel;

  ElectricCar(String brand, int year, this.batteryLevel)
      : super(brand, year);  // 调用父类构造函数

  @override
  void start() {
    // 在子类方法中调用父类的方法
    super.start();  // 先执行父类的启动逻辑
    print('电池电量: $batteryLevel%');
    print('电动机无声启动完成');
  }

  @override
  void displayInfo() {
    super.displayInfo();  // 先显示父类的基础信息
    print('电池电量: $batteryLevel%');  // 再补充子类的特有信息
  }
}

void main() {
  var car = ElectricCar('RUNOOB EV', 2026, 85);
  car.start();
  print('---');
  car.displayInfo();
}
RUNOOB EV 车辆启动了
电池电量: 85%
电动机无声启动完成
---
品牌: RUNOOB EV, 年份: 2026
电池电量: 85%

super 的使用场景总结

场景语法说明
调用父类构造函数: super(args)必须在初始化列表中调用
调用父类方法super.methodName()可在子类重写方法中调用
访问父类属性super.propertyName访问父类的成员变量

抽象类 abstract

抽象类是不能被直接实例化的类,它只定义接口规范,由子类实现具体逻辑。

实例

// abstract 关键字声明抽象类
abstract class Shape {
  // 抽象方法:没有方法体,子类必须实现
  double getArea();
  double getPerimeter();

  // 抽象类中也可以有具体方法
  void describe() {
    print('这是一个形状,面积: ${getArea()}, 周长: ${getPerimeter()}');
  }
}

class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double getArea() => width * height;

  @override
  double getPerimeter() => 2 * (width + height);
}

class Circle extends Shape {
  double radius;

  Circle(this.radius);

  @override
  double getArea() => 3.14159 * radius * radius;

  @override
  double getPerimeter() => 2 * 3.14159 * radius;
}

void main() {
  // Shape shape = Shape();  // 错误!抽象类不能实例化

  var rect = Rectangle(10, 5);
  var circle = Circle(7);

  rect.describe();
  circle.describe();

  // 抽象类作为类型使用
  List<Shape> shapes = [
    Rectangle(3, 4),
    Circle(5),
    Rectangle(6, 2),
  ];

  print('--- RUNOOB 形状统计 ---');
  double totalArea = 0;
  for (var shape in shapes) {
    totalArea += shape.getArea();
  }
  print('总面积: ${totalArea.toStringAsFixed(2)}');
}
这是一个形状,面积: 50.0, 周长: 30.0
这是一个形状,面积: 153.93791, 周长: 43.98226
--- RUNOOB 形状统计 ---
总面积: 108.54

抽象类的核心价值是"定义契约"。它告诉所有子类:你必须实现这些方法,否则编译不通过。这让团队协作更安全——实现者不会遗漏方法,调用者确信方法一定存在。