系统化掌握Dart编程之面向对象的抽象类及混入

前言

抽象类的力量

抽象类 允许定义一组共享的属性和行为,但不提供具体的实现细节。这为子类提供了一个清晰的契约 ,确保所有子类都遵循相同的接口。抽象类包含具体的方法实现没有实现的抽象方法,后者必须由子类来实现。这种方式不仅增强了系统的可维护性,还提高了代码的复用性一致性

混入的魅力

混入mixin)则是 Dart 提供的一种轻量级多重继承形式 ,它允许将多个类的功能组合到一个新的类中,而不需要复杂的继承层次结构。混入可以包含方法属性,但不能有构造函数。通过混入,可以轻松地重用代码片段,并将不同的功能模块组合在一起,从而构建出高度灵活和可扩展的系统。

千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意

一、抽象类

1.1、基本概念

抽象类不能被实例化的类,主要用于定义其他类的模板契约

特点

  • 1、不能实例化:不能创建抽象类的对象。
  • 2、具体方法 :可以有具体的方法实现,为子类提供默认行为
  • 3、抽象方法 :可以声明抽象方法,要求子类必须实现这些方法。

1.2、定义抽象类

使用abstract关键字来定义抽象类;

Dart 复制代码
abstract class Animal {
  // 成员变量
  String name;

  // 构造函数
  Animal(this.name);

  // 具体方法:所有动物都可以吃东西
  void eat() {
    print('$name is eating.');
  }

  // 抽象方法:不同动物发出的声音不同
  void sound(); // 没有实现
}

详细说明Animal 是一个抽象类,它定义了所有动物都有的行为 (如 eat()),以及每个具体动物必须实现的行为(如 sound())。

1.3、实现抽象类及其方法

要使用抽象类,必须通过继承并实现其抽象方法来创建具体的子类:

Dart 复制代码
class Dog extends Animal {
  // 调用父类构造函数
  Dog(super.name);

  // 实现抽象方法
  @override
  void sound() {
    print('$name barks.');
  }
}

void main() {
  // 不能直接实例化抽象类 提示 Abstract classes can't be instantiated.
  // var animal = Animal('Generic Animal'); // 这行代码会报错

  // 实例化具体子类
  var dog = Dog('Buddy');
  dog.eat();   // 输出: Buddy is eating.
  dog.sound(); // 输出: Buddy barks.
}

详细说明Animal 是一个抽象类,它定义了一个具体方法 eat() 和一个抽象方法 sound()。可以创建 Dog 的实例, 并实现 sound() 方法,同时可以直接使用 eat() 方法。

1.4、抽象类的优势

  • 1、定义公共接口 :抽象类允许定义一组共享属性行为,确保所有子类遵循相同的结构。
  • 2、提供默认实现 :可以为某些方法提供默认实现减少重复代码
  • 3、强制实现 :通过抽象方法,可以确保所有子类都实现特定的行为

二、多继承的二义性(Diamond Problem

多继承的二义性 ,也称为"菱形问题""钻石问题",是面向对象编程中一个多继承语言可能遇到的问题。当一个类从两个或多个基类派生,而这些基类又共同继承自同一个祖先类时,可能会出现方法或属性的二义性,即编译器无法确定应该使用哪一个版本的方法或属性

菱形继承结构

考虑以下经典的菱形继承结构

Dart 复制代码
      A
     / \
    B   C
     \ /
      D
  • A最顶层的基类
  • BC 分别继承自 A
  • D 同时继承自 BC

在这种结构下,如果类 A 中有一个方法 method(),那么类 D 会通过 BC 继承两次 method()。这会导致编译器无法确定应该调用哪一个版本的 method(),从而产生二义性问题

代码示例

C++ 复制代码
class A {
public:
    void method() {
        std::cout << "Method from A" << std::endl;
    }
};

class B : public A {
public:
    // B does not override method()
};

class C : public A {
public:
    // C does not override method()
};

class D : public B, public C {
public:
    // What happens here?
};

上述示例中,D 类同时继承了 B C,而 BC 都继承了 A。因此,D 类实际上有两个 A 的副本,每个副本都有自己的 method() 方法。当尝试创建 D 的实例并调用 method() 时,编译器不知道应该调用哪个版本的 method(),这就导致了二义性

C++ 复制代码
int main() {
    D d;
    d.method(); // 编译错误:二义性
}

解决方法

不同编程语言有不同的机制来解决这个二义性问题。以下是几种常见的解决方案:

  • 1、虚基类Virtual Inheritance):
    • C++ 中,可以通过声明虚基类来确保只有一个 A 的副本被继承。
