[Flutter]足够入门的Dart语言系列之面向对象:类之间的6大关系

简介: 无论是面向对象编程,还是面向过程、函数式编程,代码与代码之间的组织关系都是最重要的。通过代码的编写、代码间的相互调用、不同代码的合理使用、集合不同的数据和方法,才能...

无论是面向对象编程,还是面向过程、函数式编程,代码与代码之间的组织关系都是最重要的。通过代码的编写、代码间的相互调用、不同代码的合理使用、集合不同的数据和方法,才能保证一个程序正确、稳定、高效的运行。

面向对象编程是基于类实现的,类体现了对象的总结,包含了事物的数据和行为,完成封装。屏蔽了实现细节的类可以方便的创建对象,调用其功能,这就需要保证不同的类和对象之间合理的组织管理,这就引出了“类与类之间的关系”的问题,类与类之间的相互协作,或之间的关系,主要有六大类:

  • 依赖关系
  • 关联关系
  • 聚合关系
  • 组合关系
  • 继承关系
  • 实现关系

依赖关系

B 类实现功能的逻辑中,使用到 A 类对象,称 B 依赖于 A

如下,Drawable 类在 draw 方法中使用了 Vec2 对象,所以 Drawable 的功能依赖于 Vec2 ,两者是依赖关系。

class Drawable{
  void draw(Vec2 vec2){
    print("绘制向量(${vec2.x},${vec2.y})");
  }
}

class Vec2{
  double x;
  double y;
  
  Vec2(this.x, this.y);
}

一般通过如下的单向虚线箭头表示依赖关系,其中,箭头指向被依赖方。

依赖关系比较容易形成,但关系比较薄弱,也就是就是 耦合性低。依赖关系有一般不需要长期维持,是一种临时关系,只在用到时临时使用。

Drawable drawable = Drawable();
Vec2 p0 = Vec2(3, 4);
drawable.draw(p0);
比如快递员给我送一次快递、我出门做一次出租车、外卖员为我送一次餐,这都是临时的依赖关系,依赖对方的服务。

关联关系

关联关系指两个类之间存在固定的对应关系,是一种相对于依赖而言更强的关系。这种关系一般是长期的,稳定的。

比如 用户User 和 电脑Computer 的关系,User 持有 Computer 是一种强的依赖关系。被关联者通常以 成员变量 的形式存在于关联者类中

class User{
  User(this.computer);
  Computer computer;
  
  void pressStartButton(){
    computer.open();
  }
}

class Computer{
  Computer(this.name);
  String name;
  
  void open(){
    print("====电脑开机:[${name}]=====");
  }
}

在使用时,需要提供Computer的实例,用于 User 中的 computer 成员变量赋值:

void main(){
  Computer computer = Computer("toly mac");
  User user = User(computer);
  user.pressStartButton();
}

一般通过如下的单向实线箭头表示关联关系,其中,箭头指向被关联方。

依赖关系 是虚线,表示关系较弱;关联关系 是实线,表示关系稳固。当两个类相互关联时,称 双向关联。

在 Computer 中添加一个 User 类型的 owner 对象,表示电脑的 所有者,这时 User 和 Computer 就是双向的关联关系。

class Computer{
  String name;
  User? owner;

  Computer(this.name);

  void open(){
    print("====电脑开机:[${name}]=====");
  }
}

用双向实线箭头表示双向关联:

聚合关系

聚合是一种耦合性更强的关联关系。

一般关联关系的两个类,是相互独立的,各自在地位上平等。而聚合关系在语义上有 包含 的涵义,更强调 整体/个体 的区别。比如说,对于电脑而言,它和显卡、内存等硬件间就是一种聚合关系。

class Computer{ // 电脑
  String name;
  GraphicsCard graphicsCard;
  Memory memory;

  Computer(this.name,this.graphicsCard,this.memory);

  void printConfig(){
    print("====电脑名称:[${name}]=====");
    print("====电脑显卡:[${graphicsCard.name}]=====");
    print("====电脑内存:[${memory.type}:${memory.size}GB]=====");
  }
}

