Dart基本语法3 <类、继承、重写、泛型、抽象类、实现>

发布于 16 天前  31 次阅读


Dart 类与面向对象

1. 类的基本写法

类 = 属性(数据)+ 构造函数 + 方法(行为)

class Person {
  // ========== 属性(Fields)==========
  String name;    // 姓名
  int age;        // 年龄

  // ========== 构造函数 ==========
  Person(this.name, this.age);

  // ========== 方法(Functions)==========
  void introduce() {
    print('我叫 name,今年age 岁');
  }

  bool isAdult() {
    return age >= 18;
  }
}

// 使用类
void main() {
  var person = Person('张三', 25);
  person.introduce();  // 我叫 张三,今年 25 岁
  print(person.isAdult());  // true
}

2. this 关键字详解

this 的核心作用:指向当前对象

可以把 this 理解为"我自己",它指向调用该方法的对象。

场景一:this 用于区分属性和局部变量

class Dog {
  String name;    // 属性
  int age;         // 属性

  // 参数名和属性名相同,需要用 this 区分
  Dog(this.name, this.age);

  void speak() {
    String name = '局部变量';  // 局部变量
    print('我叫 {this.name}');  // this.name 指向属性
    print('我叫name');        // 指向局部变量
  }
}

void main() {
  var dog = Dog('旺财', 3);
  dog.speak();
  // 输出:
  // 我叫 旺财
  // 我叫 局部变量
}

场景二:this 用于构造函数参数传值

class Person {
  String name;
  int age;
  String city;

  // 语法糖:this.属性名 直接接收参数并赋值给属性
  Person(this.name, this.age);  // city 没传,默认为 null

  // 等价于:
  // Person(String name, int age) {
  //   this.name = name;
  //   this.age = age;
  // }
}

场景三:this 用于方法链式调用

class Builder {
  String name = '';
  int age = 0;

  Builder setName(String name) {
    this.name = name;
    return this;  // 返回自己,实现链式调用
  }

  Builder setAge(int age) {
    this.age = age;
    return this;
  }
}

void main() {
  var builder = Builder()
    ..setName('张三')   // 级联运算符
    ..setAge(25);

  // 等价于链式调用:
  var builder2 = Builder()
    .setName('李四')
    .setAge(30);
}

场景四:继承中的 this

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

class Dog extends Animal {
  int age;

  // super 用于调用父类构造函数
  // this 用于初始化当前类的属性
  Dog(String name, this.age) : super(name);
}

void main() {
  var dog = Dog('旺财', 3);
  print(dog.name);  // 旺财
  print(dog.age);   // 3
}

this 快速记忆

场景 作用
this.属性名 区分同名属性和局部变量
this.name 在构造函数参数 语法糖,自动赋值属性
return this 返回当前对象,实现链式调用
super.name 调用父类构造函数

3. 类的继承

基本语法

// 父类
class Animal {
  String name;

  Animal(this.name);

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

// 子类 - 使用 extends 继承
class Dog extends Animal {
  String breed;  // 新增属性

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

  void bark() {
    print('name 汪汪叫');
  }
}

void main() {
  var dog = Dog('旺财', '金毛');
  dog.eat();   // 继承自父类的方法
  dog.bark();  // 子类自己的方法
}

继承的规则

  1. 单继承:Dart 只支持单继承,一个类只能继承一个父类
  2. 继承属性和方法:子类自动获得父类的属性和方法
  3. 可新增或重写:子类可以添加新属性/方法,也可以重写父类方法

4. 方法重写(Override)

什么是方法重写?

子类重新定义父类中已有的方法,叫方法重写。

重写规则

  1. 使用 @override 注解(建议添加,代码更清晰)
  2. 方法签名(参数列表)必须与父类一致
  3. 方法体可以自定义

示例

class Animal {
  String name;

  Animal(this.name);

  void sound() {
    print('name 发出声音');
  }
}

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

  @override
  void sound() {  // 重写父类的 sound 方法
    print('name 喵喵叫');
  }

  void climb() {
    print('name 在爬树');
  }
}

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

  @override
  void sound() {  // 重写父类的 sound 方法
    print('name 汪汪叫');
  }
}

void main() {
  var cat = Cat('咪咪');
  var dog = Dog('旺财');

  cat.sound();  // 咪咪 喵喵叫(调用重写后的方法)
  dog.sound();  // 旺财 汪汪叫(调用重写后的方法)
}

重写 vs 重载(区别)

概念 说明 Dart 支持
重写(Override) 子类重新定义父类方法 ✅ 支持
重载(Overload) 同名方法不同参数 ❌ 不支持

5. 泛型(Generics)详解

为什么要用泛型?

问题:如果有多种类型需要存储,每种都写一个类太麻烦了

// ❌ 没有泛型 - 需要写多个类
class IntBox {
  int? value;
}

