nxf-rabbit75

统一建模语言(Unified Modeling Language,UML)是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计种的动态与静态信息。
UML,从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等9种图。
image

一、类图

1.类图概述

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类,类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。

2.类图的作用

在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解。
类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。

3.类图表示法

3.1类的表示方式

在UML类图中,类使用包含类名、属性(filed)和方法(method)且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和address这三个属性,以及work()方法。
image
属性/方法名称前加的加号和减号表示这个属性/方法的可见性,UML类图中表示可见性的符合有三种:

  • +:表示public
  • -: 表示private
  • #: 表示protected

属性的完整表示方式是:可见性 名称 :类型 [ = 缺省值]
方法的完整表示方式是:可见性 名称(参数列表) [ : 返回类型]
注意:
1.中括号中的内容表示是可选的
2.也有将类型放在变量名前面,返回值类型放在方法名前面
举例:
image

3.2类与类之间关系的表示方式

3.2.1关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为关联关系、聚合关系和组合关系
关联又可以分为单向关联,双向关联,自关联。
(1)单向关联
在UML类图中单向关联用一个带箭头的实线表示。下图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
image
(2)双向关联
从下图中看出,所谓的双向关联就是双方各自持有对方类型的成员变量。在UML图中,双向关联用一个不带箭头的直线表示。下图中在Customer类中维护一个List,表示一个顾客可以购多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。
image
(3)自关联
自关联在UML类图中用一个带有箭头且指向自身的线表示。下图的意思就是Node类包含类型为Node的成员变量,也就是"自己包含自己"。【LinkedList底层用到了自关联】
image

3.2.2聚合关联

关联关系的一种,而且它是强关联关系,是整体和部分之间的关系。如下图所示,以大学和老师为例,大学属于整体,老师就属于部分。
image
关于聚合关系,我们需要知道聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。从上图中可以看到,在大学里面,是有成员的,而且成员还是另外一种类型(即老师)的对象,只不过它是放在了一个集合里面,所以我们才说聚合关系是通过成员对象来实现的;其次,由于大学是一个整体,而老师是部分,也即成员对象,所以很显然成员对象是整体对象的一部分;最后,一个大学肯定会有多位老师,如果这个大学停办了,或者说这个大学没落了,那么老师是还依旧存在的,他还可以到其他的学校继续任职,所以我们才说成员对象可以脱离整体对象而独立存在。在UML类图中,聚合关系可以用带空心菱形的实线来表示,而且菱形指向整体。在上图中,老师是属于部分,大学是属于整体,所以空心菱形就得指向于大学这一边

3.2.3组合关系

组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,头属于整体,嘴属于部分,没有了头,嘴也就不存在了,因为嘴是依赖于头而存在的。
在UML类图中,组合关系用带实心菱形的实线来表示,而且菱形指向整体。对于组合关系而言,如果整体不存在了,那么部分肯定不复存在了。
image

3.2.4依赖关系

依赖关系是一种使用关系(在我们平时定义类的时候特别常用),它是对象之间耦合度最弱的一种关联关系,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另外一个类(被依赖类)中的某些方法来完成一些职责。
在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关联关系图,司机驾驶汽车:
image

3.2.5继承关系

继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。当两个类有了继承关系之后,如果我们在父类中定义了一个成员,不管是成员变量还是成员方法,那么子类都会继承过来,所以耦合度就特别高。
在UML类图中,泛化关系也被称为继承关系(也可以说泛化关系表示的其实就是继承关系),只不过这一块我没有单独去说它。那么,泛化关系在类图中是如何来表示的呢?泛化关系在UML类图中是用带空心三角箭头的实线来表示的,而且箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系(或者说继承关系),这个想必大家应该很熟了。例如,Student类和Teacher类都是Person类的子类,其类图如下图所示。
image

3.2.6实现关系

实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图所示。在Vehicle上面有一个<<Interface>>这样的标识,这表明了Vehicle是一个接口,而且,该接口又有两个子实现类,只因它们上面没有声明<<Interface>>。
image

二、软件设计原则

1.开闭原则

开闭原则就是对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
比如笔记本会预留有一些USB接口,不管是U盘还是一些什么其他的外接设备,例如鼠标、键盘,我们都可以随插随用了,即实现了一个热插拔的效果,也更加方便我们进行扩展。
image