class GraphicsCard{ // 显卡
  String name;
  GraphicsCard(this.name);
}

class Memory{ // 内存
  double size;
  String type;

  Memory(this.type,this.size);
}

此外,房子、桌子等各个物件的整体和组成其的部分,都能很好的体现聚合关系。

一般通过如下的空心菱形实线箭头表示聚合关系,菱形指向整体类,箭头指向个体类。

聚合关系 在代码上的表现,和 关联关系 一样,也是持有 个体类 作为 成员变量。不过对于聚合来说,两类间的关系更加紧密,耦合性更强,有 整体/个体 的区别,在地位上是不平等的。

GraphicsCard graphicsCard = GraphicsCard("NVIDIA GeForce RTX 3060");
Memory memory = Memory("DDR4", 32);
Computer computer = Computer("toly mac",graphicsCard,memory);
computer.printConfig();

组合关系

组合是一种 耦合性 比 聚合 更强的关联关系。

组合同样强调整体/个体间的关系,但要求 整体个体 不可分割。这个 不可分割 体现在个体整体 是一体的,个体 的生命周期被 整体 控制,整体 对象的消亡,也会导致 个体 的消亡。

比如 学生学籍档案 之间的关系,学生 学习阶段的变化,学籍档案信息也会随之变化。当学生生涯结束时,档案不再变化,却伴随学生一生。同样的,还是个人与身份证、公司和员工信息之间的关系。

class Student{
  Document document;

  Student(this.document);

  void event(String string){
    document.records.add(string);
  }

  String callDocument(){
    return document.records.join("\n");
  }

  void died(){
    document.records.clear();
  }
}

class Document{
  List<String> records = [];

  void addRecord(String string){
    records.add(string);
  }
}

随着学生活动event的进行,档案会跟随变化,档案和学生有着较强的附属关系,两个事物紧紧绑定。

一般通过如下的实心菱形实线箭头表示组合关系,菱形指向 整体类,箭头指向 个体类。从实心和空心上也能感受到 组合 的关系更为紧密。

依赖、关联、聚合 、组合 四者的耦合性依次增高,本质上四者都是B类需要使用到A类对象,只是类之间关系的紧密程度不同,即耦合度耦合性

耦合度越高,各模块间的联系越紧密,独立性就越差,代码拓展或扩展的灵活性也就越低,代码的修改难度也就越大。

  • 依赖 是对 A 的使用,也被称为 use-a
  • 关联、聚合、组合都是持有 A 类成员,称为 has-a

继承关系

继承是面向对象三大基本特征之一 ,广泛地应用于面向对象的开发中。

前面的四种关系都是在使用或持有的层面,BA 是独立的个体存在。

继承在语义上是让 B 成为 A,此时 BA 将作为同一类型被对待,被成为 is-a,这就表示继承的耦合性要远高于组合在功能上,B 类将拥有 A 类的所有成员属性和方法 ,让 B 类可以使用 A 类的数据和功能

继承是语法层面的特性(或特性支持),而上面介绍的四种关系都是通过代码在逻辑层面的实现或使用。

通过继承,可以建立一个类族系,通过继承可以很好的体现层级关系或父子关系,这在现实中也有着很好的对应。通过继承上级的族系中的公共成员属性和方法,可以起到很好的复用价值(复用性)。

如果 B 类继承自 A 类,则称 A基类 ,也称为 超类父类 ;称 B派生类子类

下面的 Circle 和 Rectangle 派生类继承自父类Shape,两个类的对象都可以使用基类 Shape 的实例方法和属性。通过 move 方法,center 实例变量会被修改。

class Shape {
  Vec2 center;

  Shape(this.center);

  void move() {
    center.x += 1;
    center.y += 1;
    print("$runtimeType:move(10,10)==> center:(${center.x},${center.y})");
  }
}

class Circle extends Shape{
  double radius;
  Circle(Vec2 center,{this.radius =10}) : super(center);
}

class Rectangle extends Shape{
  double width;
  double height;

  Rectangle(Vec2 center,{this.width=20,this.height=20}) : super(center);
}

一般通过单向实线三角空心箭头表示继承关系,其中箭头指向 基类

