目录
第13章 详细设计中的模块化与信息隐藏
1.耦合与内聚(名词解释)
(1)耦合
(2)内聚
2.信息隐藏基本思想
第14章 面向对象的模块化
14.1.访问耦合
14.1.1隐式耦合:Cascading Message 级联调用问题
14.2.解耦方法
14.2.1.针对接口编程
14.2.2.接口最小化/接口分离原则
14.2.3.迪米特法则
14.3.继承耦合
14.4.解耦方法
14.4.1.liskov替换原则
14.4.2.组合替代继承
14.3.内聚
14.3.1. 方法内聚
14.4. 提高内聚的方法
14.4.1.单一责任原则(SRP)
14.5.Summary:Principles from Modularization 模块化的原则
第15章 面向对象的信息隐藏
15.1.信息隐藏的含义
15.2.类的封装
15.3.开闭原则OCP
15.4.依赖倒置原则DIP
第16章 设计模式
16.1.如何实现可修改性、可扩展性、灵活性
16.2设计模式和策略配对
16.3.策略模式
16.4.工厂模式
16.5.单件模式
16.6.迭代器模式
参考:南软考研大书,软工二
这位大佬:
还有这位大佬:
第13章 详细设计中的模块化与信息隐藏
1.耦合与内聚(名词解释)
(1)耦合
描述的是两个模块之间的关系的复杂程度。
耦合根据其耦合性由高到低分为几个级别:模块耦合性越高,模块的划分越差,越不利于软件的变更和重用。1、2、3不可接受,4、5可以被接受,6最理想
(1)内容耦合(一个模块直接修改另一个模块的内容,如GOTO语句;某些语言机制支持直接更改另一个模块的代码;改变另一个模块的内部数据)
(2)公共耦合(全局变量,模块间共享全局数据,例子:全局变量)
(3)重复耦合(模块间有重复代码)
(4)控制耦合(一个模块给另一个模块传递控制信息,如“显式星期天”)
(5)印记耦合(共享数据结构却只是用了一个部分)
(6)数据耦合(模块间传参只传需要的数据,最理想)
(2)内聚
【题型】对实例,说明它们之间的耦合程度与内聚,给出理由。书上P226习题
2.信息隐藏基本思想
每个模块都隐藏一个重要的设计决策——职责。职责体现为模块对外的一份契约,并且在这份契约之下隐藏的只有这个模块知道的决策或者说秘密,决策实现的细节仅自己知道。
模块的信息隐藏:模块的秘密(容易变更的地方):根据需求分配的职责、内部实现机制。
【题型】对实例,说明其信息隐藏程度好坏。教材222页
【项目实践】耦合的处理br> 分层风格:仅程序调用与简单数据传递
包设计:消除重复
分包:接口最小化
创建者模式:不增加新的耦合
控制者模式:解除View与Logical的直接耦合内聚的处理br> 分层:层间职责分配高内聚,层内分包高内聚
信息专家模式
控制器与委托式控制风格信息隐藏处理br> 分层与分包:消除职责重复、最小化接口:View独立据库连接独立br> 模块信息隐藏:模块需求分配与接口定义
类信息隐藏:协作设计,接口定义
变化设计层风格、RMI
第14章 面向对象的模块化
14.1.访问耦合
14.1.1隐式耦合:Cascading Message 级联调用问题
- 避免隐式耦合,变为显式耦合,降低耦合度
- 使用委托的方式来解决,委托给一个类来完成这个业务
14.2.解耦方法
14.2.1.针对接口编程
- 编程到所需的接口,不仅是受支持的接口
- 按照约定设计
- 模块/类合同:所需方法/提供的方法
- 方法合同:前提条件,后置条件,不变式
- 前置条件( precondition):用例在调用某个方法时必须满足的条件。
- 后置条件(postcondition):实现在方法返回时必须达到的要求。
- 副作用(side effects):方法可能对对象产生的任何其他变更。
- 在考虑(非继承的)类与类之间的关系时,一方面要求值访问对方的接口,另一方面要避免隐式访问。
14.2.2.接口最小化/接口分离原则
独立接口避免不必要偶合
14.2.3.迪米特法则
- 通俗说法
- 你可以自己玩。(this)
- 你可以玩自己的玩具,但不能拆开它们(自己的成员变量)
- 你可以玩送给你的玩具。(方法)
- 你可以玩自己制作的玩具。(自己创建的对象)
- 更加形式化的说法:
- 每个单元对于其他单元只能拥有优先的知识,只是与当前单元紧密联系的单元
- 每个单元只能和它的朋友交谈,不能和陌生单元交谈
- 只和自己的直接的朋友交谈
- 书本p233
14.3.继承耦合
- 在以上的各种类型的继承关系中,修改规格、修改实现、精化规格是不可以接受的。
- 扩展是最好的继承耦合
14.4.解耦方法
14.4.1.liskov替换原则
这人写得蛮清楚的:深度解析设计模式七大原则之——里氏替换原则
- 子类可以实现父类中的抽象方法,但是不能重写(覆盖)父类的非抽象方法。
- 当子类需要重载父类中的方法的时候,子类方法的形参(入参)要比父类方法输入的参数更宽松(范围更广)。
- 重写或者实现父类方法的时候,方法的返回值可以被缩小,但是不能放大。
“在派生类中重新定义一种方法时,只能用一个较弱的方法代替其先决条件,而用一个较强的方法代替其后置条件” — B. Meyer,1988年
问题案例
Is a Square a Rectangle/strong>
正方形的长宽相同,不必输入width和height两个数。 子类比父类条件更强,多限制条件。说明前置条件过强。
Penguin is a bird/strong>
企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。
课堂练习
左边不好,因为前置条件过强,一般的门不会 警。
右边不好,door不可替代alarm
14.4.2.组合替代继承
- 组合优于继承
- 使用继承实现多态
- 使用委托不继承重用代码!
继承/组合 实例一
- 如果出现一个用户既是 Passenger 也是 Agent
- Java不允许多继承
- 直接的想法就是直接组合
- Person里面持有Passenger、Agent,但是这时候对于单一身份的人是很奇怪的
案例二
- Person持有Role,Passenger和Agent实现抽象接口PersonRole
- Role可以是一个List
案例三
- 问题:游戏引擎中存在很多的对象,三个类分别实现方法之一
- 继承三件事但是只做了一件,Promise No Less不符合
- 接口应该拆成3个
改进方案:
14.3.内聚
- 内聚的分类参考课本237页,功能内聚、信息内聚、过程内聚、时间内聚、逻辑内聚、偶然内聚。
- 方法和属性保持一致
- 提高内聚性:将一个类分为三个类
- 将时间抽象出来
14.3.1. 方法内聚
- 一类方法是普通耦合
- 所有方法尽一责
- 信息内聚
- 相对功能(功能内聚)
- 第九个原则:单一职责原理
14.4. 提高内聚的方法
14.4.1.单一责任原则(SRP)
“一个类只有一个改变的理由”-罗伯特·马丁(Robert Martin)
- 与内聚性相关并从中导出,即模块中的元素应在功能上紧密相关
- 班级履行某种职责的责任也是班级变化的原因
- 一个高内聚的类不仅要是信息内聚的,还应该是功能内聚的。
问题案例一
- 修改的原因:
- 业务逻辑
- XML格式
- 如何修改如何分开
- 我们将两部分职责分离开
案例二
- 打电话和挂起两个职责分离开
案例三
- 几何画板:Draw和Area的计算如何分开
案例四:
- 解决方案:集合长方形和图形长方形一一对应
14.5.Summary:Principles from Modularization 模块化的原则
- 《Global Variables Consider Harmful》 全局变量被认为是有害的
- 《To be Explicit》让代码清晰一点
- 《Do not Repeat》避免重复
- 《Programming to Interface(Design by Contract)》面向接口编程,按照契约设计
- 《The Law of Demeter》迪米特法则
- 《Interface Segregation Principle(ISP)》接口分离原则
- 《Liskov Substitution Principle (LSP)》里氏替换原则:Request No More, Promise No Less
- 《Favor Composition Over Inheritance》 选择组合而不是继承
- 《Single Responsibility Principle》单一职责原理
第15章 面向对象的信息隐藏
15.1.信息隐藏的含义
每一个模块都隐藏了这个模块中关于重要设计决策的实现,以至于只有这个模块的每一个组成部分才能知道具体的细节
需要隐藏的两种常见设计决策
- 需求(模块说明的主要秘密)与实现——暴露外部表现,封装内部结构
- 实现机制变更(模块说明的次要秘密)——暴露稳定抽象接口,封装具体实现细节
面向对象机制
- 封装:封装类的职责,隐藏职责的实现+预计将要发生的变更
- 抽象类(接口)/继承(实现):抽象它的接口,并隐藏其内部实现
15.2.类的封装
- 目的是信息隐藏
- 封装将数据和行为同时包含在类中,分离对外接口与内部实现。
- 接口是模块的可见部分:描述了一个类中的暴露到外界的可见特征
- 实现被隐藏在模块之中:隐藏实现意味着只能在类内操作,更新数据,而不意味着隐藏接口数据。
- 封装的初始观点:把数据(内部结构)隐藏在抽象数据类型内
新观点(信息隐藏):隐藏任何东西:数据与行为、复杂内部结构、其他对象、子类型信息、潜在变更 -
封装数据与行为:除非(直接或间接)为满足需求(类型需要),不要将操作设置为public。类型需要的操作:为了满足用户任务而需要对象在对外协作中公开的方法,例如下图的4个操作(属于后一个对象,为满足计算商品总价的任务)
除非(直接或间接)为满足需求(类型需要),不要为属性定义getX方法和setX方法,更不要将其定义为public。例如上一示例中的getPrice() -
封装结构:不要暴露内部的复杂数据结构,经验表明复杂数据结构是易于发生修改的。例如暴露了内部使用List数据结构。
改进:Iterator模式(所有涉及到集合类型的操作都可能会出现此问题) - 封装其他对象:委托而不是提供自己拥有的其他对象的引用,或者new一个新对象返回。除非Client对象已经拥有了该其他对象的引用,这时返回其引用不会增加总体设计的复杂度可以保证Sales只需要关联SalesList,不需要关联SalesLineItem和Commodity;从整个设计来讲,Sales不需要知道SalesList里面存储的是SalesLineItem的集合,更不需要知道SalesLineItem使用了Commodity类型。
- 封装子类(LSP:子类必须能够替换他们的基类)
-
封装潜在变更:识别应用中可能发生变化的部分,将其与不变的内容分离开来
封装独立出来的潜在变化部分,这样就可以在不影响不变部分的情况下进行修改或扩展( DIP 和OCP)
15.3.开闭原则OCP
对扩展开放,对修改封闭
违反了OCP原则的典型标志:出现了switch或者if-else
分支让程序增加复杂度,修改时容易产生新错误(特例:创建)
就是那种有扩展的,比如:面向os和面向andriod,这时候需要用工厂模式等把他们分隔开,便于扩展(可能还有面向mac等等)。
15.4.依赖倒置原则DIP
(与工厂结合紧密,解决new的创建问题)
I. 高层模块不应依赖底层模块,两者都应依赖抽象
II. 抽象不应依赖细节,细节应依赖抽象
使用抽象类(继承)机制倒置依赖
示例:A依赖于B:B不是抽象类,所以A依赖于具体,而不是抽象,如果需要变更B的行为,就会影响到A
添加抽象类BI,让 B实现(继承)BI:A依赖于BI,B依赖于BI,BI是抽象类,所以是依赖于抽象,BI比较稳定,如果B发生变更,可以通过为BI扩展新的实现(子类型)来满足
题目类似于:
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!