class StringBox {
  String? value;
}

class DoubleBox {
  double? value;
}

// ✅ 有泛型 - 一个类搞定所有类型
class Box<T> {  // T 是类型参数,可以是任何类型
  T? value;
}

void main() {
  var intBox = Box<int>();
  var stringBox = Box<String>();
  var doubleBox = Box<double>();

  intBox.value = 42;
  stringBox.value = 'Hello';
  doubleBox.value = 3.14;

  print(intBox.value);      // 42
  print(stringBox.value);   // Hello
  print(doubleBox.value);   // 3.14
}

泛型的本质

泛型 = "类型参数"
用一个占位符(T)代替具体类型,使用时再指定具体类型

泛型语法

基本语法

class 类名<类型参数> {
  类型参数 属性名;
}

// 常见类型参数名:T、E、K、V、R(只是一种约定)

常见命名约定

命名 含义 示例
T Type(类型) List<T>
E Element(元素) Set<E>
K Key(键) Map<K, V>
V Value(值) Map<K, V>
R Return(返回值) R Function()

泛型用法示例

泛型类

class Stack<T> {
  final List<T> _items = [];

  void push(T item) => _items.add(item);
  T pop() => _items.removeLast();
}

// 使用
var intStack = Stack<int>();
var stringStack = Stack<String>();

泛型方法

T first<T>(List<T> list) {
  return list[0];
}

// 使用
first<int>([1, 2, 3]);      // 返回 1
first<String>(['a', 'b']); // 返回 'a'

泛型接口

abstract class Repository<T> {
  Future<T> get(int id);
  Future<List<T>> getAll();
}

class UserRepository implements Repository<User> {
  @override
  Future<User> get(int id) async {
    // 实现
  }

  @override
  Future<List<User>> getAll() async {
    // 实现
  }
}

多类型参数

class Pair<K, V> {
  K first;
  V second;

  Pair(this.first, this.second);
}

// 使用
var pair = Pair<String, int>('年龄', 25);

泛型约束

限制泛型必须是某个类型的子类:

class Printable<T extends Comparable> {
  void print(T value) {
    print(value.toString());
  }
}

// 使用
var p = Printable<int>();  // ✅ int 实现了 Comparable
// var p = Printable<String>();  // ❌ String 也实现了 Comparable,所以也可以

泛型的好处

好处 说明
代码复用 一个类可以处理多种类型
类型安全 编译时检查类型错误
性能优化 不需要运行时类型检查

6. 综合示例:完整类结构

// 泛型父类
abstract class Storage<T> {
  void save(T item);
  T? get(int id);
  List<T> getAll();
}

// 子类继承 + 重写 + 泛型
class UserStorage implements Storage<User> {
  final List<User> _users = [];

  @override
  void save(User user) {
    _users.add(user);
  }

  @override
  User? get(int id) {
    return id < _users.length ? _users[id] : null;
  }

  @override
  List<User> getAll() {
    return List.from(_users);
  }
}

class User {
  String name;
  int age;

  User(this.name, this.age);

  @override
  String toString() => 'User(name,age)';
}

void main() {
  var storage = UserStorage();

  storage.save(User('张三', 25));
  storage.save(User('李四', 30));

  print(storage.get(0));      // User(张三, 25)
  print(storage.getAll());     // [User(张三, 25), User(李四, 30)]
}

7. 快速记忆

🎯 this 的作用:指向当前对象,区分属性与局部变量,实现链式调用

🎯 继承extends 继承父类,super() 调用父类构造函数

🎯 方法重写@override 注解 + 签名一致 + 自定义方法体

🎯 泛型:用 <T> 表示"类型参数",<T> 写在使用时替换为具体类型


8. abstract(抽象类)

什么是抽象类?

abstract 修饰的类不能直接实例化,只能被继承或实现。

为什么需要抽象类?

问题:有时候我们需要一个"模板"或"规范",规定子类必须有什么方法,但不关心具体怎么实现。

// ❌ 普通类:方法必须实现
class Animal {
  void sound() {
    // 父类不知道具体怎么叫,不知道子类想怎么叫
  }
}

// ✅ 抽象类:定义规范,子类必须实现
abstract class Animal {
  String name;

  Animal(this.name);

  // 抽象方法:只有声明,没有方法体
  // 子类必须重写这个方法
  void sound();
}

抽象类 vs 普通类

特性 抽象类 普通类
实例化 ❌ 不能直接创建对象 ✅ 可以
抽象方法 可以有,子类必须实现 不能有
普通方法 可以有,子类直接继承使用 必须有实现
使用场景 定义"规范/模板" 直接创建对象使用

示例对比

// 抽象类 - 定义规范
abstract class Shape {
  // 抽象方法 - 子类必须实现
  double area();

