OOAD之设计原则

一、设计原则意义

为了设计出一个好的软件系统。我们必须遵照一定的规则。

衡量软件设计质量的首要标准是该设计是否能满足软件的功能需求。除了功能需求以外,还有很多衡量软件设计质量的标准,包括可读性、可复用性、可扩展性、可维护性等。

1、一般一个好的软件具有以下特点:

可读性:软件的设计文档是否轻易被其他程序员理解。可读性差的设计会给大型软件的开发和维护过程带来严重的危害。

可复用性:软件系统的架构、类、组件等单元能否很容易被本项目的其它部分或者其它项目复用。

可扩展性:软件面对需求变化时,功能或性能扩展的难易程度。

可维护性:软件维护(主要是指软件错误的修改、遗漏功能的添加等)的难易程度。

2、内聚度和耦合度标准:

内聚度:

表示一个应用程序的单个单元所负责的任务数量和多样性。内聚与单个类或者单个方法单元相关。好的软件设计应该做到高内聚。

理想状态下,一个代码单元应该负责一个内聚的任务,也就是说一个任务可以看作是一个逻辑单元。一个方法应该实现一个逻辑操作,一个类应该代表一种类型的实体。

内聚原则背后的主要原因是重用:如果一个方法或一个类只负责一个定义明确的事情,那么在不同的上下文环境中,它就能更好地被再次使用。
遵循该规则的另一个优点是,当一个应用程序的某些方面需要做出改变时,我们能够在相同单元中找到所有相关的部分。

如果一个系统单元只负责一件事情,就说明这个系统单元有很高的内聚度;如果一个系统单元负责了很多不相关的事情,则说明这个系统单元是内聚度很低。内聚度很高的系统单元通常很容易理解,很容易被复用、扩展和维护。

如果一个方法可以用简单的“动词+名词”的形式来命名(例如,loadFile()、getName()),或者如果一个类可以用准确的名词来命名(例如,Employee、Student),那么这样的类或者方法就是内聚度较高的系统单元;反之,如果类或者方法的名字必须包含“和”、“或”等字样才能准确反映其功能特性的话,这些类或方法的内聚度就一定不高。

耦合度:

耦合度表示类之间关系的紧密程度。耦合度决定了变更一个应用程序的容易程度。在紧密耦合的类结构中,更改一个类会导致其它的类也随之需要做出修改。显然,这是我们在类设计时应该避免的,因为微小的修改会迅速波动影响到整个应用程序。此外,找到需要修改的所有的地方是必须的,实际上就使得修改变得困难并且耗费时间。而在松散耦合的系统中,我们可以更改一个类,不需要修改其它类,而应用程序仍然能够正常工作。
概括起来,较低的耦合度和较高的内聚度,也即我们常说的“高内聚、低耦合”是所有优秀软件的共同特征。

如果一个软件的内聚度和耦合度都符合要求,它也就自然具备了比较好的复用性、可扩展性和可维护性。

二、7大设计原则

1、单一职责原则

单一职责原则(Single Responsibility Principle,SRP)是指:所有的对象都应该有单一的职责,它提供的所有的服务也都仅围绕着这个职责。换句话说就是:一个类而言,应该仅有一个引起它变化的原因,永远不要让一个类存在多个改变的理由。

类的职责是由该类的对象在系统中的角色所决定的。

一个类如果有多个职责,也有多个改变它的理由。反之,如果你能想到一个类存在多个使其改变的原因,那么这个类就存在多个职责。

2、开闭原则

开闭原则(Open-Close Principle,简称OCP)是指一个软件实体(类、模块、方法等)应该对扩展开放,对修改关闭。

遵循开闭原则设计出来的模块具有两个基本特征:

对于扩展是开放的(Open for extension):模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求。

对于更改是封闭的(Closed for modification):对模块行为扩展时,不必改动模块的源代码或二进制代码。

这两个特征看起来是相互矛盾的。扩展模块的行为通常需要修改该模块的源代码,而不允许修改的模块通常被认为是具有固定的行为。

实现开闭原则的关键在于抽象化。在Java中,抽象化的具体实现就是使用抽象类或接口。

