洞悉编程思想是我们学习语言过程中的必由之路,但注意对于编程思想的理解一定要建立在大量的代码实现经验上,不然只是在口头空谈编程思想而不去编程,根本无法深入理解思想的核心。
面向对象思想的核心之一,就是模拟真实世界,把真实世界中的事物抽象成类,整个程序靠各个类的实例互相通信、互相协作完成系统功能,这非常符合真实世界的运行状况,也是面向对象思想的精髓。
例如:接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。在自然界的体现就是“如果你是……则必须能……”的理念。
面向接口编程
高内聚低耦合
设计模式之开闭原则
面向接口编程和面向对象编程是什么关系
首先,面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。
接口就是标准规范,就是定死了一个框架,你根据这个框架去执行!有了标准去遵守就容易扩展!我们只需要面向标准编程,而不用针对具体的实现类!
面向接口编程的好处:
1.更加抽象,更加面向对象
2.提高编程的灵活性
3.实现高内聚、低耦合,提高可维护性,降低系统维护成本。
面向接口主要是传统行业学习,比如建筑业,建筑业最重要的是图纸,那就是接口。开发软件的图纸就是软件设计师设计的接口。
一个接口可以从三方面去考察:
制定者(或者叫协调者),实现者(或者叫生产者),调用者(或者叫消费者)。
接口本质上就是由制定者来协调实现者和调用者之间的关系。
所以通常说的“面向接口编程”可以理解为:只有实现者和调用者都遵循“面向接口编程”这个准则,制定者的协调目的才能达到。
一个老生常谈的例子就是JDBC。
很多人费解:既然我每连接一种数据库(如mysql)都要事先部署驱动程序,那我直接访问驱动程序不就行了?还要JDBC干吗?
实际上,JDBC已经起了至关重要的作用了:正因为驱动程序是按照JDBC所规定的方法编写的,你才可以按照JDBC的方式去使用。
换句话说,如果驱动程序提供者不按照JDBC标准来编写,而是按它自己独创的方式编写,那么你在使用驱动程序的时候就要花时间查看驱动程序的文档以搞清楚用法。而当你日后决定使用另一种数据库的时候,这种数据库的驱动程序也不是按照JDBC编写的,你又得去搞清楚另一套完全不同的用法,而你的所有代码都必须做相应的更改。这种代价是不可想象的。
而现在的情况是,驱动程序提供者都按照JDBC规定的方式来编写,程序员都按照JDBC规定的方式来使用。程序员不用关心自己正在使用何种数据库,而驱动程序编写者也不用费尽心力去编写接口文档来向程序员解释驱动程序的用法,大家都向标准看齐就行了。
现在,你觉得面向接口(标准)编程的好处还不明显吗?
当你正在你的键盘上打字的时候,你是否想到,你在学校就学会了的打字方法至今还在用,因为所有计算机键盘的布局都是一样的。这时,你会不会由衷地感激这个设计键盘布局的人呢?正是他让你只要学会一种打字方法就可以用在所有计算机的键盘上。
这个例子的总结:接口(标准)作为中间方,作为制定者,为实现者与调用者提供统一的行业规范,这样大家做出来的东西就是按照统一的标准而来,协调了两者之间的关系,消除了两者的不同!
java之所以受欢迎就是得益于它的规范。我们为什么用struts框架?因为它提供你一个固定的模式给你,大家写出来的东西都是类似的,好懂!如果用C或C++,10个人可能有10种不同的实现和思路,这样没人愿意花时间去理解别人的代码!而JAVA的面向接口其实就是让大家按规范办事。这在大型项目的开发中是很有好处的。
再比如在JDBC中比如有个BaseDao接口,现在有MySQLDao实现了一个(我们可以把具体的实现类配在配置文件中,再通过反射进行实例化),也就类似这样的:
BaseDao dao
= (BaseDao)(Class.forName(Config.getDaoName()).newInstance());
其中Config.getDaoName()可以获得配置文件中的配置,比如是:com.bao.dao.impl.MySQLDao。
之后,客户开始要烧钱了,要改用Oracle了,但是我们是面向接口不错的,我们只要按BaseDao的定义,再实现一个OracleDao就可以了,再将配置文件中的配置改为:com.bao.dao.impl.OralceDao就可以了,而在已经写好的代码中,我们可以一行不改的进行了数据库移植,这个就是面向对象设计原则中的“开-闭原则”(对增加是开放的,对修改是封闭的)。但这只是理论上的,现实中很难做到的。
对这个例子的理解:利用面向接口和配置文件进行解耦,增强了系统的灵活性、可维护性,换代码不换实现,将对项目的影响降低到最小!
在现实生活的理解:比如公司里制定了接口,也就是标准、规范,我们的工作只需要根据公司的规范进行,而不需要根据具体的不同领导者执行;否则的话:不同领导者的交替更换,它的要求一直更换,我们今天习惯了听这个领导的,明天又来了一个,我们就要更换思维:这样总是更换就类似于实际项目中的混乱问题;所以面向接口是一个很好的解决办法!我们不需要具体的听谁的,只需要遵守事先制定的接口规范就行!
总结:接口也就是一种规范!面向接口编程就类似于现实中遵守公司规定一样!这样增强了系统的灵活性、可维护性,减小影响!实现项目中常说的:高内聚、低耦合!
什么是高内聚、低耦合?
起因:模块独立性指每个模块只完成系统要求的独立子功能,并且与其他模块的联系最少且接口简单,两个定性的度量标准――耦合性和内聚性。
内聚性又称块内联系。指单个模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
高内聚就是在一个模块内,让每个元素之间都尽可能的紧密相连。也就是充分利用每一个元素的功能,各施所能,以最终实现某个功能。如果某个元素与该模块的关系比较疏松的话,可能该模块的结构还不够完善,或者是该元素是多余的。
高内聚关键字:最充分的利用模块中每一个元素的功能,达到功能实现最大化,内聚性越强越好,用最小的资源干最大的事情!
耦合性也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
低耦合关键字:项目中的各个模块之间的关联要尽可能的小,耦合性(相互间的联系)越低越好,减小“牵一发而动全身”的可能性!
耦合性与内聚性是模块独立性的两个定性标准,将软件系统划分模块时,尽量做到高内聚低耦合,提高模块的独立性,为设计高质量的软件结构奠定基础。
有个例子很容易明白:一个程序有50个函数,这个程序执行得非常好;然而一旦你修改其中一个函数,其他49个函数都需要做修改,这就是高耦合的后果。一旦你理解了它,你编写概要设计的时候设计类或者模块自然会考虑到“高内聚,低耦合”。
高内聚低耦合是软件设计的一个基本原则,说的是在程序的各个模块中,尽量让每个模块独立,相关的处理尽量在单个模块中完成,就是俗话说的:该干嘛干嘛去!优点:能提降低各模块的之间的联系,减少“牵一发而动全身”的几率,提高开发效率,降低升级维护成本,也便于进行单元测试,提高软件质量。
内聚和耦合,包含了横向和纵向的关系。功能内聚和数据耦合,是我们需要达成的目标。横向的内聚和耦合,通常体现在系统的各个模块、类之间的关系,而纵向的耦合,体现在系统的各个层次之间的关系。
高内聚低耦合也可以更好的支持代码的复用,我们在做项目的时候发现需要的功能之前实现过,那么直接拿来用就OK,但是需要注意的是这个功能模块的耦合性一定要低才行,就是最好只拿它一个就可以来用!
设计模式的原则里的开闭原则,其实就是要使用接口来实现对扩展开放,对修改关闭。
开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。
在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。
其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
备注:别人关于面向接口编程的总结,很不错!
在项目中的意义:
在传统的项目开发过程中,由于客户的需求经常变化,如果不采用面向接口编程,那么我们必须不停改写现有的业务代码。改写代码可能产生新的BUG,而且改写代码还会影响到调用该业务的类,可能全都需要修改,影响系统本身的稳定性。而且为了将改写代码带来的影响最小,我们不得不屈服当前的系统状况来完成设计,代码质量和稳定性更低。当这种情况积累到一定程度时,系统就会出现不可预计的错误,代码凌乱,不易读懂,后接手的人无法读懂代码,系统的维护工作越来越重,最终可能导致项目失败。
接口在项目就是一个业务逻辑,面向接口编程就是先把客户的业务提取出来,作为接口。业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件(例如Spring框架)中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。
采用基于接口编程的项目,业务逻辑清晰,代码易懂,方便扩展,可维护性强。即使更换一批人员,新来的人依然可以快速上手。对于公司来说,意义更大。
在Java语言中的意义:
Java本身也是一个不断完善的语言,他也在频繁的改动他的系统API来完善,他的API是一个庞大的体系,互相关联,如果不采用接口,而都是用实现类的话,那么API的改动就会给整个体系带来不稳定。而且如果改动API,那么就会有大量采用旧API的项目因无法正常运行,会损失大量客户。换句话说,JDK已经发布的API是一种承诺,一经发布就不能更改,即使原来API存在各种各样的问题(例如java.util.Properties类就是一个失败的例子)也必须保留,于是在Java里就出现了不建议使用的方法,但JDK依然提供该方法。而且Java语言本身是一个跨平台的语言,为了满足在各个平台下运行,就必须把各种操作做成接口,在编写各个平台下的实现类。
设计模式的体现:
在设计模式的原则里的开闭原则,其实就是要使用接口来实现对扩展开放,对修改关闭。在设计模式的其他原则里也有关于基于接口编程的原则,即依赖倒转的设计原则(DIP)----高层模块不应该依赖于底层模块。二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象(注:来自《敏捷软件开发–原则、模式与实践》Robert C.Martin著)。在使用面向接口的编程过程中,将具体逻辑与实现分开,减少了各个类之间的相互依赖,当各个类变化时,不需要对已经编写的系统进行改动,添加新的实现类就可以了,不再担心新改动的类对系统的其他模块造成影响。
编程也是一门艺术,在C语言中灵活的使用指针是一门艺术,在面对对象的程序中,灵活的使用接口也是一门艺术。现在项目中,功能越来越复杂,只设计了完美的类,对于整个系统来说没有多大意义,现在的项目更注重各个功能模块的整合及可维护性,接口的设计就显得更为重要了。程序设计不再是设计类的具体实现,而是从整个项目出发,设计出可扩展性强的接口。当你发现越来越灵活的使用接口时,那么你就从程序员升级为架构师了。