第十一章.软件工程(下)


第十一章.软件工程

第九节.面向对象设计

面向对象(OAA)的基本概念

对象
在开发的时候,我们会把人物这些现实东西都抽象为对象。像人物的一些特征变成对象当中的一些属性;比如某个人抽象成对象之前,他会有身高、年龄、体重等等数据,这些都是以属性的方式出现;还会有他自己的操作方法,意思就是这个人在这个系统当中能做些什么事情。

类(实体类、边界类、控制类)
类是把对象的共性抽取出来而形成的类。具体来讲,类又分为实体类、边界类和控制类这几个类别。

  • 实体类:往往跟数据对应的就是实体类,比方说咱们的系统当中要用到老师的信息,那么教室类就可以充当实体类,实体类它是跟数据相关的。
  • 边界类:就是一个系统会有他的边界,这个系统里面会有很多类,其中有一些类是在这个边界上面需要跟外界相连。它往往也不会超出边界的范围,它还是在系统之内,但是它会有职能要跟外界的系统进行交互,这就会成为边界类要有这种特征。
  • 控制类:类与类之间是需要衔接和控制,所以会有控制类。它是做衔接部件的。

抽象
没什么好讲的。

封装
什么是封装呢?通俗一点来讲,我们会把相关的数据把它封装在一起,比如说对象就有相应的封装机制,会把这一个对象相关的信息。封装起来的表现是什么呢?我们要去用这个对象,你不能够直接操作对象里面的数据,而需要通过对象的接口进行相关的操作。这就是封装的理念。那具体编写代码的层次,封装的理念就是我们会把有一部分的属性把它定义为私有的,你要操作这部分属性也可以用我们提供给外界的接口也就是公共的一些操作方法,比如说 set、get 方法来操作这个私有变量的情况,你不能够直接用对象.名字去操作这个属性,这就是不允许的。

继承和泛化
继承和泛化其实一回事,只是从不同的角度来看,有父类,有子类,子类会继承父类的相关特性,这是继承。如果说有多个类,他们有共性,我们把共性抽出来,形成了一个上层的类,这个过程称为泛化。通俗一点来讲就是广泛化,因为上面的这一个类它的适用范围肯定会更广一些。

多态
多态最为直观的表现就是 我做同样的操作,但是控制的可能是不同的对象,那么它的操作会有差异会表现出不同的做法。比如说有动物类,底下有鱼猫狗之类的。它们的运动我们用 run()这个函数来表示。那动物这个类里面就已经有 run()函数,在鱼猫狗这些种类里面会具体地把它们如何运动给描述出来。我们知道猫和狗都是走,而鱼是游。如果说我们创建了一个对象集,这些动物都有鱼有猫有狗,我们要控制它的运动。我定义一个变量指针,这个指针类型是鱼能不能够用这个指针来操控狗和猫了,那是不行的。所以这个时候我们往往会定义一个动物类型的指针,这种指针既可以指向猫,也可以指向狗,还可以指向鱼。那表现形式就是指针.run() 就可以控制当前它指向的这个动物运动。在这个过程中如果说指针指向的是鱼,这个 run 表示游,如果说指针指向的是猫和狗,这个run表示走。所以说看上去是完全一样的形式,但是表现出了不同的状态。这就是多态的含义。如果是在Java中,这个指针就是父类对象,指向创建的子类对象。

多态分类
一般将多态分为通用多态和特殊多态。通用多态包括参数多态和包含多态。
参数多态采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。
包含多态同样的操作可用于一个类型及其子类型。 (注意是子类型,不是子类。) 包含多态一般需要进行运行时的类型检查。如Pascal中的子界。
特殊多态包括强制多态和过载多态。
强制多态编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中 基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态
过载多态是一种特定的多态,指同一个名(操作符、函数名)在不同上下文中可代表不同的含义。


