今天我们将深入面向对象的另外两个核心特性 ------继承 与多态,以及抽象类和接口的使用。这些特性能帮助我们构建更灵活、更具扩展性的代码结构。
一、继承(extends):代码复用的利器
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在此基础上添加新的属性和方法,或修改父类的方法。这极大地提高了代码的复用性。
1. 基本语法与使用
使用 extends 关键字实现继承:
            
            
              dart
              
              
            
          
          // 父类(基类)
class Animal {
  String name;
  int age;
  // 父类构造函数
  Animal(this.name, this.age);
  // 父类方法
  void eat() {
    print("$name 在吃东西");
  }
  void sleep() {
    print("$name 在睡觉");
  }
}
// 子类(派生类):继承自 Animal
class Dog extends Animal {
  // 子类新增属性
  String breed;
  // 子类构造函数:必须调用父类构造函数(通过 super)
  Dog(String name, int age, this.breed) : super(name, age);
  // 子类新增方法
  void bark() {
    print("$name($breed)在汪汪叫");
  }
}
void main() {
  // 创建子类对象
  Dog dog = Dog("旺财", 3, "金毛");
  // 调用继承自父类的属性和方法
  print("名字:${dog.name},年龄:${dog.age}"); // 输出:名字:旺财,年龄:3
  dog.eat(); // 输出:旺财 在吃东西
  dog.sleep(); // 输出:旺财 在睡觉
  // 调用子类自身的方法
  dog.bark(); // 输出:旺财(金毛)在汪汪叫
}
        在这个例子中:
Animal是父类,定义了所有动物的共同特征(name、age)和行为(eat、sleep)。Dog是子类,通过extends Animal继承了父类的属性和方法,同时新增了breed属性和bark方法。
2. 方法重写(@override):定制化父类行为
子类可以重写父类的方法,以实现自己的特有逻辑。使用 @override 注解标识重写的方法(可选,但推荐,提高代码可读性)。
            
            
              dart
              
              
            
          
          class Animal {
  String name;
  Animal(this.name);
  void makeSound() {
    print("$name 发出声音");
  }
}
class Cat extends Animal {
  Cat(String name) : super(name);
  // 重写父类的 makeSound 方法
  @override
  void makeSound() {
    print("$name 在喵喵叫"); // 覆盖父类的实现
  }
}
class Bird extends Animal {
  Bird(String name) : super(name);
  // 重写父类的 makeSound 方法
  @override
  void makeSound() {
    print("$name 在叽叽喳喳叫");
  }
}
void main() {
  Cat cat = Cat("咪咪");
  cat.makeSound(); // 输出:咪咪 在喵喵叫
  Bird bird = Bird("啾啾");
  bird.makeSound(); // 输出:啾啾 在叽叽喳喳叫
}
        方法重写的核心是:子类用自己的实现覆盖父类的方法,让相同的方法名表现出不同的行为 ------ 这正是多态的基础。
3. super 关键字:访问父类成员
super 关键字用于在子类中访问父类的属性、方法或构造函数:
super.方法名():调用父类的方法super.属性名:访问父类的属性(较少用,通常直接访问)- 构造函数中 
super(参数):调用父类的构造函数 
            
            
              dart
              
              
            
          
          class Parent {
  String name;
  Parent(this.name);
  void greet() {
    print("Hello, I'm $name");
  }
}
class Child extends Parent {
  Child(String name) : super(name); // 调用父类构造函数
  @override
  void greet() {
    super.greet(); // 调用父类的 greet 方法
    print("I'm a child"); // 新增子类逻辑
  }
}
void main() {
  Child child = Child("小明");
  child.greet();
  // 输出:
  // Hello, I'm 小明
  // I'm a child
}
        注意 :子类构造函数必须先调用父类构造函数(
super(...)必须放在初始化列表的第一位),确保父类成员先初始化。
二、多态:同一行为的不同表现
多态是指父类引用可以指向子类对象,调用方法时会表现出子类的具体实现。简单说就是 "一个接口,多种实现"。
            
            
              dart
              
              
            
          
          // 父类
class Shape {
  // 父类方法(多态的"接口")
  double getArea() {
    return 0.0;
  }
}
// 子类1:圆形
class Circle extends Shape {
  double radius;
  Circle(this.radius);
  @override
  double getArea() {
    return 3.14 * radius * radius; // 圆的面积公式
  }
}
// 子类2:矩形
class Rectangle extends Shape {
  double width;
  double height;
  Rectangle(this.width, this.height);
  @override
  double getArea() {
    return width * height; // 矩形的面积公式
  }
}
void main() {
  // 父类引用指向子类对象(多态的核心)
  Shape shape1 = Circle(5); // 圆的半径为5
  Shape shape2 = Rectangle(4, 6); // 矩形的宽4、高6
  // 调用相同的方法,表现出不同的行为
  print("圆的面积:${shape1.getArea()}"); // 输出:圆的面积:78.5
  print("矩形的面积:${shape2.getArea()}"); // 输出:矩形的面积:24.0
}
        多态的优势在于:可以编写通用的代码操作父类,而无需关心具体的子类类型 ,当新增子类(如三角形)时,通用代码无需修改,符合 "开闭原则"。
