七大设计原则:
开闭原则
单一职责原则
里氏替换原则
依赖倒置原则
接口隔离原则
最少知识原则(迪米特法则)
少用继承多用组合(合成复用)
这些原则的作用:可以让自己设计实现出来的软件系统更加稳定,容易维护,并具有一致性
(一)开闭原则
开闭原则定义 :一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
就是在最开始设计软件的时候考虑以后可能修改的地方定义接口,如果后期需要修改功能只需要重新设计一个实现方法连接需要修改的接口就行了。
开闭原则总结:面对需求,对程序的改动是通过增加新代码进行的,而不是改变原来的代码。
(1)多用抽象类
在设计类时,对于拥有共同功能的相似类进行抽象化处理,将公用的功能部分放到抽象类中,所有的操作都调用子类。这样,在需要对系统进行功能扩展时,只需要依据抽象类实现新的子类即可。如图
所示,在扩展子类时,不仅可以拥有抽象类的共有属性和共有函数,还可以拥有自定义的属性和函数。
(2)多用接口
与抽象类不同,接口只定义子类应该实现的接口函数,而不实现公有的功能。在现在大多数的软件开发中,都会为实现类定义接口,这样在扩展子类时实现该接口。如果要改换原有的实现,只需要改换一个实现类即可。如图10-2所示,各子类由接口类定义了接口函数,只需要在不同的子类中编写不同的实现即可,当然也可以实现自有的函数。
有一句话概括很不错:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
开闭原则总结:面对需求,对程序的改动是通过增加新代码进行的,而不是改变原来的代码。
(二)依赖倒转原则
核心:要依赖于抽象,不要依赖于具体的实现
1.依赖倒转原则三层含义:
i. 高层模块不应该依赖低层模块,它们都应该依赖抽象。
ii.抽象不应该依赖于细节(实现),细节应该依赖于抽象。
iii. 要针对接口编程,不要针对实现编程。 (例如我们去调用某个方法的时候,通过接口声明对象,然后通过对象去调用)
例如如下图:左边DatabaseSouce是从数据库中取得数据,ServerSouce都是从服务器中取得数据,这两个都是数据源,右边XML和Json都是解析数据的工具类,所以在MainClass中我们要从DatabaseSouce或者ServerSouce中取得数据然后利用XML和Json工具类去解析数据这样我们就相当于在MainClass中需要去调用左边的数据源和右边的解析数据工具类,这里DatabaseSouce\ServerSouce\Json\XML都是低层模块,而MainClass属于高层模块,这样就违反了依赖倒转原则,高层模块依赖于了低层模块,这样一旦我们的低层模块发生了改变增加了功能或者需要切换到其他的数据源那么我们的高层模块也要去修改代码。而我们软件系统需要的是让其可以扩展而不影响其他模块功能。
下面我们去重构下,看看该如何去设计才能让其低层模块增加功能而不改变高层模块的代码。只要将数据源和解析工具分别抽象出来一个抽象类或者接口,这样低层模块一旦发生改变就不会影响到我们的高层模块,所以这里遵循了我们的原则高层模块不应该依赖低层模块,针对接口编程,不针对实现编程。
优点:这样的话修改低层模块不会影响到高层模块,减小了它们之间的耦合度,增强系统的稳定性。
总结:如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计了。
(三)里氏代换原则
里氏代换原则分析
i. 在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
ii. 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
也就是说在有父类的地方全部替换成子类而不影响整个程序的运行
总结:子类型必须能够替换掉它们的父类型。
(四)单一职责原则
- 定义
i. 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
ii. 就一个类而言,应该仅有一个引起它变化的原因。
分析
i. 一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
ii.类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
iii.单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
单一职责表示的就是例如定义了一个主角类,那么这个主角类里面只能有这个主角的一些功能,而不能去实现其他的功能。
总结:就一个类而言,应该仅有一个引起它变化的原因。
(五)接口隔离原则
定义
i. 客户端不应该依赖那些它不需要的接口。
ii. 一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
分析
i. 接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
ii. 使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
iii. 可以在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为
例如下图有三个模块A,B,C这三个模块都需要调用接口IOperator里面三个不同的方法,然而在这里比如我们只要使用A模块,但是接口里面就指会调用OperatorA方法而其他的两个方法是无用的。并且有可能在A模块里面有可能粗心调用错方法。所以我们可以使用接口隔离原则降低其耦合度。
总结:类应该完全依赖相应的专门的接口
(六)合成复用原则
定义
i. 尽量使用对象组合,而不是继承来达到复用的目的。
分析
i. 合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
ii. 在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。
a) 继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用 )
b) 组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。(“黑箱”复用 )
iii. 组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
总结:类中应用,尽量使用对象组合而不是用继承来达到复用的目的。
(七)迪米特法则(最少知识法则)
定义
i. 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
分析
i.迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易,这是对软件实体之间通信的限制,它要求限制软件实体之间通信的宽度和深度。
ii. 狭义的迪米特法则:可以降低类之间的耦合,但是会在系统中增加大量的小方法并散落在系统的各个角落,它可以使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联,但是也会造成系统的不同模块之间的通信效率降低,使得系统的不同模块之间不容易协调。
iii. 广义的迪米特法则:指对对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发、优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。
iv. 迪米特法则的主要用途在于控制信息的过载。
例如下图Form为某个系统的界面类,而下面DAO为各个模块,而我们需要实现Form1界面功能的时候就需要调用DAO1和DAO2才能实现该界面功能的实现,以此类推要实现Form2也需要调用DAO1和DAO2,这样的话就会显得很乱,各个功能相互调用,并且当我们修改了DAO1那么就会影响Form1和Form2,所以这里就可以使用迪米特法则来重构代码。
下图是重构后的,在Form1和DAO直接添加一个Controller,把那些需要组合的功能都放到Controller里面,然后就是Form1调用的方法放到Controller1里面,然后Controller1分别去调用DAO1和DAO2,这样每一个Form只需要引用一个Controller,Controller再去调用多个DAO.这样的话当我们修改了DAO1我们只需要去修改Controller1就可以了这样就不会像上面一样有可能修改一个DAO就需要修改几个Form。这样也大大降低了耦合性。
总结:一个软件实体应当尽可能少的与其他实体发生相互作用
要使我们的编程更加灵活,封装性更好,复用性更高,易于维护我们需要我们的这些原则建设我们规范我们的程序。这样我们编写的才是高质量的程序。
设计模式总结
以下是参考博文:
(程序员必备的七大面向对象设计原则)
http://blog.csdn.net/qiulongtianshi/article/details/7570021
(面向对象七大基本设计原则)
https://wenku.baidu.com/view/90233639c850ad02de8041f0.html