接口
什么是接口了?首先我们给它定性,接口是一种类,但是它是一种特殊的类,特殊在哪里了?这种类只有方法的定义,没有方法的实现,相当于它定义的方法都是空框框。

消息
消息就是进行对象之间进行交互的时候所采用的机制,它走的是异步的方式在传输的。对象之间的通信都是走的消息的模式。

组件
组件就是构建

模式和复用
提出模式的概念本身就是为了复用。后面我们会详细讲到模式,因为有架构模式、设计模式之类的。其实模式就是一种经验的传承。我们解决这类问题以前用到了这种方式,现在我们把它归总起来,进行相关的一些规范化的调整,就形成了一种模式,以后针对这种问题我都用这种模式去解决。这就是模式的基本思路,根据不同的抽象级别又可以分为了架构模式、设计模式,还有惯用法之类的。


面向对象开发各阶段划分及任务

面向对象分析阶段:认定对象,组织对象,对象间的相互作用,基于对象的操作。
面向对象设计阶段:识别类及对象、定义属性、定义服务、识别关系、识别包。
面向对象程序设计:程序设计范型、选择一种OOPL。
面向对象测试:算法层、类层、模板层、系统层。


OOA 设计原则

面向对象设计原则在设计的过程中非常的实用。另外一方面我们后面会讲到设计模式,其实设计模式就是利用到这些设计原则来解决一些实际问题的解决方案。所以我们在设计模式里面是能够看到这些设计原则的一些影子的。

单一职责原则:设计目的单一的类
目的单一的类就是指的我们做一个类出来,这个类它的目的应该只能解决一个方面的问题,这就符合单一值的原则。为什么要考虑这样子来做呢?是因为如果说一个类它的职责越多,那它和其他类关联的可能性其实是越大的。这就好比我们日常生活中,你在公司里面管的事务,只管一个事务,让你跟你打交道的人会比较少。当你管多个业务线的业务的时候,你和相关的人员去沟通的时候,这个相关人员的群体就会要大得多。所以说我们的类如果说职责比较单一,会降低整个程序的耦合程度。

开放-封闭原则:对扩展开放,对修改封闭
对扩展开放,对修改封闭是什么意思呢?就是我们不要去做程序的修改,要的话就做程序的扩展。因为我们在维护的过程中经常要加一些新的功能,加新功能的第一种方式就是在原有的类的基础上把这个类改得更加强大一些,它支持的东西更多一些。另外一种方法就是我不去改原来的东西,新的功能用新的类去实现。故在开闭原则里面,该原则希望我们用扩展的方式,用新的类来解决问题,而不要去修改原有的东西。为什么呢?因为你在修改程序的时候很容易引入错误,引入错误影响到的不仅仅是新的功能,还会影响到原有的功能,这就带来了一些不安全的因素隐患。所以要执行开闭原则,你不去修改这个问题就可以得以避免。

李氏(Liskov)替换原则:子类可以替换父类
为什么子类可以替换父类呢?因为面向对象体系当中实际上是有着继承关系在里面的。子类继承父类,子类是拥有父类相关的特性,所以子类是可以替换父类去做相关的事情的,因为子类可能比父类还要懂得更多一些,但是父类懂的子类都懂,所以子类可以给父类顶班替班。为什么强调这一点是因为虽然有继承机制在,但是有的时候我们可能子类不见得能够替换父类。什么情况呢?就是当你把子类当中的一些方法做重载做覆盖之后,这个时候子类相同的函数和父类的起到的职能不一样,这个时候子类就不能够替换父类了。所以李氏替换原则希望我们做到的是你不要去盲目的重载去修改父类的方法,这样子会有安全隐患,因为你修改了别人不知道别人只看到他们之间有继承关系,就认为子类可以替换父类,那样子会出错。所以有这个原则就能够避免一些问题,让你设计的时候时刻记得要当子类能够替换父类时,你就不会去做重载这件事情。在我们的开发当中也确实这么去做的。为什么呢?你像接口的这种机制就是这样子引入过来的,子类和父类如果说有重载就会导致一些问题,那如果说这一个父类是一个接口,问题就不再存在了。

