目录
第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.迭代器模式
参考:南软考研大书,软工二
这位大佬:Blog of Samperson (samperson1997.github.io)
还有这位大佬:SpriCoder的博客
描述的是两个模块之间的关系的复杂程度。
耦合根据其耦合性由高到低分为几个级别:模块耦合性越高,模块的划分越差,越不利于软件的变更和重用。1、2、3不可接受,4、5可以被接受,6最理想
(1)内容耦合(一个模块直接修改另一个模块的内容,如GOTO语句;某些语言机制支持直接更改另一个模块的代码;改变另一个模块的内部数据)
(2)公共耦合(全局变量,模块间共享全局数据,例子:全局变量)
(3)重复耦合(模块间有重复代码)
(4)控制耦合(一个模块给另一个模块传递控制信息,如“显式星期天”)
(5)印记耦合(共享数据结构却只是用了一个部分)
(6)数据耦合(模块间传参只传需要的数据,最理想)
表达的是一个模块内部的联系的紧密性。
内聚性由高到低分为:内聚性越高越好,越低越不易实现变更和重用,3、4、5等价,1、2最好,6、7不能接受。
(1)信息内聚(模块进行许多操作,各自有各自的入口点,每个操作代码相对独立,而且所有操作都在相同的数据结构上进行:如栈)
(2)功能内聚(只执行一个操作或达到一个目的)
(3)通信内聚(对相同数据执行不同的操作:查书名、查书作者、查书出版商)
(4)过程内聚(与步骤有关:守门员传球给后卫、后卫传给中场球员、中场球员传给前锋)
(5)时间内聚(与时间有关:起床、刷牙、洗脸、吃早餐)
(6)逻辑内聚(一系列可替换的操作:如坐车、坐飞机)
(7)偶然内聚(多个不相关的操作:修车、烤面包、遛狗、看电影)
【题型】对实例,说明它们之间的耦合程度与内聚,给出理由。书上P226习题
每个模块都隐藏一个重要的设计决策——职责。职责体现为模块对外的一份契约,并且在这份契约之下隐藏的只有这个模块知道的决策或者说秘密,决策实现的细节仅自己知道。
模块的信息隐藏:模块的秘密(容易变更的地方):根据需求分配的职责、内部实现机制。
【题型】对实例,说明其信息隐藏程度好坏。教材222页
【项目实践】耦合的处理?
分层风格:仅程序调用与简单数据传递
包设计:消除重复
分包:接口最小化
创建者模式:不增加新的耦合
控制者模式:解除View与Logical的直接耦合内聚的处理?
分层:层间职责分配高内聚,层内分包高内聚
信息专家模式
控制器与委托式控制风格信息隐藏处理?
分层与分包:消除职责重复、最小化接口:View独立?数据库连接独立?
模块信息隐藏:模块需求分配与接口定义
类信息隐藏:协作设计,接口定义
变化设计?分层风格、RMI
独立接口避免不必要偶合
这人写得蛮清楚的:深度解析设计模式七大原则之——里氏替换原则
“在派生类中重新定义一种方法时,只能用一个较弱的方法代替其先决条件,而用一个较强的方法代替其后置条件” — 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
继承/组合 实例一
案例二
案例三
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 */ };
};
改进方案:
“一个类只有一个改变的理由”-罗伯特·马丁(Robert Martin)
问题案例一
案例二
案例三
案例四:
每一个模块都隐藏了这个模块中关于重要设计决策的实现,以至于只有这个模块的每一个组成部分才能知道具体的细节
需要隐藏的两种常见设计决策
面向对象机制
对扩展开放,对修改封闭
违反了OCP原则的典型标志:出现了switch或者if-else
分支让程序增加复杂度,修改时容易产生新错误(特例:创建)
就是那种有扩展的,比如:面向os和面向andriod,这时候需要用工厂模式等把他们分隔开,便于扩展(可能还有面向mac等等)。
(与工厂结合紧密,解决new的创建问题)
I. 高层模块不应依赖底层模块,两者都应依赖抽象
II. 抽象不应依赖细节,细节应依赖抽象
使用抽象类(继承)机制倒置依赖
示例:A依赖于B:B不是抽象类,所以A依赖于具体,而不是抽象,如果需要变更B的行为,就会影响到A
添加抽象类BI,让 B实现(继承)BI:A依赖于BI,B依赖于BI,BI是抽象类,所以是依赖于抽象,BI比较稳定,如果B发生变更,可以通过为BI扩展新的实现(子类型)来满足
题目类似于:
教材263页
需要进行接口和实现的分离:通过接口和实现该接口的类;通过子类继承父类
注意:继承关系(A+B)可能使得灵活性下降,因为父类接口的变化会影响子类,这时可以通过组合关系来解决。
利用抽象类机制实现可修改性和可扩展性:只要方法的接口保持不变,方法的实现代码是比较容易修改的,不会产生连锁反应。通过简单修改创建新类的代码,就可以相当容易地做到扩展新的需求(不用修改大量与类方法调用相关的代码。
利用委托机制实现灵活性:继承的缺陷:一旦一个对象被创建完成,它的类型就无法改变,这使得单纯利用继承机制无法实现灵活性(类型的动态改变)。利用组合(委托)机制可以解决这个问题
减少耦合,符合开闭原则,易于扩展;也符合依赖倒置原则,具体依赖于抽象。
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");
}
}
职责抽象,接口重用
同样简化一下模板:
/**数字抽象产品*/
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();
}
}
职责抽象,隐藏单件创建的实现。具体工厂的实现只有一个实现。