设计模式学习笔记(二):UML与面向对象设计原则

1 UML

1.1 UML

UML(Unified Modeling Language)是统一建模语言,1997年11月UML1.1版本提交给OMG并正式通过,成为建模语言的个那P p 7个也标准。2003年6月UML2.0获得正式通过。

1.2 UML特性

  • U(Unified)o H C 4 Y b统一,UML融合了多种e I n I + i u优秀的面向对象J c E 0 0 D l建模方法以及多种得c @ b = $ 5 - J到认可的软件工程方法,消除了因方法林f t l G -立且相互独立而带来的种种不便,集众家之长,股名“. X i C B * h = Q统一: g T 4 8 ] m I =”。通过统 J g : [ 9一的表示方法可以让不同知识背景的领域专家,系统分析设计人员以及开发人员可以方便地N G [ 6 ! . : x交流
  • M(Modeling):UR s L 6 N J RML是一种通用的可视化! e -建模语言,不同与编程语言,1 4 WUML通过一些标准的图形符号D O $ [ R - t 2 J和文字来对系统进行建模,用于对软! . o +件进行描述,可视化处理,构造系统制品的文档。UML适用于各种软件开发方法S k 8 k 5 g G,软件生命周期的各个阶段,各种应用领域以及各种开发工具
  • L(Language)3 3 p 4:UML是一种语言,也就U f ! ^ Z意味着它有属于自己的标准表达规则,不是一种类似Java,C++的编程语言,而是一种分析设计语言,一种建模语言

1.3 UML结构4 O G 6 1 ^ C

UML结构通常包括以下4个部分:视图,图,模型元素以& g 2 H及通用机& o m 0 A K 6 H制。v G G w 1 i

1.3.1 视图

UML视图用于从不同的角度来表示待建模的系统。视图是由许多图形组成的一个抽象集合,在建立一个系统模型时,只有通过定义多个视图,每个视图显示该系统的一个特G V & W t定方面,才能构造出该系统的完整蓝图。
UML视图包括:

  • 用户视图:以用户的观点表示系统的目标,是所有视图的核心,用于描述系l 8 a # Z K o { +统的需求
  • 结构视图:系统的静态行为,描述系统的静态元素,比如包,类,对象以及它们之间的关系
  • 行为视图:系统的动态行为,描述系统的组成元素在系统运行时的交互关系
  • 实现视图:系统中逻辑元素的分1 S V 8 X ] K = O布,} E w描述系统中物理文件以及它们之间的关系
  • 环境视图:系统中物理元素的分布,描述系统D S 中硬件设备以及他们之间的关系

1.3.2 图

UML图是描述UML视图b f 6 N G 1 ] e `内容的图形,UML2.0提供了13V q e种图,分别是用例图,类图,对象图,包图,组合结构图u j ( , . t A _ |,状态图,活动图8 r 6 h ^ =,顺序图,通信图,定时图,交互概览图,组件图和部署图。其中:

  • 用例图对应用户视图
  • 类图,对象图,包图和组合结构图对应结构视图
  • 状态图,活动图,顺序图,通信图,定时图和交互概览图对应行为视图
  • 组件图对应实现视图
  • 部署图对应环境视图

1.39 6 W A { 2 J i.3 模型元素

模型元素是指UML图中所使用的一些; e ( J w ` B概念,对应于普通的面向对象概念& C j,如类,对象,消h i D , g G A g Q息以及这些概念之间的关系,如关联关系,泛化关系等。

1.3.4 通用机制