依赖倒置原则:要依赖于抽象,而不是具体实现;针对接口编程,不要针对实现编程
该原则要表达的是什么意思呢?意思就是我们在进行设计的时候,那你要去依赖于接口,不要依赖于具体的实现类。依赖于接口有什么好处呢?依赖于接口,它能够让我们的操作很灵活,而且不受太多的制约。举个简单的例子,在硬件领域,比方说我们的计算机,它就是一种针对接口编程而不针对实现编程的。为什么这么讲?我们拆开计算机会发现里面有很多标准接口,比如说 PCI 接口、VGA接口等等这么一系列的接口。那么这些接口是怎么做的吗?主板上面有插槽,显卡或者其他的声卡之类的板卡上面有那种金手指的这个插条。如果说要把两者对接,那就是直接把显卡或者声卡或者网卡插到插槽里面进行对接。这说明两个部件它们要关联结合在一起的时候是用到了接口。而类似的情况在电视机里面他就不会这么去做,部件与部件之间往往是焊接的方式。那这种就属于针对实现编程紧耦合的方式。那么两者是各有千秋的。为什么呢?像计算机的这种体制都是用的接口,那么你要做一些调整和改变就会比较方便,要扩展也比较容易。比如说你要升级显卡,那你把显卡拔下来换一块上去就可以了,而电视机就没这么方便了,那是要通过焊接。那为什么电视机不做成那种可拔插可更换式的呢?因为我们的用户习惯当中电视机是不用做部件的升级的,所以不需要这么去做。而在我们的开发过程中,在开发过程中的话,软件的部件是随时可能升级的。所以如果说你是紧耦合是针对实现编程,那你一个小的部件要做调整,那会很麻烦。但如果针对接口了,这个问题就迎刃而解。

接口隔离原则:使用多个专门的接口比使用单一的总接口要好
为什么这种原则要好呢?这其实就是接口的单一职责的目的。就一个接口我只做一件事情,那就能够把问题简单化,不容易出错,不会因为职责过多而导致一些问题一些疏漏。

组合重用原则:要尽量使用组合,而不是继承关系达到重用目的
为什么尽量不使用继承关系呢?因为继承它是一种紧耦合关系,所以我们要避免去用它继承。为什么继承是紧耦合关系呢?因为父类一变,子类也都要跟着变,这就自然紧耦合。

共同封闭原则:包中的所有类对于同一性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包产生影响,则将对该包里的所有类产生影响,而对于其他的包不造成任何影响

共同重用原则:一个包里的所有类应该是共同重用的。如果重用了包里的一个类,那么就要重用包中的所有类

迪米特(Demeter)原则(最少知识法则):一个对象应当对其他对象有尽可能的了解
最少知识法则的基本思路就是一个对象,他要尽可能少的对其他对象有了解。这就跟我们之前讲到的信息隐蔽是一回事,只有你对另外一个对象了解非常之少,你才能够用标准的方式去调用它。如果说你知道的越多,你就越容易绕开规则,直接操作对象里面的属性方法之类的,这不太合适。那么最少支持法则是通过什么来实现的呢?往往就是通过封装,我们把一个对象给封装起来。封装起来之后,对象里面的属性方法可以私有的,私有的属性方法外界是看不到的。

总结
这就是面向对象的一系列原则。其实在面向对象的设计过程中,我们一直要记得这些原则,否则了开发出来的程序往往会存在这样或那样的一些问题,比如说可扩展能力不强,比如说可修改性不强等等这一系列的问题。所以这些原则是要把握好的。在考试的时候,这些原则以选择题的形式出现的比较多。然后往往问到哪些说法是对的,哪些说法是不对的,或者说了给你一个原则,给你一系列描述,问这些描述是否符合原则的这一个基本的思想。所以对这些原则要有一定的认识。