实现关系

大多数面向对象的编程语言,都不支持多继承(即一个类继承自多个类)。多继承的问题历来是一个难以处理的问题,很容易产生歧义(即二义性)。如果两个类中的实例变量或实例方法相同,子类未进行重写或覆写,在使用子类对象时就会产生二义性,不知道选择使用哪个父类的属性或方法。

但是,一个类可以实现多个接口,因为接口不允许持有实例变量,并且只定义成员方法,要求派生类在继承时必须进行实现。这样,接口就从本质上避免了普通类的多继承的歧义问题。也因此,大多数编程语言,是通过接口实现的多继承

在Dart中没有显式定义接口的方法或关键字。每一个类都隐式地定义了一个接口并实现了该接口。通过implements关键字实现一个类的接口而不是继承该类。

通常来说,都是将抽象方法的抽象类视为接口。

下面定义了两个接口,Calculate表示该接口拥有计算的能力,Electric表示该接口拥有开机(open)、关机(close)的能力。通过implements关键字实现类的接口,多个接口通过,号隔开。当Computer实现CalculateElectric接口时,必须实现接口中定义的全部方法。

abstract class Calculate{
  void calculate();
}

abstract class Electric{
  void open();
  void close();
}

class Computer implements Calculate, Electric{ 
  @override
  void calculate() {
    print("do calculate by computer");
  }

  @override
  void close() {
    print("computer close");
  }

  @override
  void open() {
    print("computer open");
  }
}

Human类也可以实现Calculate接口。

class Human implements Calculate{
  @override
  void calculate() {
    print("do calculate by Human");
  }
}

从两个不同类的不同实现可以看出,接口可以对功能进行隔离,这对于封装来说非常重要。

一般通过单向虚线三角空心箭头表示实现关系,其中箭头指向接口类

参考

相关文章
|
2月前
|
Dart
如何在 Flutter 项目中使用 Dart 语言?
如何在 Flutter 项目中使用 Dart 语言?
127 58
|
19天前
|
Dart
flutter dart mixin 姿势
flutter dart mixin 姿势
|
21天前
|
搜索推荐
Flutter 中的 AnimationController 类
【10月更文挑战第18天】深入了解了 Flutter 中的 `AnimationController`类。它是构建精彩动画效果的重要基石,掌握它的使用方法对于开发具有吸引力的 Flutter 应用至关重要。
|
1月前
|
Dart 开发者 Windows
flutter:dart的学习
本文介绍了Dart语言的下载方法及基本使用,包括在Windows系统上和VSCode中的安装步骤,并展示了如何运行Dart代码。此外,还详细说明了Dart的基础语法、构造函数、泛型以及库的使用方法。文中通过示例代码解释了闭包、运算符等概念,并介绍了Dart的新特性如非空断言操作符和延迟初始化变量。最后,提供了添加第三方库依赖的方法。
28 12
|
1月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
74 7
|
3月前
|
Dart 前端开发 JavaScript
Flutter&Dart-异步编程Future、Stream极速入门
Flutter&Dart-异步编程Future、Stream极速入门
72 4
Flutter&Dart-异步编程Future、Stream极速入门
|
3月前
|
Dart JavaScript 前端开发
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
128 4
|
3月前
|
Kubernetes Cloud Native 搜索推荐
探索云原生技术:Kubernetes入门与实践打造个性化安卓应用:从零开始的Flutter之旅
【8月更文挑战第31天】云原生技术正改变着应用开发和部署的方式。本文将带你了解云原生的基石——Kubernetes,通过实际的代码示例,从安装到部署一个简单的应用,让你迅速掌握Kubernetes的核心概念和操作方法。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你进入云原生世界的桥梁。
|
3月前
|
Dart 开发工具
消除Flutter doctor的警告Warning: `dart` on your path resolves to xxx/bin/dart
消除Flutter doctor的警告Warning: `dart` on your path resolves to xxx/bin/dart
67 0
|
1月前
|
Android开发 iOS开发 容器
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
鸿蒙harmonyos next flutter混合开发之开发FFI plugin