UML提供的通用机制为模型元素提供额外` ] S u ^ v ,的注释,; k ; N Y信息和语义,这些通用机制也提供了扩展机制,允许用户对UML进行扩展,如定义新的建模元素,扩展原有的语义,添加新的特殊信息来扩展模型元素的规则说明等,以便适用于特定的方法或过程,组织和用户。

2 UML类图

2.1 类图

类封装了数据和行为,是具有相同属性,操作,关系的对象集合的总称。类图是用出现在系统中不同类来描述系统的静态结构,主要描述不同的类以及它们之间的关系。
在UML中,类图包含类名,属性以及操作。如下面的Emp_ @ L 2 - + e u &loyee类:

类一~ G F般由三部分组成:

  • 类名
  • 属性
  • 操作

2.1.1 类名

类名就是类的名字,一个字符串。

2.1.2 属性

类的成员变量,一般的格式为

h 2 | , G见性 名称 : 类型 [ = 默认值]

可见性表示该属性对于类外的元素是否可见,包括:

  • 公有:+
  • 私有:-
  • 受保护:#
  • 包:~

2.1.3 操作

UML规定操作的定义方式为:

可见性 名称(参数列表)# T ; 7 L k X[ : 返回类型]
  • 可见性与属性可见性的定义一致
  • 参数列表表示方法的参数,语法与属性定义类似,用,分隔

2.2 类之间的关系

UML提供了四种不同的方式表V % o m e k 8 ] @示类与类之间的关系:

  • 关联关系R c 5 , g
  • 依赖关系
  • 泛化关系
  • 接口与实现关系

下面逐个看一下。

3 关联关系

关联关系是一种结构化关系,用于表示一类对象与另一类对象之间有联系。在UML中用实线连接有关联关系的类。可以在关联线上标注角色名,关系的两端代表两种不同的角色,因此在一个关联关系中可以包含两个角色名,角色名不是必须的,但可以使类之间的关系更加明确。
例如在一个登录界面类LoginForm包含一个JButton

设计模式学习笔记(二):UML与面向对象设计原则
UML中关联通常包括以下六种形式:

  • 双向关联
  • 单向关联
  • 自关联
  • 多重性关联
  • 聚合关系
  • 组合关系

3.1 双向关联

默认情况下关联是双向的,例如顾客购买商品并拥有商品,反之卖出的商品总是某个顾客与之相关联:

设计模式学习笔记(二):UML与面向对象设计原则

3.2 单向关联

d ` Q U (联也可以是单向的,在UML中关联用带箭头的实线表示,比如顾客拥有地址:
设计模式学习笔记(二):UML与面向对象设计原则

3.3 自关联

系统中可能G Z 2 m r会存在一些类的属性对象类型为/ G . x [ 6 0该类本身,这种特殊的关联关系为自{ p P i d关联,常见于链表:
设计模式学习笔记(二):UML与面向对象设计原则

3.4L ; c 5 多重性关联

多重性关联又称为多重性关联联系,表示两个关联对象在数量上的对应关系。在UML中,对象之间的多重J z `性可以直接在关联直线上用一个数字或者一个数字范围来表示。常见的表示方式如下:
设计模式学习笔记(二):UML与面向对象设计原则
例如一个界面可以具有0个或多个按钮,但是一个按钮只s k ~能从属于一个界面:
设计模式学习笔记(二):UML与面向对象设计原则

3.5 聚合关系

I s 0 K : h合关系表示整体与部分的关系,使用空心菱D 6 ; Q形表示。聚合关系中部分是整体的一部分,但是部分可以脱离整体独立存@ b Y ? v t 7 B :在,比如引擎是汽车的一部分,但是引擎可以独立于汽o J ~ ; B 8 ? `车存在:
设计模式学习笔记(二):UML与面向对象设计原则

3.6 组合关系

组合关系也表示整体与部分之间的关系,但是部分不! B B x M能脱离整体存在。组合关系使用实心菱形表示。比如人的头和嘴巴是组合关系:
设计模式学习笔记(二):UML与面向对象设计原则

4 依赖关系

依赖关系是一种使用关系,在需要表示“一个事物使用另一个事物”时使用依赖关系。UML中依赖关系用带箭头的虚线表示,由依赖的一方指向 ( 3 % + S被依赖的一方。例如驾驶员开车,开车需要车,也就是驾驶员依M N ( a M h W C j赖于车:
设计模式学习笔记(二):UML与面向对象设计原则

5 泛化关系

泛化关系也就是继承关系,用于描述父Y _ = } !类与之类之间的关系,父类又叫基类或者超类,子类又称作派生类。UML中泛化关系用带空心三角形的直线表示,箭头指向基类:
设计模式学习笔记(二):UML与面向对象设计原则

6 接口与实现关系

很多语言比如Java,C#都有接口的概念,接口通常没有属性,所有是操作都是. = z g Z g i抽象的,只有操作的声明没有; , = d ; T Z操作的实现。UML中使用<<Interface>&4 p Jamp;gt;表示接口:
设计模式学习笔记(二):UML与面向对象设计原则
类与接口之间的实现关系使用空心三角形+虚线表示:

设计模式学习笔记(二):UML与面向对象设计原则

7 面向对象设计原则

7.1 概述

面向对象设计的目标之一是支持可维护性复用,一方面需要实现设计方案或者源代码的重用,一方面要确保系r v / F W统能够易于扩展和修改,具有较好的灵活性。面向对象设计原则由此诞生,它们蕴含于很多设计模式中,是从许多方f [ _ E U C t E案总结出来的指导性原则。常见的2 X l ? n7种面向对象设计原则如下:

  • 单一权责原则
  • 开闭原则
  • 里氏代换原则
  • 依赖倒转v t = h = X e &原则
  • 接口隔离G C ~ ( T原则
  • 合成复用原则
  • 迪米特法则

7.2 单一职责原则SRL [ sP

单一权责原则(Single Responsibility Principal):一个类只负责一个功能领域中的相应职责。
或者可以定义为:就一个类而言@ 7 ! 2 { f,应该只有一个引起它变化的原因。

单一权责原则的核心思想是:一个类不能太“累”。一个类(大到模块,小到方法)承担的职责越多,被复) s + v用的可能性~ D U y b L , m 2越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一$ 4 * l个职责变化时,可能会影响其他职责的运作,因此需要将职责分离,封装在不同的类中,即将不同的变化原因封装在不同的类中。单一权责原则是实现高内聚,低耦合的指导方针。

7.3 开闭原则OCP

开闭原则(Open-Closed Pr6 O X _ .inciple):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量不修改原有代码的情况下进行扩展。

其中软件实体可以是一个软件模块^ g Y H k,一个由多个类组成的局部_ N } m : K结构或者一个独立1 % ! | B z的类。
一个软件设计符合开闭原则,则可以非常方便地对其进行扩展,而且在扩展时无须修改现有代Z o p &码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延J ] 0 ] _ . W续性。

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。可以通过接h e R G s口,抽象类等定义抽象层,通过具体类进行扩展,修改系统的r 6 $ w M行为时无需修改抽象层,只需要增加新的具体类来实现新的; s [ p k 7 W 9业务功能即可,实I } % |现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

7.4 里氏代换原N * L E -则LSP

里氏代换原则(Liskov Substi# ) x Atution Principle):所有引用基类/父类的地方必须能透明地使用其子类的对象。D ; a u n X 0 )

简单地说就是父类出现的地方可以用子类代替,程序不会产生任何的错误和异常。使用里氏代换原则时,% | : d应该将父类设计为抽象类或者接口,让子类继承父类或实现父类7 ` w s } b e |接口,并实现父类中声明的方法,运行时,子类实例代替父类实例,可以4 : @ [ 0 ) D 很方便地扩展系统的功能,无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

7.5 依赖倒转原则DIP

依赖倒转原则(k D = j f n NDependency Inversion Principal):抽象不应该依赖具体细节,细节应当依赖于抽象,换言之要针对接口编程,而不是针对实现编程。

依赖倒转原则要求程序在源代: = r G B码中2 j H # l J传递参数时或者在关联关系中,尽量引用高层次的抽象层类,即使有接口和抽象类进行变量类型声明,参数类型声明,方法返回类型声明以及数据类型的转换等,而不是用具体类来做。一个具体类应该只实现接口或者抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用在子类中新增的方法。

在实现依赖倒转原则时,需要针对抽象层进行编A U & y . ` j :程,而将具体类的对象通过依赖注入(Dependency In= # 1 E S ] - u djection)的方式注入到其他对象中D & w i $ O。依赖注入是c i U @ | J X O f指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式包括- K V r @ }

  • 5 / Y造注入:通过构造函数来传入具体类的对象
  • 设值注入(setter注入):通过seS l q z b . O Itter来传入具体类对象
  • 接口注入:通过实现在接口中声明的方法来传入具体类对象

上面的方法在定义时使用抽象类型,在运行时传入具体类型的对象,由子类对象来覆盖父类对o k 9 { 4 L a w象 。

7.6 接口隔离原则ISP

接口隔离原则(Interface Segregation Principal):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖于那些它不需要的接口。

也就是说,当一个接口太大时需要划分为更小的接口{ N Z [ S 7 = b q,使用该接口的客户端仅需知道与之相关的方法G % 1 n f ? [ s。每一个接口应该承担一种相对独立的角色,这里的接口有两层意思:

  • 一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象
  • 另一种是指某个语言具体接口的定义,有严格的定义和结构,比如Java中的interface

ISP对两种不同含义的表达方式有所不同:

  • 当接口理解成一个类型所提供的所有方法特征的集合时,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分,可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此, 3 b q D = #时这个原则可以叫“角色隔离原则”
  • 把接口理解成狭义的特定语言的接口,ISP表达的意思是接口仅W 3 =仅提供客户端需要的行为,客户端不需要) _ -的行为% * 8则隐藏起来,应当为客户端提供尽可能小的接口,而不提供大的总接口。接口应尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块h ! G x =或者业务逻辑类)所需的方法即可,这种机制也叫“定制服务”

使用接口隔离原则时,注意控制接口的粒度:

  • 接口太小导致接口泛滥,不利于维护
  • 接口太大将违背ISP,灵活性差,使用不方便

一般而言接口中仅包含为某一类用户定制的方法即可。

7.7 合成复用原则CRP

合成复用原则(Composite Reuse Principal):尽量使用对象组合而不是继承来达成复用目的。

合成复用原则又叫组合/聚合复用原则(Composition/Aggr5 C n x L + Tegs R t :ate Reuse Principal),就是在一个新对象中通过关联关系(组g p * ? 4 ~ / P ;合/聚合)对对象进行重用4 3 Q (而不是使用继承。

面向对象设计中,可以通过两种方法在不同环境中复用已有的设Y b B B n 4 / g !计和实现:

  • 继承
  • 组合/聚合

7.7.1 继承

继承需要严格遵循里氏C O Y代换原则,有效8 + L w k Z使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度。继承主要带来的问题是会破环系统的封装性,因为继承会将基类实现细节暴露给子类,由于基类Z U g 2 G D d *内部细节对子类可见,因此叫“白箱复用”。一般而言两个类之间的关系是“Is-A”关系就可以使用继承。

7.7.2 组合/聚合

尽管可以通过继承来对代码进行复用,一般来说优先考虑组合/聚合。组合/聚合可以使系统更加灵活,降低类与类之间的耦合度。由于新对象可以直接调用已有对象的功能,这样做可以使成员对象的内部实现细节对新对象不可见,所以这种复用叫“黑箱复用”。一般而言两个类之间的关系是“H= H } R j 0 : 1 nas-A”关系就可以使用组合/聚合。

7.8 迪米特法则LoD

迪米特法则(Law of DemP W & 8 ( peter):一个软件实体应当尽可能少地与其他实体发生相互作用。

迪米特法则又叫最少知识u H 5 e 9原则(Least Knowledge Principal,LK. w E V ^P),迪米特法则会对软件实体之间通信的宽度与深度进行限制,可以降低系统的耦合度,使类与类之间保持松耦合。
迪米特法则还有几种定义形式:不要和“陌生人”说话,只与直接朋友通信。对于一个对象“朋友”可以是以下几类:

  • 对象本身(this)
  • 以参数形式传入的对象
  • 成员对象
  • 如果成员对象是一个集合,那么集合中的元素也是4 : S“朋友”
  • 当前对象所创建的对象

满足上述条件之一即是“朋友”,否则就是“陌生人”,不能和“陌生人”发生直接交互。
迪米特法则要求设计系统时尽量减少对象之间的交互,通过引入一A ( 5 X j . J个合理的中间类来降低现有对象之间的耦合度。应用迪米特法则时需要注意几点:

  • 优先将类设计为不变类
  • 类划分上尽j W g f G创建松耦合的类
  • 类结构设计上尽量降低成员变量和成员函数的访问权限
  • 在对其他类的引用上,一个对象对其他对象的引用应当降到最低

8 总结

设计模式学习笔记(二):UML与面向对象设计原则

设计模式学习笔记(二):UML与面向对象设计原则

如果觉得文章好看,欢迎点赞。

同时欢迎关注微信公众号:氷泠之路` c B M

设计模式学习笔记(二):UML与面向对象设计原则