OOA - UML

UML是一个非常重要的知识点,考察频度也很高。在这里我们要了解的东西主要是 UML 的一系列的图,这是最核心的。其次是要对这几种关系有一定的了解。好下面我们来逐步展开来进行相关的讲解。
在这里插入图片描述
1.UML 是分三个部分——构造块、规则和公共机制。其实规则和公共机制几乎考试就不会涉及到,所以我们不会去讲它。然后构造块又分为事物、关系和图,主要考的是关系和图。所以我们重点讲的就是关系和图。

2.首先来看到图,因为图是 UML 的主体部分。
UML 建模做的是什么呢? 无非就是用到他提供的这些图工具,把相关的项目的一些情况用这种图来表达和表示 称之为建模。所以这些图是非常重要的,这些图相当于给我们提供了一个工具集。

在UML 2.0 里面的图集总共有14个图,在 UML 1.0 里面有 9 种图。这些图首先是分成两大块,一部分称为结构图,另外一部分称为行为图。也可以讲,一部分是静态图,还有一部分是动态图。
故我们只需要了解了解 哪些图属于静态图,哪些图属于动态图,哪些图是结构图,哪些是行为图,这是第一个梯度要掌握的信息。图中已经标出图的信息。

这里特别值得说明的一点是,用例图是在区分它是动态图还是静态图的时候存在分歧的一种图。我们查阅资料会发现,有些地方把用例图归为行为图,动态图,有些地方又把它归为静态图,结构图。所以在考试的时候,如果说遇到要你分清以下图属于静态图的是哪个,不属于动态图的是哪个?那你首先不要给用例图定性,你先来找其他图,因为其他图都是有明确分类的,把其他图全部明确了之后再来看题目的意思进行分析,这样子就不容易出错。因为在考试的时候把它归为动态还是静态,其实没有完全一致的讲法,大部分时间归成了动态,也有小部分情况把它归成了静态。所以这一点大家要了解清楚,否则你懂了知识,但是不能够得分。

3.对于常见的结构(静态)图的基本情况要有一个了解。

  • 类图它所表达的是类和类之间的关系。
  • 对象图就是对象与对象之间的关系。
  • 包图就是展示包与包之间关系以及包内部的结构。
  • 然后像构建图什么之类的都有类似的特性。

在所有的静态图里面唯一一个描述上面会有区别的是部署,因为其他的基本上都是谁和谁之间关系。而部署图所讲述的是软件的构建,应该部署在哪个硬件的节点之上,表达的是这个信息。所以相对来讲所有的结构图都还是比较简单的。

4.对于常见的行为(动态)图的基本情况要有一个了解。行为图也是各有各的特色,各有各的特征。

  • 用例图(考得比较多)表达的是系统和外部的交互关系。因为用例图最简单的一个图示,就是一个小人一样的符号,然后会跟系统内部的一些功能有交互。所以说这就是外部的角色或参与者跟系统内部的一种交互关系。
  • 顺序图/序列图 一定要强调按时间顺序,否则就无法标识它是哪一种图。
  • 通信图/协助图 没有强调时间顺序。
  • 定时图(考得比较少)
  • 状态图表达的是状态的变迁转移的情况。
  • 活动图它跟流程图的结构是一致的。

这是这些比较有代表性的常考的一些动态图基本的情况,其他没有做详细解释的,也就不用去管它了。

对UML的误区
一个就是这个 UML 这么多种图,你能不能够用一个实例把这些图都给他穿起来,然后告诉我们怎么去用它,这本身就有误区。如果一个项目把各种图用起来才算是真正掌握了 UML 用到了 UML 了吗?这种思想也非常极端。为什么呢?UML 就是一个工具箱、一个工具集。那你做什么事情就需要用到不同的工具,没有人会说你必须要把所有工具用在同一个场景里面才叫效果好,没有这种讲法。这就好比我们学了这一个中文汉字之后写文章,是不是要把所有的汉字用上才叫好文章呢,显然不是这样子的,关键是你如何去用它把相关的内容用到恰到好处。