2.1使用抽象类

在设计类时,对于拥有共同功能的相似类进行抽象化处理,将公用的功能部分放到抽象类中,而将不同的行为封装在子类中。这样,在需要对系统进行功能扩展时,只需要依据抽象类实现新的子类即可。在扩展子类时,不仅可以拥有抽象类的共有属性和共有方法,还可以拥有自定义的属性和方法。

2.2接口

与抽象类不同,接口只定义实现类应该实现的接口方法,而不实现公有的功能。在现在大多数的软件开发中,都会为实现类定义接口,这样在扩展子类时必须实现该接口。如果要改换原有的实现,只需要改换一个实现类即可。

3、里氏替换原则(The Liskov Substitution Principle,LSP)

在一个软件系统中,子类应该能够完全替换任何父类能够出现的地方,并且经过替换后,不会让调用父类的客户程序从行为上有任何改变。

里氏替换原则实现了开闭原则中的对扩展开放。实现开闭原则的关键步骤是抽象化,父类与子类之间的继承关系就是一种抽象化的体现。因此,里氏替换原则是实现抽象化的一种规范。违反里氏替换原则意味着违反了开闭原则,反之未必。里氏替换原则是使代码符合开闭原则的一个重要保证。

一般来说,只要有可能,就不要从具体类继承。在一个由继承关系形成的等级结构中,树叶节点都应当是具体类,树枝节点都应该是抽象类或者接口。

里氏替换原则是使代码符合开闭原则的一个重要的保证,同时,它体现了:

3.1类的继承原则

里氏替换原则常用来检查两个类是否为继承关系。在符合里氏替换原则的继承关系中,使用父类代码的地方,用子类代码替换后,能够正确的执行动作处理。换句话说,如果子类替换了父类后,不能够正确执行动作,那么他们的继承关系就是不正确的,应该重新设计它们之间的关系。

3.2动作正确性保证

里氏替换原则对子类进行了约束,所以在为已存在的类进行扩展,来创建一个新的子类时,符合里氏替换原则的扩展不会给已有的系统引入新的错误。

3.3对类的继承关系的定义

面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。

我们说类的继承关系就是一种“is-a”关系,实际上指的是行为上的“is-a”关系,可以把它描述为“表现为,act as”。

正方形在设置长度和宽度这两个行为上,与长方形显然是不同的。长方形的行为:设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到父类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。

3.4设计要依赖于用户需求和具体环境。

继承关系要求子类要具有基类全部的行为。这里的行为是指落在需求范围内的行为。

这里我们以另一个理解里氏替换原则的经典例子“鸵鸟非鸟”来做示例。生物学中对于鸟类的定义是“恒温动物,卵生,全身披有羽毛,身体呈流线形,有角质的喙,眼在头的两侧。前肢退化成翼,后肢有鳞状外皮,有四趾”。从生物学角度来看,鸵鸟肯定是一种鸟,是一种继承关系。但是根据上一个“正方形非长方形”的例子,鸵鸟和鸟之间的继承关系又可能不成立。那么,鸵鸟和鸟之间到底是不是继承关系如何判断呢?这需要根据用户需求来判断。

现在鸟类有四个对外的行为,其中两个行为分别落在A和B系统需求中,如下图所示。

ba9d6f76682c381c95882b13a7d7bb05.jpeg

A需求期望鸟类提供与飞翔有关的行为,即使鸵鸟跟普通的鸟在外观上就是100%的相像,但在A需求范围内,鸵鸟在飞翔这一点上跟其它普通的鸟是不一致的,它没有这个能力,所以,鸵鸟类无法从鸟类派生,鸵鸟不是鸟。

B需求期望鸟类提供与羽毛有关的行为,那么鸵鸟在这一点上跟其它普通的鸟一致的。虽然它不会飞,但是这一点不在B需求范围内,所以,它具备了鸟类全部的行为特征,鸵鸟类就能够从鸟类派生,鸵鸟就是鸟。

所有子类的行为功能必须和使用者对其父类的期望保持一致,如果子类达不到这一点,那么必然违反里氏替换原则。