三、抽象类(abstract):定义规范的模板
抽象类是不能被实例化 的类,通常用于定义一组必须被子类实现的方法(抽象方法),自身可以包含已实现的方法。使用 abstract 关键字定义。
1. 基本用法
            
            
              dart
              
              
            
          
          // 抽象类
abstract class Vehicle {
  // 抽象方法(只有声明,没有实现,子类必须重写)
  void run();
  // 普通方法(有实现)
  void stop() {
    print("车辆停止");
  }
}
// 子类1:汽车
class Car extends Vehicle {
  @override
  void run() {
    print("汽车在公路上行驶");
  }
}
// 子类2:自行车
class Bicycle extends Vehicle {
  @override
  void run() {
    print("自行车在自行车道行驶");
  }
}
void main() {
  // 抽象类不能实例化(编译错误)
  // Vehicle v = Vehicle();
  Vehicle car = Car();
  car.run(); // 输出:汽车在公路上行驶
  car.stop(); // 输出:车辆停止
  Vehicle bike = Bicycle();
  bike.run(); // 输出:自行车在自行车道行驶
}
        抽象类的核心作用是定义 "规范" :通过抽象方法规定子类必须实现的功能,而普通方法提供通用逻辑,实现 "规范与实现分离"。
四、接口(implements):多维度的规范约束
Dart 中没有专门的 interface 关键字,任何类都可以作为接口 。通过 implements 关键字让一个类 "实现" 接口,此时该类必须重写接口中的所有方法和属性(无论是否抽象)。
接口与继承的区别:
- 继承(
extends):子类与父类是 "is-a" 关系(如 Dog 是 Animal),复用代码。 - 实现接口(
implements):类与接口是 "has-a" 关系(如 Bird 实现 Flyable),表示具备某种能力。 
1. 基本用法
            
            
              dart
              
              
            
          
          // 定义接口(普通类)
class Flyable {
  // 接口中的方法(隐式要求实现类重写)
  void fly() {
    // 接口的实现通常为空,仅作为规范
  }
}
class Swimmable {
  void swim() {}
}
// 实现一个接口
class Bird implements Flyable {
  @override
  void fly() {
    print("鸟在天上飞");
  }
}
// 实现多个接口(用逗号分隔)
class Duck implements Flyable, Swimmable {
  @override
  void fly() {
    print("鸭子在低空飞");
  }
  @override
  void swim() {
    print("鸭子在水里游");
  }
}
void main() {
  Bird bird = Bird();
  bird.fly(); // 输出:鸟在天上飞
  Duck duck = Duck();
  duck.fly(); // 输出:鸭子在低空飞
  duck.swim(); // 输出:鸭子在水里游
}
        2. 用抽象类作为接口
更推荐用抽象类作为接口,因为抽象方法更明确地表示 "必须实现":
            
            
              dart
              
              
            
          
          // 抽象类作为接口
abstract class Runnable {
  void run(); // 抽象方法,无实现
}
class Person implements Runnable {
  @override
  void run() {
    print("人在跑步");
  }
}
class Robot implements Runnable {
  @override
  void run() {
    print("机器人在移动");
  }
}
        五、继承与接口的综合案例
我们用一个 "游戏角色" 案例综合运用继承和接口:
            
            
              dart
              
              
            
          
          // 父类:角色
class Role {
  String name;
  int hp;
  Role(this.name, this.hp);
  void takeDamage(int damage) {
    hp -= damage;
    print("$name 受到 $damage 点伤害,剩余血量:$hp");
  }
}
// 接口:攻击能力
abstract class Attackable {
  void attack(Role target);
}
// 接口:治疗能力
abstract class Healable {
  void heal(Role target, int amount);
}
// 战士:继承 Role,实现 Attackable
class Warrior extends Role implements Attackable {
  Warrior(String name, int hp) : super(name, hp);
  @override
  void attack(Role target) {
    print("$name 用剑攻击了 ${target.name}");
    target.takeDamage(20);
  }
}
// 牧师:继承 Role,实现 Attackable 和 Healable
class Priest extends Role implements Attackable, Healable {
  Priest(String name, int hp) : super(name, hp);
  @override
  void attack(Role target) {
    print("$name 用魔杖攻击了 ${target.name}");
    target.takeDamage(10);
  }
  @override
  void heal(Role target, int amount) {
    target.hp += amount;
    print("$name 治疗了 ${target.name},恢复 $amount 点血量,当前血量:${target.hp}");
  }
}
void main() {
  Warrior warrior = Warrior("战士", 100);
  Priest priest = Priest("牧师", 80);
  // 战士攻击牧师
  warrior.attack(priest); // 输出:战士 用剑攻击了 牧师;牧师 受到 20 点伤害,剩余血量:60
  // 牧师治疗自己
  priest.heal(priest, 15); // 输出:牧师 治疗了 牧师,恢复 15 点血量,当前血量:75
  // 牧师攻击战士
  priest.attack(warrior); // 输出:牧师 用魔杖攻击了 战士;战士 受到 10 点伤害,剩余血量:90
}
        这个案例中:
Role作为父类,封装了所有角色的共同属性(name、hp)和方法(takeDamage)。Attackable和Healable作为接口,定义了 "攻击" 和 "治疗" 的能力规范。Warrior和Priest通过继承和实现接口,形成了不同的角色特性,体现了继承的复用性和接口的灵活性。