OOA 设计模式的概念

在了解设计模式基本概念的时候,我们引入一组模式来进行区别对待和分析。这一组分别是架构模式、设计模式和惯用法,这是从高中低三个复用级别来进行分类的。

  • 架构模式它属于软件设计中的高层决策。什么叫高层决策呢?就是从全局来看待问题,分析问题得出的结论。例如 C/S 结构就属于架构模式,还有包括我们之前所讲到的 B/S 、SOA等等这么一系列,它们都属于从整体全局的角度来看的方案。因为所谓模式其实就是以前成功方案的应用和复用。
  • 设计模式主要关注软件系统的设计,具体就是局部的设计问题,与具体语言无关。这就类似于了我们在建房子的时候,整体的框架是属于架构模式。但是具体到门、阶梯、斜坡这个时候就是设计模式管的事情,再到一套房子如何去分割之类的,这也是设计模式考虑的问题。即它考虑的层次要比架构模式要低一个层次,往往是在哪个级别用到,是在进行构建的设计的时候会用到设计模式。
  • 惯用法是最低层的模式,关注软件系统的设计与实现,实现时通过某种特定的程序设计语言来描述构件与构件之间的关系。每种编程语言都有它自己特定的模式,即语言的惯用法。例如引用-计数就是C++语言中的一种惯用法。从哪里可以看出它的层级比较低呢?从它跟语言相关这一点可以看出来。因为设计模式与语言无关的,比如说工厂方法,那你用 C sharp 语言或者说 Java 语言,其实都是可以做到工厂方法这种模式的,惯用法就不一样了,它跟语言相关,比如说引用计数就是 C++语言中的一种惯用法。

所以我们要区分惯用法和设计模式,主要就是看它与语言是否相关。要区分设计模式与架构模式,主要是看它是从全局走的还是从局部来分析看待问题的,这是设计模式这一块基本的一个概念。


OOA 设计模式的分类

ps:常考,23种模式都是重点。
在这里插入图片描述
设计模式多种多样,但是总的来讲可以分成三种类别,第一种是创建型模式,第二种是结构型模式,第三种是行为型模式。

1.创建型模式就是指的用于创建对象的模式,它为设计类实例化新对象提供指南。那么我们知道对象的产生其实是用 new 这样的关键字,可以很简单很方便的产生。为什么需要设计模式来掺和这个事情呢?这是因为简单的用 new 的方式来产生对象往往灵活度不够,然后在一些场景之下要费比较大的功夫才能够完成和达到我们需要的效果。所以就诞生了一系列的创建型的模式。比如说工厂方法,工厂方法就是构建的一个工厂,你只要给工厂下发指令,就能够生产出你想需要用到的这一种对象。那这个时候你如果说要不同类型的一些对象,你只要改工厂方法里面的内容就可以了,而不需要去调整原有的业务逻辑这一块的代码。所以就相当于把创建对象这件事情把它脱离出来,这对于我们维护肯定是有好处的。像抽象工厂可以生产按系列生产相应的对象,原型模式,还有单例模式、构建器模式,这些都属于创建型模式。后面我们会逐一的进行讲解。

2.结构型的模式主要是处理类或对象的组合问题,让类或对象形成更大的结构,提供相应的一些指导。结构型模式典型的有适配器模式、桥接模式、组合模式等等,他们各有各的一些特色,比如说适配器模式就可以通过这样的模式进行统一的操作,就可以把原来不统一的东西统一起来。这就跟我们的电源适配器差不多。家用电用到的是 220 伏的交流电,而手机上用到的是 4.7 伏的直流电,笔记本上面用到的往往是 12 伏的直流电,不同的地方用到的电有不同的差别。这个时候我们就要用到电源适配器来做转换工作。所以其实我们平常的所谓的充电器就是适配器。桥接模式、组合模式、装饰模式、外观模式等等都有各自的特点。后面会一一介绍。
主要意图是 将抽象部分与其实现部分分离,使它们都可以独立地变化。

