面向对象下的模块化
14-面向对象下的模块化
面向对象中的两种特有耦合(与结构化方法中的六种耦合并不是并立关系):1.访问耦合;2.继承耦合。
访问耦合包含:
①隐式访问:B没有在A的规格出现,也没有在实现中出现(例如a.methodA.methodB(),隐式调用了B的方法,维护时很难发现调用了B方法,违反了迪米特)
②实现中访间:B的引用是A方法中的局部变量。
③成员变量访问:B的引用是A的成员变量。
④参数变量访问:B的号用是A的方法的参数变量。
⑤无访问:理论最优。

降低访问耦合的方法:
针对接口编程 (Programming to Interface):定义明确的契约并按照契约组织和理解软件结构。
针对接口编程而不是针对实现编程
1.只访问对方的接口;2.避免隐式访问;3.契约式设计。
接口最小化/接口分离原则 ISP (Interface Segregation Principle):将一个统一接口匹配为多个更独立的接口。
一个接口中出现了较多的方法且被不同的类单独依赖,客户端不应该依赖于不需要的接口
修改:1.设计独立的接口;2.按依赖分配给各个实现类;3.原接口多继承细化后的接口。
迪米特法则 (Demeter Law):一个软件实体应当尽可能少地和其他实体发生相互作用。
能调用下列对象的方法(访问耦合的合理范围):
自身;成员变量;参数对象;自身中创建的对象(局部变量)。
一般修改设计时序图都是改成委托,或者引入暂时变量。
继承耦合包含
①修改:
1.规格:任意修改继承于父类的方法的接口。
2.实现:任意修改继承于父类的方法的实现。
②精化:
1.规格:按定义的规则修改父类的方法,且至少一个方法的接口被改动。
2.实现:按定义的规则修改父类的方法,但只改动了方法的实现。
③扩展:子类只增加新的方法和成员变量,不对继承的任何属性和方法进行修改。
(Notes:“扩展”即符合LSP原则)
降低继承耦合的方法
里氏替换原则(Liskov Substitution Principle/LSP):
子类型能够替换基类型并起同样的作用。(最典型的反例:正方形和长方形)
为了满足LSP:
子类的前置方法条件(形参)必须与超类方法的前置条件相同或者要求更少。
子类的后置条件(返回值)必须与超类的后置条件相同或者要求更多。
组合代替继承(Composite/Aggregate Reuse Principle/CARP):
在不满足LSP时,用组合来代替继承实现代码的复用。
子类应该能替换基类而起相同的作用,只为了复用代码而不为了组织类型差异的继承用法往往是不符合LSP原则,需要使用组合代替继承(既能复用代码又能保持接口灵活性)
继承与组合对比
继承:
优点:能很好的完成接口与实现的分离,子类不但继承了父类的接口还继承了实现,可以更好地进行代码重用。
缺点:
1.耦合度高,继承时所有子类和父类都存在公共接口的耦合,当父类接口发生变化时,子类接口也要改动,会影响到Client代码,不利于类的拓展与维护。
2.限制了复用的灵活性,子类创建对象时(编译时)就决定了实现的选择,无法动态修改。
组合:
1.利用组合既能复用代码又能保持接口的灵活性。
2.组合中不存在接口耦合,当组合类接口发生变化时不会影响Client代码。
3.组合类能实现动态创建,动态配置,动态销毁。
面向对象的内聚:
方法的内聚:功能,通信,过程,时间,逻辑,偶然。(同结构化方法中的内聚)
类的内聚:成员变量和方法之间的内聚。(信息,功能)
子类和父类的继承内聚:只为了代码重用的继承内聚性就比较低,如果有很好的概念上的联系则继承内聚性较高(遵守LSP)
提高内聚的方法:
集中信息与行为:将信息与访问这些信息的行为放在一个类中。
单一职责原则SRP:一个类应该只包含单一职责,既是信息内聚又是功能内聚,信息与行为除了要集中外,还要联合起来表达一个内聚的概念。
耦合的度量
方法调用耦合
CBO(Coupling Between Objects):
1.调用其他类成员的方法的数量。
2.访问这个类的成员方法的数量。
访问耦合
DAC(Data Abstracion Coupling):一个类包含的其他类的实例的数量。
继承耦合
NOC(Number of Children):直接所属的子类的数目。
DIT(Depth of the Inheritance Tree):统计从继承树的根节点到叶节点的长度。