4、依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,简称DIP)是指将两个模块之间的依赖关系倒置为依赖抽象类或接口。具体有两层含义:

4.1高层模块不应该依赖于低层模块,二者都应该依赖于抽象

4.2 抽象不应该依赖于细节,细节应该依赖于抽象

具体耦合关系:发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成。

抽象耦合关系:发生在一个具体类和一个抽象类(或接口)之间,使两个必须发生关系的类之间存有最大的灵活性。

面向接口编程:

在高层模块和低层模块之间定义一个抽象接口,高层模块调用抽象接口定义的方法,低层模块实现该接口。

依赖倒转原则的本质就是要求将类之间的关系建立在抽象接口的基础上的。通过上面的方式,将错误的依赖关系倒转过来,使具体实现类依赖于抽象类和接口。这就是依赖倒转原则中“倒转”的由来。

以抽象方式耦合是依赖倒转原则的关键。

5、组合/聚合复用原则

组合/聚合复用原则(Composite/Aggregation Reuse Principle,CARP)是指要尽量使用组合/聚合而非继承来达到复用目的。另一种解释是在一个新的对象中使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象委托功能达到复用这些对象的目的。

5.1组合/聚合复用

我们知道组合/聚合都是关联关系的特殊种类,二者都是体现整体与部分的关系,也就是两个类之间的是“has-a”关系,它表示某一个角色具有某一项责任。由于组合/聚合都可以将已有的对象加入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,从而实现对象复用。

使用组合/聚合实现复用有如下好处:

5.1.1新对象存取成分对象的唯一方法是通过成分对象的接口。

5.1.2这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。

5.1.3这种复用所需的依赖较少。

5.1.4每一个新的类可以将焦点集中在一个任务上。

5.1.5这种复用可以在运行时间内动态进行,作为整体的新对象可以动态地引用与部分对象类型相同的对象。也就是说,组合/聚合是动态行为,即运行时行为。可以通过使用组合/聚合的方式在设计上获得更高的灵活性。

当然,这种复用也有缺点。其中最主要的缺点就是系统中会有较多的对象需要管理。

一般来说,如果一个角色得到了更多的责任,就可以使用组合/聚合关系将新的责任委派到合适的对象上。

5.2继承复用

继承是面向对象语言特有的复用工具。由于使用继承关系时,新的实现较为容易,因父类的大部分功能可以通过继承的关系自动进入子类;同时,修改和扩展继承而来的实现较为容易。于是,在面向对象设计理论的早期,程序设计师十分热衷于继承,好像继承就是最好的复用手段,于是继承也成为了最容易被滥用的复用工具。然而,继承有多个缺点:

5.2.1继承复用破坏封装,因为继承将父类的实现细节暴露给子类。由于父类的内部细节常常是对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。

5.2.2如果父类发生改变,那么子类的实现也不得不发生改变。

5.2.3从父类继承而来的实现是静态的,也就是编译时行为,不可能在运行时间内发生改变,没有足够的灵活性。

正是因为继承有上述缺点,所以应首先使用组合/聚合,其次才考虑继承,达到复用的目的。并且在使用继承时,要严格遵循里氏替换原则。

要正确的选择组合/聚合和继承,必须透彻的理解里氏替换原则和Coad法则。里氏替换原则前面学习过,Coad法则由Peter Coad提出,总结了一些什么时候使用继承作为复用工具的条件。

5.3Coad条件全部被满足时,才应当使用继承关系

5.3.1子类是父类的一个特殊种类,而不是父类的一个角色,也就是区分“has-a”和“is-a”。只有“is-a”关系才符合继承关系,“has-a”关系应当用组合/聚合来描述。

5.3.2永远不会出现需要将子类换成另外一个类的子类的情况。如果不能肯定将来是否会变成另外一个子类的话,就不要使用继承。

5.3.3子类具有扩展父类的责任,而不是具有置换(重写)或注销掉父类的责任。如果一个子类需要大量的置换掉父类的行为,那么这个类就不应该是这个父类的子类。

5.3.4只有在分类学角度上有意义时,才可以使用继承。不要从工具类继承。

6、接口隔离原则

