一个软件实体应当对扩展开发,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
Spring中的AOP,即面向切面编程就是对开闭原则的极大应用。
开闭原则实例:
某图形界面系统提供了各种不同形状的按钮,客户端代码可针对这些按钮进行编程,用户可能会改变需求要求使用不同的按钮,原始设计方案如图所示:
采用OOP原则后的设计结果如下图所示:
继承必须确保超类(父类)所拥有的性质在子类中仍然成立,即子类可以扩展父类的功能,但是尽量不要改变父类原有的功能,添加新的方法,完成新的功能,如果通过重写父类的方法来完成一些新的功能,这样写起来比较简单,但是整个继承体系的可复用性就会越来越差。
里氏代换原则是实现开闭原则(OOP)的重要方式之一,由于使用积累对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时在确定其子类类型,用子类对象来替换父类对象。
要面向接口编程,不要面向实现编程,即抽象不依赖于细节,细节依赖抽象。
简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不是依赖于具有的类,要针对接口或抽象类编程,而不是针对具体的类编程。
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
因此:要控制类的粒度大小,将对象解耦,提高其内聚性,即一个对象不要承担太多职责,一个方法尽量做好一件事,提高其原子性。
实例说明:
某基于Java的C/S系统的“登录功能”通过如下登录类(Login)实现:
init是初始化方法,display是界面展示方法,validate是验证登录方法,getConnection是连接数据库方法,finduser是通过用户名和密码查找用户方法,main是程序执行入口。显示这样编写一个类使得Login类过于耦合,不符合单一职责原则。
下面使用单一职责原则对其进行重构:
接口隔离原则是指使用多个专门的接口,而不是使用单一的总接口。每一个接口应该承担一种相对独立的角色。不多不少,不干不该干的事,该干的事都要都要干。要为各个类建立它们需要的专用接口。
(1)一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以佳作“角色隔离原则”。
(2)接口仅仅提供客户端需要的行为,即所需的法法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
实例说明:
下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口(胖接口)AbstractService来服务所有的客户类。
可以使用接口隔离原则对其进行重构。
迪米特法则又称为最少知识原则,它有多种定义方法,其中有几种典型定义如下:
(1)不要和陌生人说话。
(2)只与你的直接朋友通信
(3)每一个单位对其他的单位都只有最少的知识,而且局限于那些与本单元密切相关的软件单元。
迪米特法则分析:
简单来说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易,这是对软件实体之间通信的限制,它要求限制软件实体之间通信的宽度和深度。
实例说明:
某系统界面类(如Form1、Form2等类)与数据访问类(如DAO1、DAO2等类)之间的调用关系较为复杂,如图所示:
采用迪米特法则修改如下所示:
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新的对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简而言之:在开发中,尽量使用组合/聚合关系,少用继承。
合成复用原则分析:
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,
因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系
统的复杂度,因此需要慎重使用继承复用。



