1.8 抽象类与接口

基础知识

在 Dart 中,抽象类和接口是实现多态和定义契约的重要机制。它们允许我们定义一个类的行为规范,而不必提供具体的实现,从而实现代码的解耦和扩展性。

1. 抽象类 (Abstract Classes)

抽象类是不能被直接实例化的类。它通常包含抽象方法(没有具体实现的方法)和非抽象方法(有具体实现的方法)。抽象类的主要目的是作为其他类的基类,提供一个通用的接口和部分实现。

  • 使用 abstract 关键字定义抽象类和抽象方法。
  • 抽象类可以有构造函数,但不能直接调用。
  • 抽象类可以包含实例变量、getter、setter、普通方法和抽象方法。
  • 子类继承抽象类时,必须实现其所有的抽象方法,除非子类本身也是抽象类。
dart
复制代码
// 抽象类 Shape
abstract class Shape {
  // 抽象方法:计算面积,子类必须实现
  double getArea();

  // 非抽象方法:打印信息
  void printInfo() {
    print("This is a shape.");
  }
}

// Circle 类继承 Shape
class Circle extends Shape {
  double radius;

  Circle(this.radius);

  @override
  double getArea() {
    return 3.14159 * radius * radius;
  }

  @override
  void printInfo() {
    super.printInfo(); // 调用父类的非抽象方法
    print("This is a circle with radius $radius.");
  }
}

// Rectangle 类继承 Shape
class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double getArea() {
    return width * height;
  }
}

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

  var circle = Circle(5.0);
  print("Circle Area: ${circle.getArea()}"); // 输出: Circle Area: 78.53975
  circle.printInfo(); // 输出: This is a shape.
                      //       This is a circle with radius 5.0.

  var rectangle = Rectangle(4.0, 6.0);
  print("Rectangle Area: ${rectangle.getArea()}"); // 输出: Rectangle Area: 24.0
}

2. 接口 (Interfaces)

在 Dart 中,没有专门的 interface 关键字。所有的类都隐式地定义了一个接口。这意味着任何类都可以作为接口被其他类实现。当一个类实现一个接口时,它必须实现该接口中定义的所有方法和属性。

  • 使用 implements 关键字来实现接口。
  • 一个类可以实现多个接口,从而实现多重继承的效果(行为上的多重继承)。
  • 实现接口的类必须提供接口中所有方法的具体实现。
dart
复制代码
// 定义一个行为接口 (隐式接口)
class Flyable {
  void fly() {
    print("I can fly!");
  }
}

class Swimmable {
  void swim() {
    print("I can swim!");
  }
}

// Bird 类实现 Flyable 接口
class Bird implements Flyable {
  String name;

  Bird(this.name);

  @override
  void fly() {
    print("$name is flying high.");
  }
}

// Duck 类实现 Flyable 和 Swimmable 接口
class Duck implements Flyable, Swimmable {
  String name;

  Duck(this.name);

  @override
  void fly() {
    print("$name is flying low.");
  }

  @override
  void swim() {
    print("$name is swimming in the pond.");
  }
}

void main() {
  var bird = Bird("Eagle");
  bird.fly(); // 输出: Eagle is flying high.

  var duck = Duck("Donald");
  duck.fly();  // 输出: Donald is flying low.
  duck.swim(); // 输出: Donald is swimming in the pond.
}

抽象类与接口的区别:

特性 抽象类 (Abstract Class) 接口 (Implicit Interface)
关键字 abstract implements (所有类都是隐式接口)
实例化 不能直接实例化 不能直接实例化 (作为接口时)
继承数量 单继承 (一个类只能 extends 一个抽象类) 多实现 (一个类可以 implements 多个接口)
成员 可以有抽象方法和非抽象方法、实例变量、构造函数 只能定义方法签名和属性签名 (没有具体实现)
目的 定义通用行为和部分实现,作为基类 定义行为契约,强制实现特定方法
关系 “is-a” 关系 (子类是父类的一种) “can-do” 关系 (类可以做接口定义的事情)

官方文档链接

Flutter 开发中的应用案例

抽象类和接口在 Flutter 框架中被广泛使用,它们是构建可扩展、可测试和模块化应用的关键。例如,Flutter 中的 Widget 类本身就是抽象的,StatelessWidgetStatefulWidget 都是其子类。许多设计模式,如策略模式、工厂模式等,都依赖于抽象类和接口来实现。

案例:一个主题切换器,演示抽象类和接口的应用

我们将创建一个简单的应用,允许用户切换不同的主题(亮色/暗色)。这个案例将使用抽象类定义主题的通用接口,并使用具体类实现不同的主题,从而演示抽象类和接口在 Flutter 主题管理中的应用。

dart
复制代码
import 'package:flutter/material.dart';

