当前位置:网站首页>保研笔记二 软件工程与计算卷二(13-16章)
保研笔记二 软件工程与计算卷二(13-16章)
2022-07-05 23:18:00 【bulibuli蛋】
目录
14.1.1隐式耦合:Cascading Message 级联调用问题
14.5.Summary:Principles from Modularization 模块化的原则
参考:南软考研大书,软工二
这位大佬:Blog of Samperson (samperson1997.github.io)
还有这位大佬:SpriCoder的博客
第13章 详细设计中的模块化与信息隐藏
1.耦合与内聚(名词解释)
(1)耦合
描述的是两个模块之间的关系的复杂程度。
耦合根据其耦合性由高到低分为几个级别:模块耦合性越高,模块的划分越差,越不利于软件的变更和重用。1、2、3不可接受,4、5可以被接受,6最理想
(1)内容耦合(一个模块直接修改另一个模块的内容,如GOTO语句;某些语言机制支持直接更改另一个模块的代码;改变另一个模块的内部数据)
(2)公共耦合(全局变量,模块间共享全局数据,例子:全局变量)
(3)重复耦合(模块间有重复代码)
(4)控制耦合(一个模块给另一个模块传递控制信息,如“显式星期天”)
(5)印记耦合(共享数据结构却只是用了一个部分)
(6)数据耦合(模块间传参只传需要的数据,最理想)
(2)内聚
表达的是一个模块内部的联系的紧密性。
内聚性由高到低分为:内聚性越高越好,越低越不易实现变更和重用,3、4、5等价,1、2最好,6、7不能接受。
(1)信息内聚(模块进行许多操作,各自有各自的入口点,每个操作代码相对独立,而且所有操作都在相同的数据结构上进行:如栈)
(2)功能内聚(只执行一个操作或达到一个目的)
(3)通信内聚(对相同数据执行不同的操作:查书名、查书作者、查书出版商)
(4)过程内聚(与步骤有关:守门员传球给后卫、后卫传给中场球员、中场球员传给前锋)
(5)时间内聚(与时间有关:起床、刷牙、洗脸、吃早餐)
(6)逻辑内聚(一系列可替换的操作:如坐车、坐飞机)
(7)偶然内聚(多个不相关的操作:修车、烤面包、遛狗、看电影)
【题型】对实例,说明它们之间的耦合程度与内聚,给出理由。书上P226习题
2.信息隐藏基本思想
每个模块都隐藏一个重要的设计决策——职责。职责体现为模块对外的一份契约,并且在这份契约之下隐藏的只有这个模块知道的决策或者说秘密,决策实现的细节仅自己知道。
模块的信息隐藏:模块的秘密(容易变更的地方):根据需求分配的职责、内部实现机制。
【题型】对实例,说明其信息隐藏程度好坏。教材222页
【项目实践】耦合的处理?
分层风格:仅程序调用与简单数据传递
包设计:消除重复
分包:接口最小化
创建者模式:不增加新的耦合
控制者模式:解除View与Logical的直接耦合内聚的处理?
分层:层间职责分配高内聚,层内分包高内聚
信息专家模式
控制器与委托式控制风格信息隐藏处理?
分层与分包:消除职责重复、最小化接口:View独立?数据库连接独立?
模块信息隐藏:模块需求分配与接口定义
类信息隐藏:协作设计,接口定义
变化设计?分层风格、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?
Rect r = new Rect();
setWidth = 4;
setHeight = 5;
assert(20 == getArea());
class Square extends Rect{
// Square invariant, height = width
setWidth(x) {
setHeight()=x;
}
setHeight(x) {
setWidth(x)
}
} // violate LSP?
正方形的长宽相同,不必输入width和height两个数。 子类比父类条件更强,多限制条件。说明前置条件过强。
Penguin is a bird?
class Bird {
// has beak, wings,...
public: virtual void fly();
// Bird can fly
};
class Parrot : public Bird {
// Parrot is a bird
public: virtual void mimic();
// Can Repeat words...
};
class Penguin : public Bird {
public: void fly() {
error ("Penguins don’t fly!");
}
};
void PlayWithBird (Bird abird) {
abird.fly();
// OK if Parrot.
// if bird happens to be Penguin...OOOPS!!
}
企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。
课堂练习
左边不好,因为前置条件过强,一般的门不会报警。
右边不好,door不可替代alarm
14.4.2.组合替代继承
- 组合优于继承
- 使用继承实现多态
- 使用委托不继承重用代码!
继承/组合 实例一
- 如果出现一个用户既是 Passenger 也是 Agent
- Java不允许多继承
- 直接的想法就是直接组合
- Person里面持有Passenger、Agent,但是这时候对于单一身份的人是很奇怪的
案例二
- Person持有Role,Passenger和Agent实现抽象接口PersonRole
- Role可以是一个List
案例三
class Object {
public: virtual void update() {};
virtual void draw() {};
virtual void collide(Object objects[]) {};
};
class Visible : public Object {
public:
virtual void draw() {
/* draw model at position of this object */ };
private: Model* model;
};
class Solid : public Object {
public:
virtual void collide(Object objects[]) {
/* check and react to collisions with objects */ };
};
class Movable : public Object {
public:
virtual void update() {
/* update position */ };
};
- 问题:游戏引擎中存在很多的对象,三个类分别实现方法之一
- 继承三件事但是只做了一件,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扩展新的实现(子类型)来满足
题目类似于:
第16章 设计模式
16.1.如何实现可修改性、可扩展性、灵活性
教材263页
需要进行接口和实现的分离:通过接口和实现该接口的类;通过子类继承父类
注意:继承关系(A+B)可能使得灵活性下降,因为父类接口的变化会影响子类,这时可以通过组合关系来解决。
利用抽象类机制实现可修改性和可扩展性:只要方法的接口保持不变,方法的实现代码是比较容易修改的,不会产生连锁反应。通过简单修改创建新类的代码,就可以相当容易地做到扩展新的需求(不用修改大量与类方法调用相关的代码。
利用委托机制实现灵活性:继承的缺陷:一旦一个对象被创建完成,它的类型就无法改变,这使得单纯利用继承机制无法实现灵活性(类型的动态改变)。利用组合(委托)机制可以解决这个问题
16.2设计模式和策略配对
- 策略模式:减少耦合、依赖倒置。
- 抽象工厂模式:职责抽象、接口重用。
- 单件模式:信息隐藏、职责抽象。
- 迭代器模式:减少耦合、依赖倒置。
16.3.策略模式
减少耦合,符合开闭原则,易于扩展;也符合依赖倒置原则,具体依赖于抽象。
test:通过new参数新建employee,由set函数设置具体策略。
Context:employee,包含了employee的个人属性,set策略,调用接口函数实现策略。
Strategy:包括接口Strategy,以及实现类具体Strategy,Context设置了具体策略后,通过调用接口实现具体策略。
简化一下的模板子:
/**
* 策略模式
* 当一个功能的实现可以使用多种算法或者方式的时候
* 如果选择在业务代码if等分支语句下硬编码,在类似场景多次出现的时候如果修改会改很多处地方,违反开闭原则
* 基于开闭,这时会想到将这些'策略'方法进行统一管理,使用的时候直接new这个管理类,调用对应的方法即可
* 而为了将各个策略方法统一管理(如增加一些日志的打印等操作),抽象一个上下文类context对其进行统一管理
*/
public class StrategyPattern {
public static void main(String[] args) {
Context context = new Context();//新建上下文
Strategy addStrategy = new AddStrategy();//添加具体策略
context.setStrategy(addStrategy);//设置具体策略
context.invoke(1, 2);//运算
Strategy minusStrategy = new MinusStrategy();
context.setStrategy(minusStrategy);
context.invoke(4, 2);
}
}
//抽象策略是一个接口
interface Strategy {
//里面的策略交给具体策略实现
void doStrategy(int a, int b);
}
//具体策略1,实现+
class AddStrategy implements Strategy {
@Override
public void doStrategy(int a, int b) {
System.out.println(a + b);
}
}
//具体策略2,实现-
class MinusStrategy implements Strategy {
@Override
public void doStrategy(int a, int b) {
System.out.println(a - b);
}
}
//上下文类,管理策略对象以及一些额外的通用逻辑
class Context {
private Strategy strategy;
//获取具体策略
public Strategy getStrategy() {
return strategy;
}
//设置具体策略
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
//根据具体策略,运行
void invoke(int a, int b) {
System.out.println("Context invoke start");
strategy.doStrategy(a, b);
System.out.println("Context invoke done");
}
}
16.4.工厂模式
职责抽象,接口重用
同样简化一下模板:
/**数字抽象产品*/
public interface Digit {
public void display();
}
/**黑色数字类,充当具体产品*/
public class BlackDigit implements Digit {
public void display(){
System.out.println("显示黑色数字");
}
}
/**红色数字,充当具体产品*/
public class RedDigit implements Digit {
public void display(){
System.out.println("显示红色数字");
}
}
/**字母抽象产品*/
public interface Letter {
public void display();
}
/**黑色字母,充当具体产品*/
public class BlackLetter implements Letter {
public void display(){
System.out.println("显示黑色字母");
}
}
/**Summer文本框类,充当具体产品*/
public class RedLetter implements Letter {
public void display(){
System.out.println("显示红色子母");
}
}
/**符号抽象产品*/
public interface Mark {
public void display();
}
/**黑色符号类,充当具体产品*/
public class BlackMark implements Mark {
public void display(){
System.out.println("显示黑色符号");
}
}
/**红色符号类,充当具体产品*/
public class RedMark implements Mark {
public void display(){
System.out.println("显示红色符号");
}
}
/**字体颜色抽象工厂*/
public interface ColourFactory {
public Digit createDigit();
public Letter createLetter();
public Mark createMark();
}
/**黑色具体工厂*/
public class BlackColourFactory implements ColourFactory {
public Digit createDigit(){
return new BlackDigit();
}
public Letter createLetter(){
return new BlackLetter();
}
public Mark createMark(){
return new BlackMark();
}
}
/**红色具体工厂*/
public class RedColourFactory implements ColourFactory {
public Digit createDigit(){
return new RedDigit();
}
public Letter createLetter(){
return new RedLetter();
}
public Mark createMark(){
return new RedMark();
}
}
/**客户端测试类*/
public class Client{
public static void main(String args[]){
//使用抽象层定义
ColourFactory factory;
Digit dt;
Letter lt;
Mark mk;
//factory=(SkinFactory)XMLUtil.getBean();
//为了开闭原则,可以利用反射机制和xml资源获取得到想使用的界面类
factory = new RedColourFactory();//想更换颜色可以在这里做修改
dt = factory.createDigit();
lt = factory.createLetter();
mk = factory.createMark();
dt.display();
lt.display();
mk.display();
}
}
16.5.单件模式
职责抽象,隐藏单件创建的实现。具体工厂的实现只有一个实现。
16.6.迭代器模式
边栏推荐
- (4) UART application design and simulation verification 2 - RX module design (stateless machine)
- 成为程序员的你,后悔了吗?
- Technical specifications and model selection guidelines for TVs tubes and ESD tubes - recommended by jialichuang
- 3D point cloud slam
- regular expression
- Using LNMP to build WordPress sites
- ts类型声明declare
- February 13, 2022-4-symmetric binary tree
- From the perspective of quantitative genetics, why do you get the bride price when you get married
- 【原创】程序员团队管理的核心是什么?
猜你喜欢
开关电源Buck电路CCM及DCM工作模式
TVS管和ESD管的技术指标和选型指南-嘉立创推荐
Initial experience | purchase and activate typora software
Sum of two numbers, sum of three numbers (sort + double pointer)
98. Verify the binary search tree ●●
TVS管 与 稳压二极管参数对比
3:第一章:认识JVM规范2:JVM规范,简介;
698. 划分为k个相等的子集 ●●
Realize reverse proxy client IP transparent transmission
2:第一章:认识JVM规范1:JVM简介;
随机推荐
MySQL (1) -- related concepts, SQL classification, and simple operations
动态规划 之 打家劫舍
When to use useImperativeHandle, useLayoutEffect, and useDebugValue
Cwaitabletimer timer, used to create timer object access
TVS管和ESD管的技术指标和选型指南-嘉立创推荐
3D reconstruction of point cloud
数据库基础知识(面试)
2022.6.20-6.26 AI industry weekly (issue 103): new little life
AsyncSocket长连接棒包装问题解决
Spécifications techniques et lignes directrices pour la sélection des tubes TVS et ESD - Recommandation de jialichuang
无刷驱动设计——浅谈MOS驱动电路
JVM的简介
asp. Net pop-up layer instance
3: Chapter 1: understanding JVM specification 2: JVM specification, introduction;
Rasa 3.x 学习系列-Rasa X 社区版(免费版) 更改
Neural structured learning 4 antagonistic learning for image classification
Rethinking about MySQL query optimization
[classical control theory] summary of automatic control experiment
idea 连接mysql ,直接贴配置文件的url 比较方便
判断二叉树是否为完全二叉树