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 通过继承和实现接口,形成了不同的角色特性,体现了继承的复用性和接口的灵活性。
相关推荐
叽哥3 小时前
dart学习第 6 节:函数进阶 —— 高阶函数与闭包
flutter·dart
叽哥6 小时前
dart学习第 13 节:异步编程基础 —— Future 与 async/await
flutter·dart
xiaoyan20157 小时前
基于flutter3.32+window_manager仿macOS/Wins风格桌面os系统
前端·flutter·dart
叽哥7 小时前
dart学习第 11 节: 空安全(下)—— 安全操作符详解
flutter·dart
weixin_4111918417 小时前
原生安卓与flutter混编的实现
android·flutter
会煮咖啡的猫1 天前
编写 Flutter 游戏摇杆组件
flutter
来来走走1 天前
Flutter dart运算符
android·前端·flutter
风清云淡_A1 天前
【Flutter3.8x】flutter从入门到实战基础教程(五):Material Icons图标的使用
前端·flutter
阳光明媚sunny1 天前
Flutter基础知识
flutter