3.行为型模式 包括职责链、命令解释器、迭代器等等。行为型的模式它主要了是用来描述类或者说对象交互的这个情况以及职责的分配的问题是在这一方面有他的一些方案,因为每一种模式都是一种不同的方案。

这是设计模式的分类。这些分类我们要搞清楚哪些是属于创建型,哪些是结构型,哪些是行为型,这就是设计模式要掌握的这些一系列模式当中第一梯度需要掌握的东西。


创建型模式

创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表示它的对象。
在这里插入图片描述
抽象工厂模式
图中的简要说明什么意思呢?就是抽象工厂它是做一系列产品。比方说我们在创建数据库相关的一些组件的时候,用到抽象工厂的话,那是什么情况呢?我可以指定我现在要创建的这一系列的操作数据的这些对象是用来操作 access 数据库的,那你就指定是由 access 工厂来进行生产。那你生产出来的所有的部件都是适用于 access 的。如果说你指定的工厂是 Oracle 的,那么所有的部件也都是 Oracle 的,就相当于了它只要指定系列名,而不需要去指定具体的类,就能够生产出这一个系列所对应的所需要的类的对象,这是抽象工厂模式。

构建器模式
图中的简要说明什么意思呢?就是指的咱们要构造某一个复杂的对象。那么这一个对象有可能是有多个对象所组合起来的。但是构造不同的最终的这个实例可能需要的部件不太一样。那这个时候我们可以用一个构建器模式把所需要的各个部分给它封装起来。然后对各个部分你可以指定不同的部件,最后形成你所需要的这一个实例。

工厂方法模式
图中的简要说明简单一点来讲,就是我们在用工厂方法来创建的时候,我可以在运行的时候去指定我要创建的具体是哪一个类的对象,所以就使得这一个实例化过程推迟了。

原型模式
原型模式也称为了克隆模型。那么这种方式是通过拷贝原有的这个原型对象来生成一个新的对象。但是咱们觉得新生成一个对象用 new 也很好。为什么要用拷贝现有的对象来生成新的对象呢?因为用 new 的方式来创建一个新的对象,问题就在于它所消耗的资源会比较多。如果说我们只是通过拷贝,由于是拷贝的内存区域,根本不用涉及到里面的逻辑是怎么样的,直接把内存 copy 一份出来。那这样子效率是最高的。这就好比我们在写代码的时候,你把原来的代码 copy 了一份,然后再稍微修改一下,得到我们需要的新的代码,这效率肯定是高一些,比你重新开头写的效率肯定要高得多。这就是原型模式用它主要考虑的是效率问题。

单例模式
单例模式基本思路非常简单,就是它保证一个类只有一个实例,无论你在系统当中哪个位置调用,它都是得到的这一个实例,不过会形成第二个实例,有很多时候咱们需要这样的场景,比方说现在了,在一个浏览器里面可以打开多个网页,其实就是不同的标签页。但是浏览器的主窗口只有一个,在这个里面就用到了单例模式。当你要打开一个新的标签页的时候,他会找到主窗口的句柄,之后在主窗口里面去开启一个新的标签页,这是单例模式基本的思想。
对于系统中的某些类而言,只有一个实例是很重要的。单例模式的意图就是保证一个类仅有一个实例,并提供一个访问它的全局访问点。


结构型模式

在这里插入图片描述
结构型模式比较多,我们主要会讲到的是常用的一些,而且为了大家方便记忆,对于比较有非常明显特色的一些这个设计模式了,我总结了它的速记关键字。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值