// 抽象类:定义主题的通用接口
abstract class AppTheme {
  String get themeName;
  Color get primaryColor;
  Color get accentColor;
  Color get textColor;
  Color get backgroundColor;

  // 抽象方法,子类实现具体的主题样式
  ThemeData getThemeData();
}

// 亮色主题实现
class LightTheme implements AppTheme {
  @override
  String get themeName => '亮色主题';

  @override
  Color get primaryColor => Colors.blue;

  @override
  Color get accentColor => Colors.lightBlueAccent;

  @override
  Color get textColor => Colors.black87;

  @override
  Color get backgroundColor => Colors.white;

  @override
  ThemeData getThemeData() {
    return ThemeData(
      brightness: Brightness.light,
      primaryColor: primaryColor,
      colorScheme: ColorScheme.light(
        primary: primaryColor,
        secondary: accentColor,
      ),
      scaffoldBackgroundColor: backgroundColor,
      textTheme: TextTheme(
        bodyMedium: TextStyle(color: textColor),
      ),
      appBarTheme: AppBarTheme(
        backgroundColor: primaryColor,
        foregroundColor: Colors.white,
      ),
    );
  }
}

// 暗色主题实现
class DarkTheme implements AppTheme {
  @override
  String get themeName => '暗色主题';

  @override
  Color get primaryColor => Colors.deepPurple;

  @override
  Color get accentColor => Colors.purpleAccent;

  @override
  Color get textColor => Colors.white70;

  @override
  Color get backgroundColor => Colors.grey[900]!;

  @override
  ThemeData getThemeData() {
    return ThemeData(
      brightness: Brightness.dark,
      primaryColor: primaryColor,
      colorScheme: ColorScheme.dark(
        primary: primaryColor,
        secondary: accentColor,
      ),
      scaffoldBackgroundColor: backgroundColor,
      textTheme: TextTheme(
        bodyMedium: TextStyle(color: textColor),
      ),
      appBarTheme: AppBarTheme(
        backgroundColor: primaryColor,
        foregroundColor: Colors.white,
      ),
    );
  }
}

class ThemeSwitcherApp extends StatefulWidget {
  const ThemeSwitcherApp({super.key});

  @override
  State<ThemeSwitcherApp> createState() => _ThemeSwitcherAppState();
}

class _ThemeSwitcherAppState extends State<ThemeSwitcherApp> {
  AppTheme _currentTheme = LightTheme(); // 初始主题为亮色主题

  void _toggleTheme() {
    setState(() {
      // 根据当前主题切换到另一个主题
      if (_currentTheme is LightTheme) {
        _currentTheme = DarkTheme();
      } else {
        _currentTheme = LightTheme();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: _currentTheme.getThemeData(), // 应用当前主题
      home: Scaffold(
        appBar: AppBar(
          title: Text(_currentTheme.themeName), // 显示当前主题名称
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Hello, Flutter!',
                style: TextStyle(
                  fontSize: 30,
                  color: _currentTheme.textColor, // 使用当前主题的文本颜色
                ),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: _toggleTheme,
                child: const Text('切换主题'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(const ThemeSwitcherApp());
}

案例分析:

  • abstract class AppTheme:我们定义了一个抽象类 AppTheme,它定义了所有主题都应该具有的属性(如 themeNameprimaryColor 等)和抽象方法 getThemeData()。这个抽象类充当了一个契约,规定了所有具体主题类必须实现的行为。
  • class LightTheme implements AppThemeclass DarkTheme implements AppThemeLightThemeDarkTheme 分别实现了 AppTheme 接口。它们提供了 AppTheme 中所有属性和抽象方法的具体实现,定义了亮色和暗色主题的视觉样式。
  • _currentTheme = LightTheme();:在 _ThemeSwitcherAppState 中,_currentTheme 变量的类型是 AppTheme。这体现了多态性,因为 _currentTheme 可以引用 LightThemeDarkTheme 的实例。
  • _currentTheme.getThemeData():在 MaterialApptheme 属性中,我们调用了 _currentTheme.getThemeData()。由于多态性,Dart 会根据 _currentTheme 实际引用的对象类型(LightThemeDarkTheme)来调用相应的 getThemeData() 方法,从而应用不同的主题。
  • if (_currentTheme is LightTheme):在 _toggleTheme 方法中,我们使用 is 关键字来判断 _currentTheme 的实际类型,从而决定切换到哪个主题。这展示了类型测试运算符在运行时判断对象类型的作用。

这个案例清晰地展示了 Dart 的抽象类和接口在 Flutter 应用中的实际应用。通过定义抽象接口,我们可以实现主题逻辑与 UI 渲染的解耦,使得添加新的主题变得非常容易,并且代码结构更加清晰和可维护。这种设计模式在 Flutter 中非常常见,用于构建灵活和可扩展的组件。