  // 普通方法 - 子类可以直接使用
  void printInfo() {
    print('面积: ${area()}');
  }
}

class Circle extends Shape {
  double radius;

  Circle(this.radius);

  @override
  double area() {
    return 3.14 * radius * radius;
  }
}

class Square extends Shape {
  double side;

  Square(this.side);

  @override
  double area() {
    return side * side;
  }
}

void main() {
  var circle = Circle(5);
  var square = Square(4);

  circle.printInfo();  // 面积: 78.5
  square.printInfo();  // 面积: 16

  // var shape = Shape();  // ❌ 错误!不能实例化抽象类
}

9. implements(实现接口)

什么是 implements?

implements 用于实现一个抽象类或接口,强制当前类提供某些方法的具体实现。

extends vs implements 对比

关键字 含义 特点
extends 继承 获得父类功能,可选重写,可用 super
implements 实现 必须重写所有抽象方法

用 extends 继承

class Animal {
  void eat() => print('吃东西');
}

class Dog extends Animal {
  void bark() => print('汪汪叫');
}

void main() {
  var dog = Dog();
  dog.eat();   // 继承来的方法
  dog.bark();  // 子类自己的方法
  // Dog 自动有 eat 方法,可以直接用
}

用 implements 实现

abstract class Flyable {
  void fly();
}

class Duck implements Flyable {
  @override
  void fly() {
    print('鸭子扑腾翅膀');
  }
}

class Plane implements Flyable {
  @override
  void fly() {
    print('飞机起飞');
  }
}

void main() {
  var duck = Duck();
  var plane = Plane();

  duck.fly();   // 鸭子扑腾翅膀
  plane.fly();  // 飞机起飞

  // 必须重写 fly 方法,否则编译错误
}

核心区别:继承 vs 实现

// ========== extends(继承父类)==========
class Parent {
  void greet() => print('你好');
}

class Child extends Parent {
  @override
  void greet() => print('嗨');
}

void main() {
  var child = Child();
  child.greet();  // 嗨
  // Child 自动有 greet 方法,可以直接使用
  // 可以选择重写,也可以不重写
}

// ========== implements(实现接口)==========
abstract class Greeter {
  void greet();  // 抽象方法,没有实现
}

class FriendlyGreeter implements Greeter {
  @override
  void greet() => print('你好呀!');
}

class CasualGreeter implements Greeter {
  @override
  void greet() => print('嗨~');
}

void main() {
  var friendly = FriendlyGreeter();
  var casual = CasualGreeter();

  friendly.greet();  // 你好呀!
  casual.greet();    // 嗨~
  // 必须重写 greet 方法,不能省略
}

多重实现

一个类可以实现多个接口:

abstract class Printer {
  void print();
}

abstract class Scanner {
  void scan();
}

class AllInOnePrinter implements Printer, Scanner {
  @override
  void print() => print('打印中...');

  @override
  void scan() => print('扫描中...');
}

10. 为什么泛型前面加 abstract?

abstract class Storage<T> {
  void save(T item);
  T? get(int id);
  List<T> getAll();
}

原因

  1. 不能直接实例化Storage<int>() 是错误的
  2. 定义规范Storage<T> 定义了任何存储类必须有的方法
  3. 强制实现:任何 implements Storage<T> 的类都必须实现这三个方法

使用场景

当你需要定义一个"规范",让多个类都必须实现某些方法时:

abstract class Repository<T> {
  Future<T?> getById(int id);
  Future<List<T>> getAll();
  Future<void> save(T item);
  Future<void> delete(int id);
}

// 实现接口
class UserRepository implements Repository<User> {
  // 必须实现所有 4 个方法
  @override
  Future<User?> getById(int id) async { ... }

  @override
  Future<List<User>> getAll() async { ... }

  @override
  Future<void> save(User item) async { ... }

  @override
  Future<void> delete(int id) async { ... }
}

class ProductRepository implements Repository<Product> {
  // 也必须实现所有 4 个方法
  @override
  Future<Product?> getById(int id) async { ... }

  @override
  Future<List<Product>> getAll() async { ... }

  @override
  Future<void> save(Product item) async { ... }

  @override
  Future<void> delete(int id) async { ... }
}

11. 快速记忆

概念 关键字 特点
抽象类 abstract class 不能实例化,定义规范
抽象方法 void method(); 只有声明没有实现,子类必须重写
实现接口 implements 必须重写所有抽象方法
继承父类 extends 获得功能,可选重写

🎯 什么时候用 abstract?
- 需要定义"模板/规范"时
- 不需要直接创建对象时

🎯 什么时候用 implements?
- 需要强制某个类实现某些方法时
- 定义"接口"(Dart 没有 interface 关键字,用 abstract class 代替)

🎯 extends vs implements
- extends:继承父类,获得功能,可用 super
- implements:实现接口,必须重写所有方法