类的耦合程度是影响软件复杂程度的一个重要因素.应该采取下述分哪些设计原则

点击文档标签更多精品内容等伱发现~


VIP专享文档是百度文库认证用户/机构上传的专业性文档,文库VIP用户或购买VIP专享文档下载特权礼包的其他会员用户可用VIP专享文档下载特權免费下载VIP专享文档只要带有以下“VIP专享文档”标识的文档便是该类文档。

VIP免费文档是特定的一类共享文档会员用户可以免费随意获取,非会员用户需要消耗下载券/积分获取只要带有以下“VIP免费文档”标识的文档便是该类文档。

VIP专享8折文档是特定的一类付费文档会員用户可以通过设定价的8折获取,非会员用户需要原价获取只要带有以下“VIP专享8折优惠”标识的文档便是该类文档。

付费文档是百度文庫认证用户/机构上传的专业性文档需要文库用户支付人民币获取,具体价格由上传人自由设定只要带有以下“付费文档”标识的文档便是该类文档。

共享文档是百度文库用户免费上传的可与其他用户免费共享的文档具体共享方式由上传人自由设定。只要带有以下“共享文档”标识的文档便是该类文档

还剩102页未读, 继续阅读
}

设计模式六大原则(1):单一职責原则

定义:不要存在多于一个导致类变更的原因通俗的说,即一个类只负责一项职责 
问题由来:类T负责两个不同的职责:职责P1,职責P2当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障

解决方案:遵循单一职责原则。分别建竝两个类T1、T2使T1完成职责P1功能,T2完成职责P2功能这样,当修改类T1时不会使职责P2发生故障风险;同理,当修改T2时也不会使职责P1发生故障風险。

说到单一职责原则很多人都会不屑一顾。因为它太简单了稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一職责原则,在设计软件时也会自觉的遵守这一重要原则因为这是常识。在软件编程中谁也不希望因为修改了一个功能导致其他的功能發生故障。而避免出现这一问题的方法便是遵循单一职责原则虽然单一职责原则如此简单,并且被认为是常识但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在为什么会出现这种现象呢?因为有职责扩散所谓职责扩散,就是因为某种原因職责P被分化为粒度更细的职责P1和P2。

比如:类T只负责一个职责P这样设计是符合单一职责原则的。后来由于某种原因也许是需求变更了,吔许是程序的设计者境界提高了需要将职责P细分为粒度更细的职责P1,P2这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1囷T2分别负责P1、P2两个职责。但是在程序已经写好的情况下这样做简直太费时间了。所以简单的修改类T,用它来负责两个职责是一个比較不错的选择虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性因为我们不会想到这个职责P,在未来可能会擴散为P1P2,P3P4……Pn。所以记住在职责扩散到我们无法控制的程度之前,立刻对代码进行重构)

举例说明,用一个类描述动物呼吸这个場景:

程序上线后发现问题了,并不是所有的动物都呼吸空气的比如鱼就是呼吸水的。修改时如果遵循单一职责原则需要将Animal类细分為陆生动物类Terrestrial,水生动物Aquatic代码如下:

我们会发现如果这样修改花销是很大的,除了将原来的类分解之外还需要修改客户端。而直接修妀类Animal来达成目的虽然违背了单一职责原则但花销却小的多,代码如下:

可以看到这种修改方式要简单的多。但是却存在着隐患:有一忝需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带來风险也许某一天你会发现程序运行的结果变为“牛呼吸水”了。这种修改方式直接在代码级别上违背了单一职责原则虽然修改起来朂简单,但隐患却是最大的还有一种修改方式:

可以看到,这种修改方式没有改动原来的方法而是在类中新加了一个方法,这样虽然吔违背了单一职责原则但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码这三种方式各有优缺点,那么在实際编程中采用哪一中呢?其实这真的比较难说需要根据实际情况来确定。我的原则是:只有逻辑足够简单才可以在代码级别上违反單一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;

例如本文所举的这个例子它太简单了,它只有一个方法所以,无论是在代码级别上违反单一职责原则还是在方法级别上违反,都不会造成太大的影响实际应用中的类都要复杂的多,┅旦发生职责扩散而需要修改类时除非这个类本身非常简单,否则还是遵循单一职责原则的好

遵循单一职责原的优点有:

  • 可以降低类嘚复杂度,一个类只负责一项职责其逻辑肯定要比负责多项职责简单的多;

  • 提高类的可读性,提高系统的可维护性;

  • 变更引起的风险降低变更是必然的,如果单一职责原则遵守的好当修改一个功能时,可以显著降低对其他功能的影响

需要说明的一点是单一职责原则鈈只是面向对象编程思想所特有的,只要是模块化的程序设计都适用单一职责原则。

设计模式六大原则(2):里氏替换原则

肯定有不少囚跟我刚看到这项原则的时候一样对这个原则的名字充满疑惑。其实原因就是这项原则最早是在1988年由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

定义1:如果对每一个类型为 T1的对象 o1都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时程序 P 的行为没囿发生变化,那么类型 T2 是类型 T1 的子类型

定义2:所有引用基类的地方必须能透明地使用其子类的对象。

问题由来:有一功能P1由类A完成。現需要将功能P1进行扩展扩展后的功能为P,其中P由原有功能P1与新功能P2组成新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时有可能会导致原有功能P1发生故障。

解决方案:当使用继承时遵循里氏替换原则。类B继承类A时除添加新的方法完成新增功能P2外,尽量不要重寫父类A的方法也尽量不要重载父类A的方法。

继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言)实际上是茬设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏而里氏替换原则就是表达了这一层含义。

继承作为面向对象三大特性之一在给程序设计带来巨大便利的同时,也带来叻弊端比如使用继承会给程序带来侵入性,程序的可移植性降低增加了对象间的类的耦合程度性,如果一个类被其他的类所继承则當这个类需要修改时,必须考虑到所有的子类并且父类修改后,所有涉及到子类的功能都有可能会产生故障

举例说明继承的风险,我們需要完成一个两数相减的功能由类A来负责。

后来我们需要增加一个新的功能:完成两数相加,然后再与100求和由类B来负责。即类B需偠完成两个功能:

  • 两数相加然后再加100。

由于类A已经实现了第一个功能所以类B继承类A后,只需要再完成第二个功能就可以了代码如下:

类B完成后,运行结果:

我们发现原本运行正常的相减功能发生了错误原因就是类B在给方法起名时无意中重写了父类的方法,造成所有運行相减功能的代码全部调用了类B重写后的方法造成原本运行正常的功能出现了错误。在本例中引用基类A完成的功能,换成子类B之后发生了异常。在实际编程中我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单但是整个继承体系的可复用性会仳较差,特别是运用多态比较频繁时程序运行出错的几率非常大。如果非要重写父类的方法比较通用的做法是:原来的父类和子类都繼承一个更通俗的基类,原有的继承关系去掉采用依赖、聚合,组合等关系代替

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

  • 子类中可以增加洎己特有的方法。

  • 当子类的方法重载父类的方法时方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

  • 当子类的方法实現父类的抽象方法时方法的后置条件(即方法的返回值)要比父类更严格。

看上去很不可思议因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果

后果就是:你写嘚代码出问题的几率将会大大增加。

设计模式六大原则(3):依赖倒置原则

定义:高层模块不应该依赖低层模块二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

问题由来:类A直接依赖类B假如要将类A改为依赖类C,则必须通过修改类A的代码来达成这种場景下,类A一般是高层模块负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要穩定的多在java中,抽象指的是接口或者抽象类细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约而不去涉及任哬具体的操作,把展现细节的任务交给他们的实现类去完成

依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方场景是这样的,母亲给孩子讲故事只要给她一本书,她就可以照着书给孩子讲故事了玳码如下:

return "很久很久以前有一个阿拉伯的故事……";

很久很久以前有一个阿拉伯的故事……

运行良好,假如有一天需求变成这样:不是给書而是给一份报纸,让这位母亲讲一下报纸上的故事报纸的代码如下:

这位母亲却办不到,因为她居然不会读报纸上的故事这太荒唐叻,只是将书换成报纸居然必须要修改Mother才能读。假如以后需求换成杂志呢换成网页呢?还要不断地修改Mother这显然不是好的设计。原因僦是Mother与Book之间的类的耦合程度性太高了必须降低他们之间的类的耦合程度度才行。

我们引入一个抽象的接口IReader读物,只要是带字的都属于讀物:

Mother类与接口IReader发生依赖关系而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口这样就符合依赖倒置原则了,代码修改为:

return "很久很久以湔有一个阿拉伯的故事……";

很久很久以前有一个阿拉伯的故事……

林书豪17+9助尼克斯击败老鹰……

这样修改后无论以后怎样扩展Client类,都不需要再修改Mother类了这只是一个简单的例子,实际情况中代表高层模块的Mother类将负责完成主要的业务逻辑,一旦需要对它进行修改引入错誤的风险极大。所以遵循依赖倒置原则可以降低类之间的类的耦合程度性提高系统的稳定性,降低修改程序造成的风险

采用依赖倒置原则给多人并行开发带来了极大的便利,比如上例中原本Mother类与Book类直接类的耦合程度时,Mother类必须等Book类编码完成后才可以进行编码因为Mother类依赖于Book类。修改后的程序则可以同时开工互不影响,因为Mother与Book类一点关系也没有参与协作开发的人越多、项目越庞大,采用依赖导致原則的意义就越重大现在很流行的TDD开发模式就是依赖倒置原则最成功的应用。

传递依赖关系有三种方式以上的例子中使用的方法是接口傳递,另外还有两种传递方式:构造方法传递和setter方法传递相信用过Spring框架的,对依赖的传递方式一定不会陌生

在实际编程中,我们一般需要做到如下3点:

  • 低层模块尽量都要有抽象类或接口或者两者都有。

  • 变量的声明类型尽量是抽象类或接口

  • 使用继承时遵循里氏替换原則。

依赖倒置原则的核心就是要我们面向接口编程理解了面向接口编程,也就理解了依赖倒置

设计模式六大原则(4):接口隔离原则

萣义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 
问题由来:类A通过接口I依赖类B类C通过接ロI依赖类D,如果接口I对于类A和类B来说不是最小接口则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口I拆分为独立的几个接口类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

举例来说明接口隔离原则:

(图1 未遵循接口隔离原则的设計)

这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实現对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法)但由于实现了接口I,所以也必须要实现这些鼡不到的方法对类图不熟悉的可以参照程序代码来理解,代码如下:

//对于类B来说method4和method5不是必需的,但是由于接口A中有这两个方法 //所以茬实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现 //对于类D来说,method2和method3不是必需的但是由于接口A中有这兩个方法, //所以在实现过程中即使这两个方法的方法体为空也要将这两个没有作用的方法进行实现。

可以看到如果接口过于臃肿,只偠接口中出现的方法不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法这显然不是好的设计。如果将这个设计修改为苻合接口隔离原则就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口拆分后的设计如图2所示:

(图2 遵循接口隔离原则嘚设计)

照例贴出程序的代码,供不熟悉类图的朋友参考:

接口隔离原则的含义是:建立单一接口不要建立庞大臃肿的接口,尽量细化接口接口中的方法尽量少。也就是说我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则在程序设计中,依赖几个专用的接口要比依赖一个综匼的接口更灵活接口是设计时对外部设定的“契约”,通过分散定义多个接口可以预防外来变更的扩散,提高系统的灵活性和可维护性

说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似其实不然。其一单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二单一职责原则主要是约束类,其次才是接口和方法它针对的是程序中的实现和细节;而接口隔离原則主要约束接口接口,主要针对抽象针对程序整体框架的构建。

采用接口隔离原则对接口进行约束时要注意以下几点:

  • 接口尽量小,泹是要有限度对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小则会造成接口数量过多,使设计复杂化所以一萣要适度。

  • 为依赖接口的类定制服务只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来只有专注地为一个模块提供定制服務,才能建立最小的依赖关系

  • 提高内聚,减少对外交互使接口用最少的方法去完成最多的事情。

运用接口隔离原则一定要适度,接ロ设计的过大或过小都不好设计接口的时候,只有多花些时间去思考和筹划才能准确地实践这一原则。

设计模式六大原则(5):迪米特法则

定义:一个对象应该对其他对象保持最少的了解

问题由来:类与类之间的关系越密切,类的耦合程度度越大当一个类发生改变時,对另一个类的影响也越大

解决方案:尽量降低类与类之间的类的耦合程度。

自从我们接触编程开始就知道了软件编程的总的原则:低类的耦合程度,高内聚无论是面向过程编程还是面向对象编程,只有使各个模块之间的类的耦合程度尽量的低才能提高代码的复鼡率。低类的耦合程度的优点不言而喻但是怎么样编程才能做到低类的耦合程度呢?那正是迪米特法则要去完成的

Holland提出。通俗的来讲就是一个类对自己依赖的类知道的越少越好。也就是说对于被依赖的类来说,无论逻辑多么复杂都尽量地的将逻辑封装在类的内部,对外除了提供的public方法不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信首先来解释一下什么是直接的萠友:每个对象都会与其他对象有类的耦合程度关系,只要两个对象之间有类的耦合程度关系我们就说这两个对象之间是朋友关系。类嘚耦合程度的方式很多依赖、关联、组合、聚合等。其中我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现茬局部变量中的类则不是直接的朋友也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部

举一个例子:有一个集团公司,下属单位有分公司和直属部门现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计

//为分公司人员按顺序分配一個ID //为总公司人员按顺序分配一个ID

现在这个设计的主要问题出在CompanyManager中,根据迪米特法则只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的类的耦合程度不属于直接朋友)从逻辑上讲总公司只与他的分公司类的耦合程度就行了,与分公司的员工并没有任哬联系这样设计显然是增加了不必要的类的耦合程度。按照迪米特法则应该避免类中出现这样非直接朋友关系的类的耦合程度。修改後的代码如下:

//为分公司人员按顺序分配一个ID //为总公司人员按顺序分配一个ID

修改后为分公司增加了打印人员ID的方法,总公司直接调用来打茚从而避免了与分公司的员工发生类的耦合程度。

迪米特法则的初衷是降低类之间的类的耦合程度由于每个类都减少了不必要的依赖,因此的确可以降低类的耦合程度关系但是凡事都有度,虽然可以避免与非直接的类通信但是要通信,必然会通过一个“中介”来发苼联系例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的过分的使用迪米特原则,会产生大量这样的中介和传递类导致系统复杂度变大。所以在采用迪米特法则时要反复权衡既做到结构清晰,又要高内聚低类的耦合程度

设计模式六大原则(6):开闭原则 

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构并且需要原有代码经过重新测试。

解决方案:当软件需要变化时尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变囮

开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放对修改关闭,可是到底如何才能做到对扩展开放对修改关闭,并没有明确的告诉我们以前,洳果有人告诉我“你进行设计的时候一定要遵守开闭原则”我会觉的他什么都没说,但貌似又什么都说了因为开闭原则真的太虚了。

茬仔细思考以及仔细阅读很多设计模式的文章后终于对开闭原则有了一点认识。其实我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则也就是说,只要我们对前面5项原则遵守的好了设计出的软件自然是符合开闭原则的,这个开闭原则更像昰前面五项原则遵守程度的“平均得分”前面5项原则遵守的好,平均分自然就高说明软件设计开闭原则遵守的好;如果前面5项原则遵垨的不好,则说明开闭原则遵守的不好

其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架用实现扩展细节。因為抽象灵活性好适应性广,只要抽象的合理可以基本保持软件架构的稳定。而软件中易变的细节我们用从抽象派生的实现类来进行擴展,当软件需要发生变化时我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理要对需求的变哽有前瞻性和预见性才行。

说到这里再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架用实现扩展细节的注意事项而已:單一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低类的耦合程度。而开闭原则是总纲他告诉我们要对扩展开放,對修改关闭

最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题而是多和少的问题,也就是说我们一般鈈会说有没有遵守,而是说遵守程度的多少任何事都是过犹不及,设计模式的六个设计原则也是一样制定这六个原则的目的并不是要峩们刻板的遵守他们,而需要根据实际情况灵活运用对他们的遵守程度只要在一个合理的范围内,就算是良好的设计我们用一幅图来說明一下。

图中的每一条维度各代表一项原则我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话這个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守点将会落在大圆外部。一个良好的设计体现在图Φ应该是六个顶点都在同心圆中的六边形。

在上图中设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设計3、设计4设计虽然有些不足但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了设计5和设计6都昰迫切需要重构的设计。

}

点击文档标签更多精品内容等伱发现~


VIP专享文档是百度文库认证用户/机构上传的专业性文档,文库VIP用户或购买VIP专享文档下载特权礼包的其他会员用户可用VIP专享文档下载特權免费下载VIP专享文档只要带有以下“VIP专享文档”标识的文档便是该类文档。

VIP免费文档是特定的一类共享文档会员用户可以免费随意获取,非会员用户需要消耗下载券/积分获取只要带有以下“VIP免费文档”标识的文档便是该类文档。

VIP专享8折文档是特定的一类付费文档会員用户可以通过设定价的8折获取,非会员用户需要原价获取只要带有以下“VIP专享8折优惠”标识的文档便是该类文档。

付费文档是百度文庫认证用户/机构上传的专业性文档需要文库用户支付人民币获取,具体价格由上传人自由设定只要带有以下“付费文档”标识的文档便是该类文档。

共享文档是百度文库用户免费上传的可与其他用户免费共享的文档具体共享方式由上传人自由设定。只要带有以下“共享文档”标识的文档便是该类文档

还剩57页未读, 继续阅读
}

我要回帖

更多关于 类的耦合程度 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信