序
本篇文章源于该篇文章《UML类图很难吗?这样理解一文就通》。因为小马以此为基础整理了一次内部分享会,所以讲内容展开具体化了,也可以认为是上一篇文章的升级版。
该标题为什么如此奇葩而不失优雅呢?因为本来小马是打算直奔主题,直接分享IoC的,但又考虑如果对依赖注入不熟悉就不好理解,如果对类的关系不理解又不好理解依赖注入,如果要理解类的关系就必须引入类图关系来介绍,如果引入类图的话就不得不从UML说起,于是就诞生了这个奇怪的标题。
小马把本次的内容调侃为:适用场景:软考考试敲黑板必考题,架构师一技能,码猿代码实现与编写框架思想神器,面试护盾...
后面加了一堆省略号一点也不为过,哈哈哈。
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入。
目录
1.UML
2.UML类图关系
3.依赖强弱
4.依赖注入
5.Ioc
一、UML
UML统一建模语言
要从UML说起,虽然并不是本次的重点内容。
百科怎么说?
Unified Modeling Language 统一建模语言,又称标准建模语言。是用来对软件密集系统进行可视化建模的一种语言。UML的定义包括UML语义和UML表示法两个元素。
UML是在开发阶段,说明、可视化、构建和书写一个面向对象软件密集系统的制品的开放方法。最佳的应用是工程实践,对大规模,复杂系统进行建模方面,特别是在软件架构层次,已经被验证有效。统一建模语言(UML)是一种模型化语言。模型大多以图表的方式表现出来。一份典型的建模图表通常包含几个块或框,连接线和作为模型附加信息之用的文本。这些虽简单却非常重要,在UML规则中相互联系和扩展。
UML的作用
概括起来说,UML主要有以下作用:
(1)为软件系统建立可视化模型。
UML符号具有良好的语义,不会引起歧义;基于UML的可视化模型,使系统结构直观、易于理解;使用UML进行软件系统的模型不但有利于系统开发人员和系统用户的交流,还有利于系统维护。模型是系统的蓝图,它可以对开发人员的规划进行补充,模型可以帮助开发人员规划要建的系统。有了正确的模型就可以实现正确的系统设计,保证用户的要求得到满足,系统能在需求改变时站得住脚。对于一个软件系统,模型就是开发人员为系统设计的一组视图。这组视图不仅描述了用户需要的功能,还描述了怎样去实现这些功能。
(2)为软件系统建立构件。
UML不是面向对象的编程语言,但它的模型可以直接对应到各种各样的编程语言。例如,它可以使用代码生成器工具将UML模型转换为多种程序设计语言代码,如可生成C++,XML,DTD,JAVA, Visual basic等语言的代码,或使用反向生成器工具将程序源代码转换为UML;甚至还可以生成关系数据库中的表。
(3)为软件系统建立文档。
UML可以为系统的体系结构及其所有细节建立文档。不同的UML模型图可以作为项目不同阶段的软件开发文档。
UML特点
(1)UML统一了各种方法对不同类型的系统、不同开发阶段以及不同内部概念的不同观点,从而有效的消除了各种建模语言之间不必要的差异。它实际上是一种通用的建模语言,可以为许多面向对象建模方法的用户广泛使用。
(2)UML建模能力比其它面向对象建模方法更强。它不仅适合于一般系统的开发,而且对并行、分布式系统的建模尤为适宜。
(3)UML是一种建模语言,而不是一个开发过程。
UML图的种类
2.0版本之后增加到13种
UML各种图的作用
用例图:从用户角度描述系统功能。
类图:描述系统中类的静态结构。
对象图:系统中的多个对象在某一时刻的状态。
状态图:是描述状态到状态控制流,常用于动态特性建模
活动图:描述了业务实现用例的工作流程
顺序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互
协作图:描述对象之间的协助关系
构件图:一种特殊的UML图来描述系统的静态实现视图
部署图:定义系统中软硬件的物理体系结构
包图:对构成系统的模型元素进行分组整理的图
组合结构图:表示类或者构建内部结构的图
交互概览图:用活动图来表示多个交互之间的控制关系的图
然而,我们今天并不关心各种图的作用,有兴趣的小伙伴可以自行研究,参考《九种模型图的参考示例》, 我们今天只关心类图。
# 二、UML类图关系
类图:
在UML的静态机制中类图是一个重点,它不但是设计人员关心的核心,更是实现人员关注的核心。建模工具也主要根据类图来产生代码。类图在UML的9个图中占据了一个相当重要的地位。James Rumbaugh对类的定义是:类是具有相似结构、行为和关系的一组对象的描述符。类是面向对象系统中最重要的构造块。类图显示了一组类、接口、协作以及他们之间的关系。在UML中问题域最终要被逐步转化,通过类来建模,通过编程语言构建这些类从而实现系统。类加上他们之间的关系就构成了类图,类图中还可以包含接口、包等元素,也可以包括对象、链等实例。
类之间的关系:
常见的有以下几种关系:泛化(Generalization), 实现(Realization),组合(Composition),聚合(Aggregation),关联(Association),依赖(Dependency)。
下图中,类图之间分别是什么关系呢?
根据图我们可以自己总结一些口令便于记忆(因为考试总会考这些):箭头指向于依赖于;根据依赖强弱,图标分别是箭头,箭尾,方向标。--这些如何理解下文会讲到。
泛化(Generalization)
【泛化关系】:是一种继承关系,它指定了子类如何特化父类的所有特征和行为例如:老虎是动物的一种.
【代码体现】:继承
【箭头指向】:带三角箭头的实线,箭头指向父类
实现(Realization)
【实现关系】:是一种类与接口的关系,表示类是接口所有特征和行为的实现
【代码体现】:接口实现
【箭头指向】:带三角箭头的虚线,箭头指向接口
组合(Composition)
【组合关系】:是整体与部分的关系。组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期(同时消亡)。比如没有公司就不存在部门。
【代码体现】:成员变量
【箭头及指向】:带实心菱形的实线,菱形指向整体(有些是右边也有箭头)
注意有些右边是带箭头的
聚合(Aggregation)
【聚合关系】:是整体与部分的关系。如汽车类和轮胎类是整体和部分的关系。整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。
聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。
【代码体现】:成员变量
【箭头及指向】:带空心菱形的实心线,菱形指向整体
注意有些右边是带箭头的
关联(Association)
【关联关系】:是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子,我和我的朋友。
关联的两个对象之间一般是平等的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。
关联可以是双向的,也可以是单向的(可以自己关联自己)。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。
【代码体现】:成员变量
【箭头及指向】:带普通箭头的实心线,指向被拥有者
注意双向关联有些左右是带箭头的
依赖(Dependency)
【依赖关系】:是一种使用的关系,所以要尽量不使用双向的互相依赖。比如动物新陈代谢需要氧气、水。
【代码表现】:局部变量、方法的参数或者对静态方法的调用
【箭头及指向】:带箭头的虚线,指向被使用者
# 三、依赖强弱
关联关系的强弱:
有时候,我们也把组合、聚合、关联、依赖都叫做“依赖”或者笼统地归结为关联关系,可以认为是关联关系的一种强弱表达。后面我们会讲到依赖注入。
关系的强弱顺序或者说耦合度强弱:
泛化= 实现> 组合> 聚合> 关联> 依赖
思考:如果给出一段代码实现如何区分类之间是什么关系,尤其是组合、聚合、关联?
依赖比较好区分,因为其表现为局部变量、方法的参数或者对静态方法的调用;
而聚合、组合、关联都是成员变量注入只能配合语义,结合上下文才能够判断出来,而只给出一段代码让我们判断是关联,聚合,还是组合关系,则是无法判断的,上面说到其代码实现都是成员变量/类属性。
# 四、依赖注入
依赖关系代码
思考:上面代码中体现了哪些“依赖”关系?有没存在什么问题?
语义上:老师与学生 =》 关联关系
实现上:严格意义上来说不能算依赖注入(依赖倒转),算依赖实现
teacher1类与student类 成员变量注入=》关联关系 =》组合,聚合,关联都是用成员变量注入
teacher2类与student类 command1方法形参;command2 局部变量 ; command3 对静态方法的调用 注入 =》依赖关系 =》如果区分语义,学生和老师的关联关系不能按依赖关系来实现
通俗地理解为依赖就是我自己不能单独完成任务,必须要引入外部别人的资源来一起完成任务。
依赖注入
如上,假如这个时候老师想换教学对象了,教老年人。那么需要更改自己的代码new student()为new elder();假如又突然被调配成教中年人呢?是不是要一直改代码。这其实是一个强耦合。下面代码我们进行了形参依赖注入,还有一种是构造器注入。
对于依赖注入而言,需要遵循依赖倒转原则。
依赖倒转原则:
依赖倒转原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
依赖注入的好处:
依赖之间的解耦
单元测试,方便Mock
# 五、IoC
依赖注入它不香吗?为啥还要引入IoC容器?
大多数时侯,在使用依赖注入方式解耦组件时,并不需要用到容器。当一段程序需要实例化的类太多或者依赖太多的时候,重复依赖注入的代码是比较繁琐的事情,此时需要使用容器。
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
IoC在控制啥?反转啥?
由IoC控制对象实例的创建;
反转是针对正向而言,本来A需要自己找到外部B并自己实例化注入进来,现在A在等容器创建一个B的实例然后反向注入A,最后从容器中得到已注入B依赖的A实例。
IoC实例解析
IOC容器即服务容器。一个功能、命令或者任务都可以叫做服务service(一般指提供了一些功能的类)。如果把所有的服务(类)装在一个地方,那这个地方叫容器。
容器就是提供服务的载体,在程序运行过程中,动态的提供服务(资源)。
IoC容器实现解析案例
以下代码是小马整理的一个IOC容器案例解析,和Laravel的核心IOC基本相似,但有部分区别。
经典案例 Laravel Service Container 传送门。
对以上代码的一些总结:
构造器注入:
总结:把闭包放入容器数组,使用时make调用闭包即可得到已经依赖注入的实例。
$bindings = []保存与接口绑定的闭包,闭包必须能够返回接口的实例。(存的是闭包,make运行闭包后即可得到实例。闭包函数在执行如果有依赖则依赖注入无则直接实例化,最终return实例化后的实例)
bind($abstract, $concrete = null)方法 可以理解为concrete 有两种:new class A实例化对象直接入容器和 calss类A,如果是class类则先反射,找到依赖B后new实例化B,并通过反射拿到被依赖的类A的实例化对象A,最后再加入容器。(即有依赖的反射找到被依赖的类B实例化后再实例化本身A,没有依赖的直接实例化A)
make运行闭包得到实例时,传入的参数是容器本身,即 $this
为某个接口绑定一个实现两种方式(有无依赖注入):
1. 绑定接口的实现的类名(student)
2. 绑定一个闭包,这个闭包应该返回接口的实例(teacher2)
两种方式的实例化操作都是调用 build() 方法完成
make方法是在用闭包得到一个最终的实例(new class)
其实还使用了注册树的设计模式。
放松一下
本次小马的分享就到这了,希望对大家有帮助,感谢。