C++ 复制代码
  class B : virtual public A {};
  class C : virtual public A {};
  • 2、接口与实现分离
    • JavaDart 等语言中,类不能多重继承,但可以通过接口Mixin 来实现类似的效果,避免直接的多继承带来的二义性问题。
  • 3、默认方法冲突规则
    • 在支持接口默认方法的语言(如 Java 8+),如果接口中有默认方法冲突,子类必须显式地覆盖并提供实现,以消除二义性。

小结

多继承的二义性问题 是由于在一个类从多个基类继承时,这些基类共享 同一个祖先类 ,导致方法属性的重复继承和不确定调用路径。不同的编程语言有不同的机制来解决这个问题,包括虚基类接口Mixin 的使用、以及明确指定调用路径等。

三、混入类

3.1、基本概念

Mixin混入)是 Dart 提供的一种轻量级多重继承形式,允许将多个类的功能组合到一个新的类中,而不需要复杂的继承层次结构

特点

  • 1、多重行为组合 :一个类可以使用多个 Mixin 来组合多种功能。
  • 2、简化代码复用 :避免了传统多重继承带来的复杂性二义性问题。
  • 3、灵活性高 :可以在不改变现有类层次结构的情况下添加新功能

3.2、定义和使用Mixin

Dart 中,定义 Mixin 使用 mixin 关键字:

Dart 复制代码
// 定义Mixin
mixin Flyable {
  void fly() {
    print('Flying...');
  }
}

// 定义Mixin
mixin Swimmable {
  void swim() {
    print('Swimming...');
  }
}

// 使用 Mixin 的类
class Bird with Flyable, Swimmable {
  void chirp() {
    print('Chirp chirp!');
  }
}

void main() {
  var bird = Bird();
  bird.chirp(); // 输出: Chirp chirp!
  bird.fly();   // 输出: Flying...
  bird.swim();  // 输出: Swimming...
}

详细说明Bird 类通过 with 关键字引入了两个 MixinFlyableSwimmable。这样,Bird 类不仅拥有自己的方法 chirp(),还获得了来自 Mixinfly()swim() 方法。

3.3、Mixin 的优势

  • 1、避免多重继承的复杂性及二义性Dart 不支持传统意义上的多重继承 ,但 Mixin 提供了一种安全且简单的方式来实现类似的效果。
  • 2、增强代码复用性 :可以将常用的方法和属性封装在 Mixin 中,然后在需要的地方轻松引入。
  • 3、保持类层次结构的清晰 :由于 Mixin 不涉及复杂的继承关系,因此不会使类层次结构变得混乱。

3.4、Mixin 的限制

  • 1、不能有构造函数Mixin 不能定义构造函数,这意味着它们不能在初始化时执行特定的逻辑。
  • 2、不能访问超类成员Mixin 不能直接访问其父类的成员变量或方法除非这些成员是通过接口可见的

