0%

Android架构学习之路二-面向对象

前言

架构学习之路系列

这是架构学习系列的第二篇,主要介绍一下 UML 图,设计原则以及设计模式。这些名词大家估计都耳熟能详了,它们对于架构而言非常重要,只有理论结合实际,以后才会越来越娴熟。在开始做需求的时候,先别急着写代码,思考一下这个需求的本质是干嘛,用面向对象的思想去抽象这个过程,不是直接搞几个类就可以了的。

面向对象和面向过程

  • 面向对象:面向对象是一种风格,以类作为基本单位,通过对象访问,万物皆对象,拥有 封装、继承、抽象、多态 等特性。
  • 面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,更侧重于功能的设计。

UML图

来自网络:UML 即 Unified Model Language, 是一种建模语言。在软件开发中,当系统规模比较复杂时,需要用图形抽象地来表达复杂的概念,让整个软件设计更具有可读性,可理解性,以便尽早发现软件设计时存在的潜在问题,从而降低开发风险。同时,也极大地方便了业务人员与开发人员之间的交流。

UML 建模时常见的图:用例图,类图,对象图,活动图,状态图,时序图,协作图,组件图,部署图。这里主要看看 UML 类图。

类之间的关系:继承/泛化、实现、组合、聚合、关联、依赖。继承/泛化、实现关系体现的是一种类与类、或者类与接口间的纵向关系;其他关系则体现的是类与类、或者类与接口间的引用,是横向关系。有时候比较难以区分,不过很多事物间的关系想要定位清晰本身就是很难的,这几种关系都是语义级别的,所以从代码层面并不能完全区分。

总的来说,后几种关系所表现的强弱程度依次为: 组合 > 聚合 > 关联 > 依赖

关于下面这些关系图用的是箭头还是菱形,是空心还是实心,一般 UML 类图工具都会有提示,用多了就熟了。我这里使用的是一个在线画图工具, 跟 ProcessOn 类似也可以用来画流程图,思维导图以及 UML 图等。

上述六种关系表示如下:

类图关系

类结构

一般来说:

  • 一般类名:正常字体粗体
  • 抽象类名:斜体字粗体
  • 接口:在粗体上方加上<interface>
  • 可见性符号:+=public, #=protected, -=private
  • 冒号:表示属性的类型和方法的返回类型

如图:

类结构

泛化/继承

泛化和继承其实是一个逆过程,泛化就是由子类抽象出一个父类,而继承就是由父类具体化一个子类。

泛化关系用一条带空心箭头的直线表示(A继承自B):

泛化

实现

指的是一个 Class 类实现 Interface 接口的功能。

实现关系用一条带空心箭头的虚线表示:

实现

依赖

一个类依赖于另一个类的定义,是一种“使用”关系,如,汽车依赖于汽油。一般来说依赖关系在 Java 中体现为局部变量,形参,或者对静态方法的调用等。与关联关系不同的是,它是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化,依赖关系也可能发生变化。

依赖关系是用一条带箭头的虚线表示,描述一个对象在运行期间会用到另一个对象的关系,如下图表示 A 依赖于 B:

依赖

关联

关联是类与类之间的联接,使一个类知道另一个类的属性和方法,比如,乘车人和车票之间就是一种关联关系;学生和学校就是一种关联关系;关联可以是双向,也可以是单向的,一般使用成员变量来实现。

关联关系默认不强调方向,表示对象间相互知道,如果特别强调方向,如下图,表示 A 知道 B,但 B 不知道 A:

关联

聚合

聚合是一种强的关联关系,它体现的是 has-a 的关系,整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象。比如计算机与 CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

聚合关系用一条带空心菱形箭头的直线表示,如下图表示 A 聚合到 B 上,或者说 B 由 A 组成:

聚合

组合

组合属于关联关系,比聚合更强,也称为强聚合,它体现的是 contains-a 的关系,同样是整体与部分间的关系,但此时整体与部分是不可分离的,整体的生命周期结束也就意味着部分的生命周期结束。比如你和你的脑子,在代码层面和关联关系是一致的,只能从语义级别来区分。

组合

六大设计原则

这里简单介绍一下六大设计原则(内容来自网络,由于很多文章写的都差不多,就不贴链接了):面向对象设计原则是 OOPS(Object-Oriented Programming System, 面向对象程序设计系统) 编程的核心。

在实际的架构设计中,很难满足所有的设计原则,需要根据实际业务来决定如何取舍

单一责任原则

单一责任原则:让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类,强调高内聚

听说即使是经验丰富的程序员,也可能会违背这一原则,因为有职责扩散,即因为某种原因,职责 P 被分化为粒度更细的职责 P1 和 P2。所以使用面向对象语言开发时,不要急着写代码,优先考虑下模块、类、函数等的设计是否足够单一。

开闭原则

开闭原则:对扩展开放,对修改关闭。充分诠释 抽象、多态 特性,又是多数行为型设计模式的基础,遍布于各大优秀框架之中,是最重要的一条设计原则。比如说对于数据库操作,可以抽象出一个 CRUD 接口,后续如果切换实现方式,比如说 ROOM or GreenDAO 框架,只需重新实现该接口,而外部业务层只持有接口,对实现无感知,也不需要改动。

迪米特法则

迪米特法则:通俗来讲就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的 public 方法,不对外泄漏任何信息。

迪米特法则根本思想是强调了类之间的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,对有关系的类造成的影响比较小,即信息的隐藏促进了程序的复用。

接口隔离原则

接口隔离原则:建立单一接口,不要建立臃肿的接口,尽量细化接口。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。

接口隔离原则与迪米特法则目的很相似,都可以降低模块间依赖关系。但接口隔离更侧重于设计单一接口,提升复用性并间接降低模块间依赖关系,而迪米特法则是直接降低模块间依赖关系。

运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

里氏替换原则

里氏替换原则:设计子类的时候,要遵守父类的行为约定。父类定义了函数的行为约定,子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。

依赖倒置原则

依赖倒置原则:高层模块(使用者)不应依赖低层模块(被使用者),它们共同依赖同一个抽象,抽象不要依赖具体实现细节,具体实现细节依赖抽象。即针对接口编程,不要针对实现编程。

在实际编程中,我们一般需要做到如下 3 点:

  • 低层模块尽量要有抽象类或接口
  • 变量的声明类型尽量是抽象类或接口
  • 使用继承时遵循里氏替换原则

设计模式

关于设计模式,网上很多文章讲解,这里就不赘述了,之前也做过笔记,有兴趣可以看看 Java面向对象设计模式

这些设计模式是前人深思熟虑后的精华,我们在实际开发中,可以多思考思考是否可以使用,用得多了就熟了。另外也不一定要拘泥于这些形式化的模式,结合实际业务,我们可以考虑使用自己的设计模式,其实很多思想都是互通的,只要记得遵循上述的设计原则即可。

写在最后

写代码的时候,记得三思而后行,想一想你写的代码是不是在它该在的位置,是不是以该有的形式存在的。

架构不是一蹴而就的,希望我们有一天的时候,能够从自己写的代码中找到架构的成就感,而不是干几票就跑路。这个系列应该会一直更新,记录我在架构之路上学习的脚印儿,一件一件扒开架构神秘的面纱