UML类图与面向对象编程

概念

面向对象程序设计(Object-Oriented Programming, 缩写为OOP)是一种范式,其基本理念是将数据块及与数据相关的行为封装成为特殊的、名为对象的实体,同时对象实体的生成工作则是基于程序员给出的一系列“蓝图”,这些“蓝图”就是

类层次结构

一些类可能会组织起来形成类层次结构

假如你的邻居有一只名为“福福”的狗。其实狗和猫有很多相同的地方:它们都有名字、性别、年龄和毛色等属性。狗和猫一样可以呼吸、睡觉和奔跑。因此似乎我们可定义一个动物(Animal)基类来列出它们所共有的属性和行为。

我们刚刚定义的父类被称为超类。 继承它的类被称为子类。子类会继承其父类的状态和行为,其中只需定义不同于父类的属性或行为。因此, 猫类将包含meow方法, 而狗类则将包含bark 方法。

假如我们接到一个相关的业务需求,那就可以继续为所有活的生物体( Organisms) 抽取出一个更通用的类, 并将其作为动物(Animal)和植物(Plants)类的超类。这种由各种类组成的金字塔就是层次结构。 在这个层次结构中, 猫 类将继承动物和生物体类的全部内容。

子类可以对从父类中继承而来的方法的行为进行重写。子类可以完全替换默认行为,也可以仅提供额外内容来对其进行加强。

基础概念

面向对象程序设计的四个基本概念使其区别于其他程序设计范式。

1. 抽象

抽象是一种反映真实世界对象或现象中特定内容的模型,它能高精度地反映所有与特定内容相关的详细信息,同时忽略其他内容。

当使用面向对象程序设计的理念开发一款程序时,你会将大部分时间用于根据真实世界对象来设计程序中的对象。但是,程序中的对象并不需要能够百分之百准确地反映其原型(极少情况下才需要做到这一点)。实际上,你的对象只需模拟真实对象的特定属性和行为即可,其他内容可以忽略。

例如, 飞行模拟器和航班预订程序中都可能会包含一个飞机(Airplane 类)。但是前者需包含与实际飞行相关的详细信息,而后者则只关心座位图和哪些座位可供预订。

2. 封装

封装是指一个对象对其他对象隐藏其部分状态和行为,而仅向程序其他部分暴露有限的接口的能力。

如果想要启动一辆车的发动机,你只需转动钥匙或按下按钮即可,无需打开引擎盖手动接线、转动曲轴和气缸并启动发动机的动力循环。这些细节都隐藏在引擎盖下,你只会看到一些简单的接口:启动开关、方向盘和一些踏板。该示例讲述了什么是对象的接口——它是对象的公有部分,能够同其他对象进行交互。

封装某个内容意味着使用关键字 private 私有 来对其进行修饰,这样仅有其所在类中的方法才能访问这些内容。还有一种限制程度较小的关键字 protected 保护 ,其所修饰的对象仅允许父类访问其类中的成员。

绝大部分编程语言的接口和抽象类(或方法)都基于抽象和封装的概念。在现代面向对象的编程语言中,接口机制(通常使用 interface 或 protocol 关键字来声明)允许你定义对象之间的交互协议。这也是接口仅关心对象行为,以及你不能在接口中声明成员变量的原因之一。

由于接口(interface)这个词代表对象的公有部分,而在绝大部分编程语言中又有 interface 类型,因此很容易造成混淆。在这里我将对此进行说明。

假 如 你 的 航空运输(FlyingTransport) 接 口 中 有 一 个fly(origin, destination, passengers) 方法 (即以起点、 终点以及乘客为参数的飞行方法)。 在设计航空运输模拟器时, 你可以对 机场(Airport)类做出限制, 使其仅与实现了航空运输接口的对象进行交互。 此后, 你可以确保传递给机场对象的任何对象——无论是 飞机 、 直升机(Helicopter )还是可怕的 家养狮鹫(DomesticatedGryphon)都能到达或离开这种类型的机场。

3. 继承

继承是指在根据已有类创建新类的能力。

继承最主要的好处是代码复用。如果你想要创建的类与已有的类差异不大,那也没必要重复编写相同的代码。你只需扩展已有的类并将额外功能放入生成的子类(它会继承父类的成员变量和方法)中即可。

使用继承后,子类将拥有与其父类相同的接口。如果父类中声明了某个方法,那么你将无法在子类中隐藏该方法。你还必须实现所有的抽象方法,即使它们对于你的子类而言没有意义。

在绝大多数编程语言中,子类仅能对一个父类进行扩展。另一方面,任何类都可以同时实现多个接口。但是正如我之前提到的那样,如果父类实现了某个接口,那么其所有子类都必须实现该接口。

4. 多态

多态是指程序能够检测对象所属的实际类,并在当前上下文不知道其真实类型的情况下调用其实现的能力。

让我们看一些动物的例子。绝大部分动物(Animals)可以发出声音。我们需要所有子类都重写基类的发出声音(makeSound)方法,让每个子类都发出正确的声音,因此我们可以马上将其声明为抽象。这让我们得以忽略父类中该方法的所有默认实现,从而强制要求所有子类自行提供该方法的实现。

对象之间的关系

继承

一、继承关系 继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议性。在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。

img

实现

二、实现关系 实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。

img

依赖

三、依赖关系 简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。

img

关联

四、关联关系 关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标记。

img

聚合

五、聚合关系 聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。

img

组合

六、组合关系 组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,组合关系以实心菱形加实线箭头表示。

img

总结

七、总结 对于继承、实现这两种关系没多少疑问,它们体现的是一种类和类、或者类与接口间的纵向关系。其他的四种关系体现的是类和类、或者类与接口间的引用、横向关系,是比较难区分的,有很多事物间的关系要想准确定位是很难的。前面也提到,这四种关系都是语义级别的,所以从代码层面并不能完全区分各种关系,但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖。

        可以不使用关联关系画类图,除非你的类图可以比较粗糙。因为无论哪种标准下,关联关系都可以分为其他几种子类型。 UML标准中,关联只包含组合和聚合两种;C++标准中,关联仅表示有联系而已,只包含组合,聚合,依赖,继承(继承又包括实现和泛化,此处不讨论)四种关系。

        最简单的辨别方法,可以通过 语法 来辨别组合聚合以及依赖。

        “依赖”是最好区分的,如果A类只是使用B类,而没有持有B类的对象和指针,那么A就依赖B。如B作为局部变量,函数参数等。

        “组合与聚合”都需要类A中持有类B中的对象或指针:

        “组合”:

                1. 生命周期必须一致。

                2. 大部分时候,B类会以对象的方式存在,或者在A类中创建和销毁。

                3. 语义上有整体和部分的关系,如果实在看不出来就根据1和2判断

        “聚合”:事实上如果已经确定持有对象和指针的基础上,不是组合就是聚合。

                1. 生命周期可以不一致。

                2. 大部分时候,B类会以指针或引用的形式存在于A类中,很可能是在初始化或者某些接口中传入的。

                3. 语义上讲,聚合并不强调整体与部分的关系,并不是没有而是不强调,这种整体与部分的关系常常也不明显。所以如果分不清是不是整体与部分也无所谓,根据1和2判断或者排除法即可。

                4. 有时候两个类会相互持有对方的指针,这时候整体往往有更长的生命周期。

(更多的解释可以参阅以下两个帖子:C++类间关系之组合与聚集 - 知乎、类的六大关系——依赖和关联的区别_关联和依赖_夏诗曼CharmaineXia的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值