3.5、Mixin 的高级用法

  • 1、Mixin 组合 :可以将多个 Mixin 组合在一起,创建更复杂的行为组合。 如上述3.2中的代码示例

  • 2、Mixin约束条件(On Clause :可以为 Mixin 指定约束条件,确保只有满足这些条件的类才能使用该 Mixin,通过 on 关键字来实现。

    scala 复制代码
    // 定义一个 Mixin,并指定它只能应用于实现了特定接口的类
    mixin CanEat on Animal {
      void eat() {
        print('$name is eating.');
      }
    }
    // 抽象类 Animal
    abstract class Animal {
      String name;
    
      // 构造函数
      Animal(this.name);
    
      // 抽象方法
      void makeSound();
    }
    
    // Dog 类继承自 Animal 并使用 CanEat Mixin
    class Dog extends Animal with CanEat {
      Dog(super.name);
    
      @override
      void makeSound() {
        print('$name barks.');
      }
    }
    
    // Cat 类继承自 Animal 并使用 CanEat Mixin
    class Cat extends Animal with CanEat {
      Cat(super.name);
    
      @override
      void makeSound() {
        print('$name meows.');
      }
    }
    
    void main() {
      var dog = Dog('Buddy');
      dog.makeSound(); // 输出: Buddy barks.
      dog.eat();       // 输出: Buddy is eating.
    
      var cat = Cat('Alice');
      cat.makeSound(); // 输出: Alice meows.
      cat.eat();       // 输出: Alice is eating.
    }

    详细说明CanEat Mixin 只能被实现了 Animal 接口的类使用。如果尝试将其应用于未实现 Animal 的类,编译器会报错

  • 3、Mixin组合、约束及继承顺序 :可以将组合约束及继承一起创建更复杂的实现。

    javascript 复制代码
    mixin A {
      void methodA() {
        print('Method A from Mixin A');
      }
    }
    mixin B {
      void methodB() {
        print('Method B from Mixin B');
      }
    }
    
    mixin C on A, B {
      void methodC() {
        print('Method C from Mixin C');
        super.methodA();
        super.methodB();
      }
    }
    
    class MyClass with A, B, C {
      void myMethod() {
        methodA();
        methodB();
        methodC();
      }
    }
    
    void main() {
      var obj = MyClass();
      obj.myMethod();
    }
    
    输出:
    Method A from Mixin A
    Method B from Mixin B
    Method C from Mixin C
    Method A from Mixin A
    Method B from Mixin B

    详细说明

    • 首先,有 mixin Amixin B,它们分别定义了 methodAmethodB 方法,用于输出相应的信息。

    • mixin C 定义了 methodC 方法,并且使用 on A, B 表示 C 可以混入的类必须已经包含 AB 的特性。在 methodC 中,使用 super.methodA()super.methodB() 来调用 AB 中的方法,确保调用的是父类的方法,避免混淆。

    • MyClass 现在先混入 AB,然后混入 C,这样就满足了 C 的混入条件。MyClass 还定义了 myMethod 方法,用于调用 methodAmethodBmethodC

    • main 函数中,创建 MyClass 的实例 obj,并调用 myMethod,该方法会依次调用 methodAmethodBmethodC 并输出相应的信息。

四、总结

Dart 中的抽象类Mixin 各自提供了独特的优势,帮助开发者构建健壮灵活且易于维护的系统。抽象类 通过定义公共接口和提供默认实现,确保了代码的一致性和可预测性;而 Mixin 通过灵活地组合功能,提升了代码的复用性和扩展性。理解并合理运用这两个特性,可以使应用程序更加模块化易维护及扩展

码字不易,记得 关注 + 点赞 + 收藏 + 评论

相关推荐
程思扬2 小时前
Android笔记: 实现点击事件透传到底部
android·前端·windows·经验分享·笔记·科技·ui
带电的小王3 小时前
Android Studio:Linux环境下安装与配置
android·linux·android studio
柯南二号6 小时前
Android 实现多语言功能
android·gitee
梦浪流云6 小时前
RK3576 Android14 状态栏和导航栏增加显示控制功能
android·java
我是大佬的大佬6 小时前
在Android Studio中如何实现contentprovider实验+SQLite数据库(保姆级教程)
android·开发语言·sqlite·android studio
Grassto7 小时前
Android adb 调试,不在手机上点击信任 “允许usb调试” 即可连接的方式(手机需root)
android·adb
哥咫匙传说8 小时前
frameworks 之 AMS与ActivityThread交互
android·车载系统
大G哥9 小时前
Android 13 Hotseat定制化修改——001 hotseat布局方向
android
hongkid9 小时前
windows 搭建flutter环境,开发windows程序
windows·flutter
我命由我1234512 小时前
11-2.Android 项目结构 - themes.xml 文件基础解读
android·xml·java·java-ee·gitee·android jetpack·android runtime