dart学习第 8 节:面向对象(下)—— 继承与多态

今天我们将深入面向对象的另外两个核心特性 ------继承多态,以及抽象类和接口的使用。这些特性能帮助我们构建更灵活、更具扩展性的代码结构。

一、继承(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 是父类,定义了所有动物的共同特征(nameage)和行为(eatsleep)。
  • 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 作为父类,封装了所有角色的共同属性(namehp)和方法(takeDamage)。
  • AttackableHealable 作为接口,定义了 "攻击" 和 "治疗" 的能力规范。
  • WarriorPriest 通过继承和实现接口,形成了不同的角色特性,体现了继承的复用性和接口的灵活性。
相关推荐
火柴就是我16 小时前
flutter 之真手势冲突处理
android·flutter
Speed12317 小时前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间17 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭17 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone18 小时前
从flutter源码看其渲染机制
android·flutter
ALLIN2 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei2 天前
Flutter 国际化
flutter
Dabei2 天前
Flutter MQTT 通信文档
flutter
Dabei2 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉2 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter