设计模式 学习笔记 之 设计原则 (2)

来源:互联网 发布:软件测试实训生 编辑:程序博客网 时间:2024/06/05 01:06

什么是面向对象设计?

一套可执行的程序的业务当中都会存在稳定变化的部分,然而面向对象的重点就是复用。如果存在变化的部分,变化就是复用的天敌,面向对象其实就是抵御这些变化。说抵御变化 ,但是我们又不可能让程序,完全不去变化,那是不可能的,我们能做的紧紧是将变化最小化,同时变化也是我们程序出问题的关键

以前我们理解的面向对象就是封装,多态,继承,这些更多的可能是语言的底层的特性。不能足以去说明为什么他就是面向对象,那么我们可以换个思维方式去理解什么是面向对象。

首先 隔离变化。先来伪代码吧!

第一段:


class Shape{public:    onShowCircle();    onShowSquare();};int main(int argc, char *argv[]){   Shape * S= new Shape();   S->onShowCircle();   S->onShowSquare();}



第二段:

class Shape{public:    Shape() {}public :    virtual onShow();    virtual ~Shape() ;};class Circle :public Shape{public:    onShow();};class Square :public Shape{    public:       onShow();};int main(int argc, char *argv[]){   Shape * C= new Circle();   C->onShow(); // 责任   Shape *S = new Square();   S->onShow();}

这第2段代码 无非就是封装了一下,并且使用了继承多态而已,而且代码也并没有少用,然而他的实际意义并不是这么简单,

假设我们现在需求变化了 ,要增加一个显示梯形的方法那么怎么做

第一段代码,当图形在不断变化的同时我们就要不停的去修改shape类 (当前就是增加onshowTrapzoid() 方法),这样做的后果是否改变了类本来的属性。

第二段代码 , 创建对应图形的类然后去实现对应图形的onshow方法然。将变化封装在了shape类当中,我们不需要修改原来的代码 只要去继承shape类,过来并重写onshow()方法 ,也就是创建一个新的类 Trapzoid  

这样使程序在宏观层面来看变化的过程当中将其所带来的影响减少的最小。

在微观层面来说面向对象的设计更强调“责任”,也就是各负其责需求变化导致新增的类型的时候不会去影响原有的类型的实现。同时”责任”也是我们类设计非常重要的一点。

其次 对象是什么?

从语言角度来说,对象就是一种封装形式。将代码和数据进行类别封装。

从规格角度来说,对象就是一系列可被使用的公共接口。

从概念角度来说,对象就是某种拥有责任的抽象


理解了什么是面向对象,那么就继续理解下设计原则

设计原则比设计模式更加重要,设计模式依赖于设计原则推导出来。设计原则就是一把尺子,它可以衡量你的设计是否是一个良好的设计,

设计模式不单单就是函数的调用流程。如果是这样, 23种设计模式很快都可以看的懂,然而到自己设计的时候,又开始迷茫,或者设计不出好的软件架构。



1)依赖倒置原则(DIP)

贯穿于所有模式。

高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖抽象(稳定)。

抽象(稳定)不应依赖实现细节(变化),实现细节应该依赖于抽象(稳定)。

单独去看这句话是不是有点莫名奇妙 无解。好我们继续用上面的伪代码来说事!


第一段代码:


main 高层模块  直接使用变化底层模块 明明是稳定的 结果是不是被shape 变化带来了不稳定的因素。


第二段代码:



main 稳定的高层模块 依赖于抽象的稳定的接口, 变化被抽象的shape类,将变化隔离了成了 一个稳定的依赖了。 

(抽象没有实现变化具体细节,具体细节全在依赖shape 的子类当中)


2)开闭原则(OCP)

对扩展开放,对更改封闭。

类模块本身是不可以修改的 ,应该是可以扩展的。

同样回到我们刚刚的代码其实我们就是利用这个开闭原则。而且刚刚也说明了,(增加梯形的例子)当需求变更的时候 不要想着去修改,而是去增加一些东西。


3)单一职责原则(SRP)

一个类应该仅有一个引起他变化的原因。

变化的方法隐含类的责任。

关键词 责任 通常情况感受不是那么强烈,如何去把控这个责任 ,没有一个量化的标准,到底一个类负责哪些责任?

这些都要从实际出发,尤其在今后学习的桥模式当中。


4)Liskov 替换原则(LSP)

子类必须能够替换他们的基类(IS-A)。

继承表达类型抽象。


简单来说就是所有需要子类的地方,父类都可以传过去使用, 有些人可能会觉得这不是天经地义的事吗。

其实不然打个简单的比方:

继承过来的了子类  突然发现父类的里面的方法不能使用,然后在子类的重写方法里面去扔异常。其实这里就违背了这个原则。

可能当前完全没有必要去继承,单纯的组合关系可能更好的解决当前的状况。


5) 接口隔离原则(IPS)

不应该强迫客户程序依赖他们不用的方法。

接口应该小而完备。

  小而完备的具体意思就是没必要把所有的接口全部暴露给客户程序(使用接口的程序),将功能接口做到尽量的少。什么是需要public,什么是private 需要明确。


6)优先使用对象组合,而不是类继承

继承在某种程度上破坏了封装的特性,(父类暴露给子类的东西还是比较多的)子类父类耦合度高。

对象组合 只要求被组合的对象具有良好的定义接口耦合度低。


7)封装变化点 (封装 语言的底层的特性 面向对象来说就是封装变化)

使用封装来创建对象之间的分界层,让设计和可以在分界层的一侧惊醒修改,而不会对另一侧产生不良的影响。从而实现层次的松耦合。(比如 刚刚的shape类)


8)针对接口编程,而不是针对实现编程

不降变量类型声明为某个具体的类(类 :业务类) ,而是声明位某个接口。

客户程序无需获知具体类型,只需要知道对象所具有的接口。

减少系统中各部分的依赖关系,从而实现“高内聚 低耦合”的类型设计方案。 他和依赖倒置原则 相辅相成。


面向接口设计是产业强势的标志 ,传统行业才是接口标准化的鼻祖,软件只是借鉴了传统行业 (比方汽车制造业,他们有这一套高度统一的标准化的零件,

这些零件可能是不同国家制造的)!





原创粉丝点击