一:为什么提倡面向对象设计?
变化时复用的天敌!
面向对象设计的最大优势在于:抵御变化
二:重新认识面向对象
(一)理解隔离变化:
从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小(隔离不是绝对的)
(二)各司其职
从微观层面来看,面向对象的方式更强调各个类的”责任“。
由于需求变化导致的新增类型不应该影响原来类型的实现。---是所谓的各负其责
(三)对象是什么:
从语言层面来看,对象封装了代码和数据。
从规格层面讲,对象是一系列可被使用的公共接口。
从概念层面讲,对象是某种拥有责任的抽象。
三:设计模式基本原则
(一)依赖倒置原则(DIP)
高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
传统过程设计:倾向于是高层次的模块依赖于低层次的模块,抽象层依赖于具体层(高度依赖)
依赖倒置实现解耦合,将上面的依赖关系倒转过来
(二)开放封闭原则(OCP)
对扩展开放,对更改封闭
类模块应该是可拓展的,但是不可修改
父类指针指向子类对象,使用多态等来扩展功能,某一个错误不会引起全局错误
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
原来的结构设计:将所有功能放在某一个大类中,导致业务扩展性差,修改都是在一个类模块中(易出现关联错误)
符合开发封闭原则的设计:
(三)单一职责原则(SRP)
类的职责要单一,对外只提供一种功能,而引起类变化的原因只有一个
一个类应该仅有一个引起它变化的原因。
变化的方向隐含着类的责任
如果一个类承担的职责过多,就等于把这些职责耦合在一起了。
一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。
这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。
而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。
此原则的核心就是解耦和增强内聚性。
问题由来
T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。也就是说职责P1和P2被耦合在了一起。
解决方法
职责扩散:因为某种原因,某一职责被分化为颗粒度更细的多个职责了
遵守单一职责原则,将不同的职责封装到不同的类或模块中。
优点
(1)降低类的复杂度;
(2)提高类的可读性,提高系统的可维护性;
(3)降低变更引起的风险(降低对其他功能的影响)
(四)里氏替换原则(LSP)(多态)
任何抽象类出现的地方,都可以用他的实现类进行替换(多态)。实际就是虚拟机制,语言级别实现面向对象功能
子类必须能够替换它们的基类(IS-A)。
继承表达类型抽象。
原则:子类可以扩展父类的功能,但不能改变父类原有的功能。
父类能出现的地方都可以用子类来代替,而且换成子类也不会出现任何错误或异常,而使用者也无需知道是父类还是子类,
但反过来则不成立
1. 子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法;
2. 子类中可以增加自己特有的方法;
3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数要更宽松;
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
优点:
1. 提高代码的重用性,子类拥有父类的方法和属性;
2. 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;
缺点:
1. 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
2. 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成
非常糟糕的结果,要重构大量的代码。
(五)接口隔离原则(ISP)
不应该强迫客户程序依赖它们不用的方法。
接口应该小而完备。
一个接口应该只提供一种对外功能,不应该把所有操作都封装到一个接口中去
接口隔离原则和单一职责原则的区别
接口隔离原则与单一职责原则的审视角度不相同。单一职责原则要求是类和接口的职责单一,注重的是职责,这是业务逻辑上的划分。
接口隔离原则要求接口的方法尽量少。
接口要尽量小
这是接口隔离原则的核心定义,接口要尽量小,不要出现臃肿的接口,但是小也是有限度的,不能违背单一职责原则。
接口要高内聚
高内聚就是提高接口,类,模块的处理能力,减少对外的交互。具体到接口隔离原则就是要求在接口中尽量减少公布public方法,接口是对外的承诺,承诺越少对系统开发越有利,变更的风险就越少。
原则
一个接口只服务于一个子模块或业务逻辑
通过业务逻辑压缩接口中的public方法,接口要不断的精简,以达到接口不断完善
已经被污染的接口,尽量去修改,若变更的风险较大,则采用适配器进行转化处理
(六)优先使用对象组合,而不是类继承
类继承通常为“白箱复用”,对象组合通常为"黑箱复用"。
继承在某种程度上破坏了封装性,子类父类耦合度高。
而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
如果使用了继承,会导致父类的任何变换都可能影响到子类的行为。
如果使用对象组合,就降低了这种依赖关系
(七)封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
(八)迪米特法则:针对接口编程,而不是针对实现编程
不将变量类型声明为某个特定的具体类,而是声明为某个接口。
客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
减少系统中各部分的依赖关系,从而实现”高内聚、松耦合“的类型设计方案。
一个对象应该对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。
例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现,这样其他模块不需要了解另一个模块的内部实现细节,
这样当一个模块内部的实现发生改变时,不会影响其他模块的使用(黑盒原理)
四:面向接口设计
五:将设计原则提升为设计经验
设计习语 Design Idioms
Design Idioms描述与特定编程语言相关的低层模式,技巧,惯用法。
设计模式 Design Patterns
Design Patterns主要描述的是”类与相互通信的对象之间的组织关系”,包括它们的角色、职责、协作方式等方面。
架构模式 Architectural Patterns
Architectural Patterns描述系统中与基本结构组织关系密切的高层模式,包括子系统划分,职责,以及如何组织它们之间关系的规则。