接口隔离原则(Interface Segregation Principle,简称ISP)是指客户不应该依赖它们用不到的方法,只给每个客户它所需要的接口。换句话说,就是不能强迫用户去依赖那些他们不使用的接口。

接口隔离原则实际上包含了两层意思:

6.1接口的设计原则

接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口,使用多个专门的接口比使用单一的总接口要好。

6.2接口的继承原则

如果一个接口A继承另一个接口B,则接口A相当于继承了接口B的方法,那么继承了接口B后的接口A也应该遵循上述原则:不应该包含用户不使用的方法。反之,则说明接口A被B给污染了,应该重新设计它们的关系。

6.3通过多重继承分离接口

多重继承可以有两个方式,第一种方式是同时实现两个接口,属于多重接口继承;第二种方式是实现一个接口,同时继承一个具体类,实际上也是一种多重继承。

6.4通过委托分离接口

使用聚合和组合来将部分实现交给已知类实现。自己在实现部分接口。

7、迪米特法则

迪米特法则(Law of Demeter,简称LOD),又称为“最少知识原则”,它的定义为:一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易。迪米特法则是对软件实体之间通信的限制,它对软件实体之间通信的宽度和深度做出了要求。迪米特的其它表述方式为:

7.1只与你直接的朋友们通信。

7.2不要跟“陌生人”说话。

7.3每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

7.4做为“朋友”的条件为:

7.4.1当前对象本身(this);

7.4.2被当做当前对象的方法的参数传入进来的对象;

7.4.3当前对象的方法所创建或者实例化的任何对象;

7.4.4当前对象的任何组件(被当前对象的实例变量引用的任何对象)。

出处:http://www.uml.org.cn/mxdx/201409044.asp

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1381421.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

ooad设计模式_OOAD-面向对象的分析与设计

ooad设计模式 让我们分为以下几节来讨论OOAD: (Lets discuss OOAD by dividing into the below sections:) OOS 操作系统 OOA OOA OOD 面向对象 Thumbnails principles of the object oriented design 面向对象设计的缩略图原则 OO Design Quality Metrics OO设计质…

OOAD复习笔记

OOAD学习目标: 1.A critical,fundamental ability in OOAD is to skillfully assign responsibilities to software components.(面向对象分析设计种至关重要的的能力是熟练地为软件部件分配职能) 2)GRASP patterns &#xff0c…

几款免费的数据恢复软件测试

目录 一、前言 二、测试正文 1、Windows File Recovery (不推荐) 2、Recuva 3、Undelete360 (最轻量级) 4、Glary Undelete 三、总结 一、前言 刚进回收站,清空后的,都能找到,还原后能正常…

一款非常好用且专业的免费万兴数据恢复软件

Wondershare Recoverit Mac版是一款非常好用且专业的免费万兴数据恢复软件,可以恢复所有文件类型,包括照片、视频、文档和其他文件。从所有数据丢失场景中检索数据,并从所有存储设备中恢复数据,并崩溃Windows系统或可启动问题!可靠…

【C++基础(十)】C++泛型编程--模板初阶

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C   🔝🔝 模板 1. 前言2. 函数模板3. 函数模板原理4. 函数…

PETool free分享

百度盘下载pe解析工具、PETool.1.0.0.5.exe(免费) 链接: https://pan.baidu.com/s/1A44RaB3GotWfmWmQCv3qqw 提取码: 33fh MD5: 5691CCA8BD8C5BDEE5F839098CE82A2A SHA1: 1DAD3D205405EB8E99BD39DAE891463C15F995CD 下载后请校验文件防篡改。 文件安全性…

CPABE

属性加密的基础是秘密共享。 什么是秘密共享? 秘密共享指的是dealer有一个秘密,他想要在w个人中分享这个秘密,但是他希望任何单个人都无法计算出(获得)这个秘密,也就是说他想任意t个人在一起才能够把这个…

Peers

Peers 区块链网络主要由一组peers节点组成。 peers是网络的基本要素,因为他们主持分类账和智能合约。 回想一下,分类账不可变地记录智能合约产生的所有交易。 智能合约和分类帐分别用于封装网络中的共享进程和共享信息。 peers的这些方面使他们成为理解H…