2.里氏代换原则

里氏代换原则指任何基类可以出现的地方,子类一定可以出现。可以理解成子类可以扩展父类的功能,但不能改变父类原有的功能,指的就是在Java里面通常都会有父子类的关系,一般而言,我们都会将子类中的功能抽取到父类中,以提高代码的复用性,而在子类中,我们只需要去定义子类特有的功能即可。
换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。为什么呢?因为如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性就会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
你想啊,要是在父类中已经声明了一个方法,而你又在子类中再进行了一个重写,那么在父类中定义的方法是不是就没有任何意义了?如果说父类定义规则,要求子类必须重写,那么在父类中只需要定义成抽象的方法就可以了。
image

3.依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
image
image

4.接口隔离原则

接口隔离原则是指客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上面。
image
image

5.迪米法特原则

迪米特法则,又叫最少知识原则。其表示的含义是只和你的直接朋友交谈,不跟"陌生人"说话。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
举个例子,如果一个人想要租房的话,那么他找的就该是房屋中介了,而不是直接的房主;再来看这样一个例子,明星和粉丝进行见面的话,肯定是由经纪人来进行安排的。
最后,再看一个例子,假设现在有家公司需要一个办公软件,那么这家公司是直接找具体的软件工程师,还是找某家开发软件的公司啊?肯定是软件公司,而软件公司又会有许多软件工程师,所以它会把甲方需要的办公软件交给具体的软件工程师去开发。其实,软件工程师在去开发软件的时候,很多情况下,他是压根就不知道到底是哪个甲方需要这个软件的,因此他是不需要直接去和甲方沟通的。如果要沟通的话,那么可以通过第三方转发进行沟通。也就是说,软件工程师把他在开发中遇到的问题告诉公司,公司再跟甲方进行沟通,这样,就可以降低软件工程师和甲方之间的耦合度了,继而提高模块的相互独立性。
image

6.合成复用原则

合成复用原则是指尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用可分为继承复用(使用继承实现代码的复用性)和合成复用(使用组合或者聚合实现代码的复用性)两种。
(1)继承复用
优点是简单、易实现。缺点是继承复用破坏了类的封装性。为什么说破坏了类的封装性呢?因为继承会将父类的实现细节暴露给子类,也就是说子类可以直接去继承父类中的功能,这样,子类就可以将父类中的功能给覆盖掉了,所以,父类对子类是透明的。其实,这种复用我们又可称为"白箱"复用。
子类与父类的耦合度高。为什么说耦合度高呢?因为我们之前就已经讲过了,继承它本身就比组合、聚合的耦合度高。这样,父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
继承限制了复用的灵活性。如何理解呢?从父类继承而来的实现是静态的,这是因为在编译时就已经定义好了,所以在运行时是不可能发生变化的。我们总不能说,在程序运行过程中,来解除子类和父类的继承关系吧!况且,这也是无法实现的,所以我们才说继承限制了复用的灵活性
(2)组合或聚合复用
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,那么新对象就可以调用已有对象的功能了。所以,它相比于继承复用有以下优点:维护了类的封装性。为什么说维护了类的封装性呢?因为成员对象的内部细节是对新对象不可见的,也就是说新对象不知道成员对象里面的具体的实现,但是可以调用其功能,所以这种复用又被称为"黑箱"复用。
对象间的耦合度低。我们之前就讲过,组合或者聚合本身就比继承的耦合度低。当我们真正去使用组合或者聚合复用时,我们可以在类的成员位置声明抽象父类或者父接口,这样,我们就能动态地去传递该抽象类或者父接口的子类对象了
复用的灵活性高。这种复用可以在运行时动态进行,也就是说如果我们要给成员变量进行赋值,那么我们就可以在程序运行的时候才对其进行赋值了。若成员位置声明的是抽象的类或者接口,则我们就可以传递该接口或者该类的子类对象了。
总之,这种复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的对象。
image

参考链接:
【1】使用Emacs敲出UML,PlantUML快速指南
【2】顺序图的语法和功能
【3】软件设计原则篇:开闭原则
【4】从零开始学习Java设计模式_李阿昀的博客-CSDN博客

分类:

技术点:

相关文章: