面向接口设计和编程——(面向对象、面向接口、面向过程、面向实现)

时间:2021-05-28 17:24:38


引言--面向接口所处的设计模式中的位置。

其实,我认为Java/C#比C++高级的其中一个原因是,它对面向接口编程的支持。不要误解,并不是说C++不支持面向接口编程,而是说C++的语法中没有这种天然的机制。

面向对象之于面向过程,面向接口之于面向实现。但基本上,面向接口和面向实现都基于面向对象的模式,也就是说面向接口并不能称为比面向对象的更高的一种编程模式。而是在面向对象中大的背景下的一种更加合理的软件设计模式,它增强了类与类之间,模块与模块的之间的低耦合性,是软件系统更容易维护、扩展。

不管是面向什么,都是一种软件设计模式,与具体的语言有没多大关系。

就像之前介绍C语言一样,并不是说C语言这种面向过程的语言不能做面向对象编程,而是说,C语言当初设计的并没有针对面向对象软件系统的风格而进行设计的。由于后来的面向对象软件设计风格的流行,后来者语言C++/java/C#都在语言设计上充分考虑了支持面向对象的方便性,所以这些语言称为面向对象编程语言。

根据语言之间没有能力大小的理论,C是可以进行面向对象编程的(实践上也是可行的)。

上面理论同样适合讨论—— C++之于面向接口编程。

面向接口软件设计,并不是在java或C#中出现interface这种关键字后才有的。还是那句话,它是一种软件设计模式,与具体语言无关,C++或者C都可以并且也大量使用过这种编程模式。而仅是因为这种模式的优点,java与C#才在C++的基础上,设计成更好的支持面向接口编程(里面提出的语言级别的接口的概念)。

两种interface的概念

这就需要了解语言(java/C#)级上的interface,与软件设计级别上的interface了。可以说,是现有软件设计上的interface概念,然后那些“后起”语言,就为了充分的支持这种软件设计(毕竟设计最终要用语言实现),而加入了"interface"这个关键字及其相关概念。也就是说,即使java/C#不使用它们语言自带的interface技术,也可以进行面向接口编程(你或许知道——使用抽象类)。这么讲吧,两种接口不属于同一级别的东西,并不应该拿来比较,但一定要比较,可以认为,软件设计上的接口概念比语言级别的接口概念要大。

面向接口(设计)编程是什么意思,为了什么,有什么好处

(1)说文解字

“面向”这个词,在软件设计编程中得到大量的使用。但似乎我们有不是很明白它到底代表什么意思。就像前面纠结“面向对象”这个词是什么意思的时候,我们也需要对“面向接口”这个词有较为深刻的解析。

“对象”与“数据”的区别在于“对象是信息与处理信息的方法的载体,而数据只是信息的载体”,而“面向”这个词的关键含义是,当我们在设计一个系统、实现一个系统的时候,我们基于、以什么为设计和编程的目标。面向过程设计和编程,设计师和程序员在设计和编程的时候所面对的、和所能够利用的东西往往只是一些变量。而面向对象则不同,他们面对的是一些对象,可以说是一些活生生的对象。

比如你要编程控制一个人。

面向过程编程中,给你的只是一个人的信息体(结构体),这个信息体中包含完整描述一个人的各种“静态信息”的数据,例如姓名、性别、年龄、体重、样貌等等,这种时候,如果要控制一个人,让他说一句话“你好吗”。那么你就需要,专门设计一个函数或者一个过程,来调用这个人的所具备的各种机体,然后让他说出话来(实际上是等于知道一个人如果把那句话发出来)。这是一个很让人头痛的东西,而我们希望的是程序员或这是设计人员面向的是一个活生生的人,这个人是会说话的,它并不需要我们叫他如何和说话,我们要让它说话只需要将说的内容传递给他,而他就会说出来。这个说话就是这个人的“动态信息”。

面向对象,就是提供给程序员一个完整的包括静态信息和动态信息的对象。让程序员不必花时间在该个体的某些动作的实现过程上。就像一个动物饲养员一样,不同的动物他们自己吃饭有不同的吃饭,动物饲养员只需要给予不同的动物不同的食物就可以了,前提是这个动物是有自主吃这个动作的。这就是面向对象最基本的概念——提供给设计师或程序员的是一个对象,一个包含那个个体的静态(数据)与动态(处理数据的方法)信息的整体。

有了上面的概念,我们可以首先确定“面向”是什么一会事,实际上就是他的字面意义,就是在设计者或程序员进行工作的时候,他们做面对的是一个什么?

(2)解释开始

面向实现,就是在面向对象编程的时候,当我们要控制某个类的对象,那么我们会直接在当前程序中(自身类)中,实例化该类,然后通过该类调用相应的方法。这是一种最基本的面向对象编程模式。

有人说,这有问题吗?这不就是我们所惯用的面向对象设计方法吗?要使用某个对象,就像实例化它然后调用它的方法,这种模式还做了比较好的低耦合性。你调用的对象,如果实现发生了变化,你基本不需要出现什么修

改。但就一个问题,就很难解决。

(3)难以解决的问题

动物饲养员喂养三种动物(鸡、鸭、鹅),只需要使用上述面向实现的方法,分别实例化那些动物,然后分别调用那些动物的“吃”这个方法。对那些动物如何次这些东西,是他们自己的是,饲养员根本不需要管,所以饲养员的程序基本稳定,这也就是面向对象的优点。(各种动物的个数不是问题,关键就是饲养员掌握了三种动物的类,实例化他们、调用他们只是一个迭代过程而已)。

但是,如果这时,动物园的新安排一种动物(狗),给这个饲养员饲养,狗这个动物的类已经做好,无需饲养员管,反正这种动物一定是用嘴吃食物就是(饲养员才可能知道怎么饲养)。但是,这个时候,需要对饲养员这个对象的类进行修改,加入实例化狗和调用狗吃食物的方法。

这就出现了问题,我们最理想的状态就是,动物园改点东西(加入那些狗),然后给饲养员就是了,这个时候因为加入一些狗,需要对饲养员从新培训(更新他的代码),这是我们不想要的。这也说明这种模式设计方法并没有做到很好的低耦合性。这也是普通的面向对象设计和编程手段做不到的。

(4)分析问题

解决上述问题的方法,需要我们从新思考面向问题。前面我们说的面向实现这个概念可以暂且不管,在上述的设计编程的思想下,饲养员,直接面向的是,它要饲养的那些动物(并且区别对待不同的动物)。而实际上,饲养员基本不需要区别对待。因为各种动物吃饭的细节(实现)根本不是你要管的。那么在这种情况下,作为普通实验员的你,为什么要区别对待不同的动物呢?

也就是说,我们所要的更好的状况是,饲养员只要知道他们是动物,有吃的动作就可以了。如果饲养员是按照那种模式培训的,那么动物园随便怎么安排新的物种,或者下掉新的物种,饲养员那边的代码就完全不动了,这就做到了低耦合了。

(5)初次解决(设计)

那么如何实现上面的目标呢?

我们知道,饲养员之前面向的是不同种类的动物,而现在我们只需要让他面向动物。什么意思?更抽象一层了。于是我们需要怎么实现,那就是继承嘛!也就是说,我们需要在设计每个动物的时候需要让他们继承“动物”这个更为上层的一个类。并且还要使用多态的技术才行。

也就是说,根据多态的观点,如果饲养员得到的是动物园给它的一批动物的指针(基类指针),指针指向的可能有多种动物(派生类指针),饲养员可以通过这个指针调用那些动物的吃的动作就可以了。这也就是要求,每个动物必须遵守从基类继承并覆盖一个虚函数,这样,饲养员,通过动物指针调用的吃函数,才会动态绑定到各个动物上去。而这个饲养员是完全不必知道的。而动物园对饲养员进行编程(管理)的时候传给饲养员的东西只是那些动物的指针而已。所以只要后来加上的动物,是通过是以动物为基类,饲养员就完全不必更新什么!

你觉得这是什么编程,或则说你觉得,这个饲养员面向的是什么?对象吗?对,依然是,但是是属于更为抽象的一个对象(动物),而因为它面向得更为抽像,导致,外界的更新基本不需要改他的改变。

(这也可以与我们学习有些类似,如果你学的是一些过于具体的东西,当新的类似的东西出现,你又要学那个东西,如果你学的是较为抽象的基本、一般原理,当新的类似的东西出现,你套用就是,这就是“一通万通”,关键是第一个通是通在一般原理,后面的通是通在各种类似具体知识)

我们为什么不叫“面向基类编程”呢?因为上面介绍的这种模式,似乎,就是面向更为抽象的基类编程嘛。对的,这种说法很正确,并且也说出了这种编程模式的基本思想。但是,当我们需要解决另外一个问题的时候,这种说法,不具备广泛的适应性。

(6)进一步的问题:

还是前面动物园为背景。现在加入检疫员这个角色(类)。这个类对象需要对动物园的动物进行检疫,不同的动物有不同的检疫方法,但是这个具体的动作内容,包含在各个动物内部,也就是说,你可以想象成,检疫的时候,拿起一只动物,检疫员是按照这个动物信息提上所携带的相关检疫信息,进行检疫。所以,这个检疫员与饲养员没有太多大区别,关键是,饲养员调用“吃”这个操作,而检疫员调用“检疫”这个动作。

按照前面的编程模式,“动物”内中应该至少包括“吃”和“检疫”两个函数,(虽然这两个函数可能都没有实际内容,但他们的功能是很多的)。于是检疫员同样做到了与饲养员那样的在系统中的低耦合性。

但是,你会发现,饲养员面向的是“动物”,检疫员面向的也是“动物”,而是不是有些问题呢?实际上,检疫员和饲养员所关注的动物的方面是不同的(虽然他们都不在乎是什么动物),饲养员只在乎动物“吃”这个方面,而检疫员只在乎动物“检疫”这个方面。

(7)进一步分析问题

有人会问,这个有问题吗?理论上讲,只要动物园严加管理,对饲养员进行严加培训,是没有多大问题的,但怕就怕在,饲养员给动物检疫,检疫员给动物东西吃。为什么会这样?为什么你把整个“动物”都暴漏个了两者,也就是说,你并没有区别对待这两种角色。还有一个不好的问题是,给检疫员编程的时候,如果动物这个类太复杂了(包含太多的方法和属性),会是编程趋于困难,如果,给检疫员的只是一个它所能关注和需要调用使用的动物的一部分,那么编程就方便了。并且也不会让检疫员调用到“吃”这个方法,这是一种“隔离”,也是一种安全措施。

也许到现在有些人有点模糊了。前面认为面向对象(具体对象)编程不好,认为面向基类(抽象)编程可以降低耦合,现在好像又需要具体了。其实两者一定不矛盾。前者将各个具体动物归为“动物”,后则将“动物”分解成不同的方面。两者一点都不矛盾。

(8)解决问题

那么如何解决这个问题呢?

(9)基类和接口(引深)

这就让我们想到了,现代编程中的两个概念。基类和接口。他们的共同特点就是让派生类继承或实现(暂时可以理解成一个意思)。从语法结构上看,基类和接口的结构非常相似,那么他们到底有什么区别呢?基类只能单继承,而接口可以进行多实现。这种规定又是为什么?

这就是我们要理解的自然界中的事物,通常只属于某一个类。也就是说鸡就是动物类,不应该属于其他类。

这种做法和不合理。要知道在这种做法的背景下,类只能继承一个类哦。

我们再看看,不同类的东西,也有相同的属性,是否我们能够更具这些属性来分类呢?

人和动物不是一个类(虽然也是),但是人和动物都有“吃”和“睡”这样的属性,那么是不是人和动物都可以归类为可以“吃”的,可以“睡”呢?对的!可以这么分的,但是,它与通常的分发是有本质不同的。表达除了一种"I'm is..." 和"I have ..."之间的区别。为了区分两种,在软件设计行业中就出现了类与接口的不同概念,并且在“后起的”程序设计语言java和C#中,也对着两者做的显示的区分(从语法上进行定义)。

我们再来看看动物园里的那些动物。他们应该都属于一个类“动物”但是他们还有许多东西是共有的,并且这些东西不是他们特有的,人也可以有,例如“吃”,“睡”,“检疫”(虽然通常不这么说)等。更正确的设计方法,并应该让“吃”,“睡”这些东西独属于“动物”,而对于人,又从新定义类似的概念。这样并不符合自然规律和特点。而应该是,对于这些“吃”,“睡”什么的,应该独立定义,然后,动物可以继承它,人也可以继承它,但是动物和人不属同类(不要纠结啊,虽然他们也是同类)。这样你发现,自然间的各种事物,不在是我们之前的树形结构的联系,而是更具不同的方面某个东西有多方面的从属联系。这样我们描述自然的能力就更为丰富了。而java/C#与C++的区别在于,他们严格区别开了前面我们介绍的基类和接口的区别。、

抽象类和接口的比较

所以虽然通常,我们老是将“接口”和“抽象类”进行比较,但是,事实上他们不属于同一个级别的东西,可以这么说吧,我们有了类的概念,但是我们将类分成了“唯一类”和“接口”(在后进语言中才有区别),然后,当初,C++只有类的概念,但是这个类概念包含了后来在java中的类和接口的概念。抽象类(具有纯虚函数的类)存在的最初意义,是不希望你实例化这样的一个不能代表任何东西的类。实际上在C++面向接口、面向对象编程的过程中,是可以不使用到抽象类的概念的。接口由于完全从类分离出来的一种特殊类,它负责对继承它的类的方法进行规范,正是因为接口的这个特殊作用,使得,它不会语义上不需要对方法的实现,这一点,在java和C#中,被钉死了。而在C++中,没有这种规定,但建议这么做。

(10) 再回到我们的动物园——全面解决问题

我们定义的时候,首先应该定义“吃”这个类(C++语法意义上的类,其实是涉及意义上的接口,里面的吃,是定义为纯虚函数,还是普通虚函数都无所谓,但是必须是虚函数,这也是java的优势,否则不能出现多态特性,那前面讲的也白搭了)。也需要相应的定义“检疫”这个类。我们现在就改口说他是接口(在C++中,只能说他的设计意义上的接口)。

然后,定义“动物”这个类,里面包含了“动物”的特质信息,同时要继承“吃”和“检疫”两个接口。这样一个完整的动物定义(相对完整)就出现了。

我们知道,鸡、鸭、鹅、狗,是要继承“动物”这个类的。

现在给饲养员进行编程:

我们还用“动物”作为他面向的东西吗?自然不会,我们用“吃”做为他面向的东西,因为不管是C++,还是C#/java.他们都保证了接口和基类在这方面的特性,所以,使用“吃”作为面向的东西来实现我们要实现的多态性是语法可行的。

同样,给检疫员编程的时候,使用“检疫”作为面向的东西。

这样做,就既可以保证了,饲养员和检疫员和系统的低耦合性,同时也保证了他们各自职能干他们该干的事情。可以说几乎完美了。

总结

现在我们在来看看,我们用了什么设计模式。对,面向接口,实验员和检疫员编程的时候,就是面向接口。所以面向接口编程就是这么来的啦。而面向实现基本上是因为提出了面向接口之后,才出现的这个与它相对的编程方法。没有多大意义。

从我们所说的动物园的例子中看,似乎面向接口比面向对象更抽像了一级,但是,这只是在面向对象这个天才的实际模式之下的一点优化而已。所以,不能说面向接口是比面向对象更高级的设计模式。

面向接口的核心意义就是,编程的时候,不是直接面向你要控制的那个对象(普通面向对象),而是通过利用继承与多态的功能,面向一个更抽象的东西。这样降低的模块之间的耦合度,提高了代码复用。通过近一步分析提取了“接口”这个管理意义上的概念,并且在此基础上,进一步减低了模块的耦合度,提高了模块的单一性。这就是面向接口编程。(其实,想要了解它的核心,可以称它为面向基类编程)。

我们发现,“动物”这个类的吃的动作,是不应该有什么内容的,因为动物是一个抽象的东西,不想鸡、鸭什么的,他们吃的动作可以具体。所以,动物类中吃的动作必须有,否则:从管理上,你无法统一所有具体动物吃的外界接口;从编程上你也无法让编译器通过饲养员的程序,因为无法通过“动物”指针引用吃这个动作。

所以“吃”这个动作,在“动物”类中,顶多有一些通用的内容。