Protobuf-importimport public

【转载】https://www.cnblogs.com/letsgollc/p/7423248.html 场景:假如有文件hundredbulls.proto,需要导入另一个文件common.proto,两者在同一个目录中. 导入方式 在hundredbulls.proto文件的开头,使用关键字import导入另一个文件…

pump-probe技术简介

pump-probe技术简介 pump-probe定义介绍应用 pump-probe定义介绍 泵浦-探测(pump-probe)是一种利用短激光脉冲测量超快现象的技术。**当泵浦光照射在样品上时,可以激发出各种各样的物理现象,例如电子激发。随后,经过一…

记录更换若依框架的用户和部门两种表的过程

背景: 公司使用若依框架快速构建项目,客户那边原有的数据要同步过来,且要求字段与原先的字段一致,可以让数据丝滑无畅导入。用户表和部门表是基础在代码出现的地方比较多,该如何考虑去过度去更换) 如何快速…

【单片机/嵌入式】最完整学习路线

一.什么是单片机?什么是嵌入式?它们之间的区别与联系。 关于这个问题我在网上寻找到了相关文章解释得很详细,不了解的同学可以参考一下这篇文章:到底什么是嵌入式?什么是单片机? - 知乎 二.学习路线 一个人…

大学学习历程简单总结

一、主要学习经历: 2017年9月进入大学: 最开始自己对大学的认识是一种特别仰慕的感觉,并且当时自己认为在大学里面是做各种各样的研究和学习的,并且认为只有对科研特别喜欢而且拥有天赋的大学生才能去升学为研究生; 进…

“机器学习”名字的由来

阿瑟萨缪尔(Arthur Samuel, 1901-1990) 阿瑟萨缪尔是人工智能研究的先驱。 从1949年到1960年代后期,他在让计算机从经验中学习方面做了最出色的工作,而他的研究工具是跳棋游戏。(玩游戏的程序通常在人工智能研究中扮演果蝇在遗传学中所扮演的…

【Unity每日一记】让一个物体按余弦曲线移动—(三角函数的简单运用)

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

Bootstrap 样式之 文本颜色

Bootstrap 自定义的文本颜色 源码查看&#xff08;测试版本3.x&#xff09; ------------------------------------------------------------ 先来看看bootstrap自定义的文本颜色是什么样的&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh-C…

A. Red Versus Blue

https://codeforces.com/contest/1659/problem/A input 3 7 4 3 6 5 1 19 13 6output RBRBRBR RRRBRR RRBRRBRRBRRBRRBRRBRinput 6 3 2 1 10 6 4 11 6 5 10 9 1 10 8 2 11 9 2output RBR RRBRBRBRBR RBRBRBRBRBR RRRRRBRRRR RRRBRRRBRR RRRBRRRBRRR题意 T组询问&#xff…

new、new[]和new()

文章目录 new是怎么调用的&#xff1f;那么delete呢&#xff1f;new[]和delete[]为什么要成对使用&#xff1f;注意到了operator new和operator delete~new()怎么用&#xff1f;delete()有点复杂 new是怎么调用的&#xff1f; 这里是一条new的使用语句&#xff1a; A *pc ne…

Bootstrap颜色对应对照表

Bootstrap自带颜色&#xff1a;class "bg-xxx" bg-red红色bg-yellow黄色bg-aqua湖绿色bg-blue蓝色bg-light-blue浅蓝色bg-green绿色bg-navy藏青色bg-teal青色bg-olive橄榄色bg-lime荧光绿 bg-orange橙色bg-fuchsia紫红色bg-purple紫色bg-maroon红褐色bg-black黑色b…

BLE蓝牙

简介 重点了解GAP、ATT、Link Layer&#xff0c;其它有个简单认识即可 1. 什么是蓝牙主从关系&#xff1f; BLE蓝牙的角色有以下几种&#xff1a;广播者&#xff08;duAdvertise&#xff09;、扫描者&#xff08;Scanner&#xff09;、从设备zhi&#xff08;daoSlave&#x…