面向对象设计原则概述
软件的可维护性和可复用性
软件工程和建模大师Peter coad认为,一个好的系统设计与应该具备如下三个性质
可扩展性
灵活性
可插入性
软件的可维护性和可复用性
-
软件的复用和重用拥有众多优点,如可以提高软件的开发效率,提高软件质量,节约开发成本,恰当地服用还可以改善系统发的可维护性
-
在面向对象设计服用的目标在于实现支持可维护性的复用
-
在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,准学这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性
软件的可维护性和可复用性
面向对象设计原则和设计模式也是对系统进行合理重构的指南针,重构是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
面向对象设计原则简介
常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,他们相互依赖,相互补充。
设计原则名称 | 设计原则简介 | 重要性 |
---|---|---|
单一职责原则 | 类的职责要单一,不能将太多的职责放在一个类中 | **** |
开闭原则 | 软件实体退扩展是开发的,但对修改是关闭的,即在不修改个软件实体的基础上去扩展其功能 | ***** |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以接受子类对象 | **** |
依赖倒转原则 | 要针对抽象层编程,而不要针对具体类编程 | ***** |
接口隔离原则 | 使用多个专门的接口来取代一个统一的接口 | ** |
合成复用原则 | 在系统中应该尽量多使用组合和聚合管理关系,尽量少使用甚至不使用继承关系 | **** |
迪米特法则 | 一个软件实体对其他实体的应用越少越好,或者说如果两个类不必必测直接通信,不那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 | *** |
单一职责原则
单一职责原则定义如下下:
一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中。
另一种定义方式如下:就一个类而言,应该仅有一个引起它变化的原因
单一职责原则分析
- 一个类或者大到模块,小到方法,承担的职责越多,他被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责偶合在一起,当其中一个职责变化时,可能会影响其他职责的运作
- 类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,行为之者通过其方法来体现
- 单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计院人员发现类的不同职责并将其分离,而发现类的多重职责需要设设计人员具有较强的分析设计能力和相关重构经验。
单一职责原则
sunny软件公司开发人员针对某CRM(客户关系管理)系统中客户信息图形统计模块提出了如图1所示初始设计方案
CustomerData类承担了太多的职责, 即包含与数据库相关的方法,有办函与图表生成和显示相关的方法。
如果其他类中也需要连接数据库或者使用findCustiners()方法查询客户信息,则难以实现代码的重用。无论是修改数据库连接方式还是修改图标显示方式都需要修改该类,他不止一个引起他变化的原因,违背了,违背了单一职责原则。
(1) DBUtil: 负责连接数据库, 包含数据库连接方法getConnection();
(2) CustomerDAO**:** 负责操作数据库中的Customer表, 包含对Customer表的增删改查等方法, 如findCustomers();
(3) CustomerDataChart: 负责图表的生成和显示, 包含方法createChart()和displayChart()
练习
某基于java的C/S系统的登录功能通过如下登录类实现
先使用单一职责原则对其进行重构
开闭原则
开闭原则定义如下
一个软件实体应当对扩展开放,对修改关闭,也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
开闭原则是面向对象的可复用设计的第一会基石
任何软件都需要面临一个很重要的问题,他们的需求会随时间的推移而发生变化,当软件系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。
小宋参加工作不久,买了套小房子,房间里有点灯,他希望设计一个智能控制器控制电灯的开馆
小宋后来又陆续买了很多电器,他希望智能控制器都能控制这些 电器开关
Class 控制器{
void 启动(){
灯.打开();
电视.打开();--改动点1
洗衣机.打开();--改动点4
计算机.打开();--改动点7
}
void 停止 (){
灯.关闭();
电视.关闭();--改动点2
洗衣机.关闭();--改动点5
计算机.关闭();--改动点8
}
灯
电视 --改动点3
洗衣机 --改动点6
计算机 --改动点9
}
OCP的关键在于抽象
- 抽象技术:Abstract class Interface
- 抽象遇见了可能的所有扩展(闭)
- 由抽象可以随时导出新的类(开)
使用开闭原则步骤
- 在变化的需求中提供共性、制定抽象标准
- 具体实现抽象标准
- 让软件主体依赖抽象标准
超市收银
收银机设计是否真的完全开闭了
Class 收音
日常生活中有哪些软件系统可以使用开闭原则
移动公司花费系统
里氏代换原则
LSP(Liskov Substitution Principle)在软件中如果能够使用基类对象,那么一定能够使用其子类对象
里氏代换原则分析
喜欢动物–>喜欢猫,因为猫是动物
我喜欢动物,那我一定喜欢猫,因为猫是动物的子类;但是我喜欢猫,不能据此推定我喜欢动物,因为我并不喜欢老鼠,虽然他也是动物
在sunny软件公司开发的CRM系统中,客户可以分为VIP客户和普普通客户两类,系统需要提供一个发送EMAIL的功能,原始设计方案如所示
无论是普通客户还是vip客户,发送邮件的过程都是相同的,也就是说
也就是说两个send方法中的代码重复,而且在本系统可能还将增加新类型的客户,代码修改范围比较大。
使用里氏代换原则对其进行重构
可以考虑增加一个新的抽象客户类,而降CommonCustomer和VIPCustomer类作为其子类,邮件发送类EmailSender类针对抽象客户类Customer编程。
依赖倒装原则
实现“开-闭“原则的关键是抽象化,并且从抽象化到处具体化实现。如果开闭原则 是面向对象设计的目标的话,依赖倒转原则就是这个面向对象涉设计的主要机制
依赖倒转原则讲得是:要依赖与抽象,不要依赖与具体。
传统的依赖关系
传统的过程性系统的设计方法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖与具体层次。依赖倒转原则是要把这个错误的依赖关系到转过来,这就是依赖倒转原则的来由。
符合DIP的系统
依赖倒转原则定义
依赖倒转原则(Dependence Inversion Principle,Dip)的定义如下:高层模块不应该依赖底层模块,他们都应该以来抽象,抽象不应该依赖于细节,细节应该依赖于抽象。
另一种表述为:要针对接口编程, 不要针对实现编程。‘
依赖倒转原则分析
类之间的耦合
- 0耦合的关系
- 具体耦合的关系
- 抽象耦合的关系
依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键
Sunny软件公司开发人员在开发某CRM系统时发现,该系统经常需要将存储在TXT或EXCEL文件中的客户信息转存到数据库中,因此需要进行数据格式转换,在客户数据操作类将调用数据格式转换类的方法实现格式转换和数据库插入操作,出是设计方案结构如所示
设计方案存在一个非常严重的问题,由于每次转换数据时数据来源不一定相同,因此需要更换数据转换类,如有时候需要将TXTDataConvertor改为ExcelDataConvertor,此时,需要修改CustomerDao的源代码,而且在引并使用新的数据转换类时也不得不得修改CUSTOMERDaode源代码,系统扩展性较差,违反了开闭原则,现需要对该方案进行重构。
由于CUSTOMERDAO针对具体数据转换类编程,因此在增加新的数据转换类或者更换数据转换类时都不得不修改CustomerDao的源代码,,我们可以通过引入抽象数据转换类解决该问题,在引入抽象数据转换类DataConvertor之后,Customer针对抽象类DataConvertor编程,而将具体数据转换类名存储在配置文件中,符合依赖倒转原则。
接口隔离原则
接口隔离原则(ISP)的定义如下
客户端不应该依赖那些他不需要的接口
另一种定义如下:
一旦一个接口太大,则需要将他风格成一些更细小的接口,使用该接口的客户端仅需指导与之相关的方法即可
接口隔离原则是指使用多个专门的接口,而不使用单一的总接口,,每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
- 一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做角色隔离原则
- 接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
接口隔离原则分析
- 使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
- 可以在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为。
实例说明
下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口AbstratctService来服务所有的客户类,可以使用接口隔离原则对其进行重构。
合成复用原则
合成复用原则定义如下(CRP)又称为组合/聚合复用原则,其定义如下
尽量使用对象组合,而不是继承来达到复用的目的
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用已有功能的目的,简言之:要尽量使用组合/聚合关系,少用继承。
合成复用原则分析
在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。
- 继承复用,实现简单,易于扩展。破坏系统的封装性,从基类继承而来的实现是今天的,不可能在运行时发生改变,没有足够的灵活性,只能在有限的环境中使用(白箱复用)
- 组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行(“黑箱复用”)
某教学管理悬停部分数据库访问类设计如图所示
如果需要更换数据库连接方式,如原来采用jdbc连接数据库,则需要修改DBUtil类源代码,如果StudentJDBC链接,但TeacherDAO采用连接池链接,则需要增加一个新的DBUtil类并修改StudentDao或者teacherDao的源代码,使之及诚信的数据库连接类,这将违背开闭原则,系统扩展性较差
迪米特法则
迪米特法则又称为最少知识原则,她有多重定义方法,其中几种电信定义如下
- 不要和陌生人说话
- 只与你的直接朋友通信
- 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
简单来说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用,这样,当一个模块修改时就会尽量少的影响其他的模块,扩展会相对容易,这是对软件实体之间通信的限制,他要求限制软件实体之间的通信的宽度和深度。
迪米特法则分析
在迪米特法则中,对于一个对象,其朋友包括以下几类
- 当前对象本身(this)
- 以参数形式传入大当前对象方法中的对象
- 当前对象的成员对象
- 如果当前对象的成员对像是一个集合,那么集合中的元素也是朋友
- 当前对象所创建的对象
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
实例说明
某系统界面类(如form1,form2)与数据访问类如dao1,dao2等类之间的调用关系较为复杂
本章小结
- 对于面向对象的软件系统涉及来说,在支持可维护性的同事,需要提高系统的可复用性
- 软件的服用可以提高软件爱你的开发效率,提高软件质量,节约开发成本,恰当地复用还可以改善系统的可维护性
- 单一职责原则要求在软件系统中,一个雷只负责一个功能领域的相应职责
- 开闭原则要求一个软件实体应当对扩展开放对修改关闭,即在不修改源代码的基础上扩展一个系统的行为
- 里氏代换原则可以通俗表述为在软件中如果能够使用几基类对象那么一定能够使用其子类对象
- 依赖倒转原则要求抽象不应该依赖于细节,细节应该依赖于抽象;要针对接口编程不要针对实现编程
- 接口隔离原则要求客户端不应该依赖于那些他不需要的接口,即将一些大的接口细化成一些小的接口供客户端使用
- 合成复用原则要求复用时尽量使用对象组和,而不使用集成。
- 迪米特法则要求一个软件实体应当尽量少的与其他实体发生相互作用。