接口可以定义对象_理解计算机语言中的接口

前言

在计算机领域,接口与堆栈一样是一个异常重要的概念。计算机语言中也通常会有相应的抽象,比如Java 和 C# 中都可以用面向对象的方式定义接口。尽管我们一直都在用,但还没有提高到与面向对象同等重要的级别,甚至我们只是将其看作是面向对象的一个方面。

我们都知道接口在计算机语言中的语法和语义,所以我并不打算再次详细重复这些定义,而是试图脱离语言环境,从我们身处的这个世界中进行一番分析和阐述,然后再映射回语言中,以便有可能帮助人们更直观地理解和使用接口。不过,在开始之前,我们还是先回顾一下在计算机语言中接口的基本定义。

语言中的接口

在计算机语言中,接口通常具有如下的特性:

  1. 不包含实例字段。
  2. 包含方法、属性、索引器和事件等行为,且通常都是公共的。
  3. 在单继承语言中,一个类不可以继承多个基类,但可以同时实现多个接口。
  4. 接口不能被实例化。
  5. 接口之间可以继承。

当然,随着语言的发展,接口也在进化。比如接口可以提供默认实现,可以包含静态或常量字段,可以包含嵌套类型等。不过这些进化并没有改变接口的基本含义。我们在这里不做详细的展开。

对象都具有多面性

我们每一个人都具有多面性。在父母面前,我们是孩子。在孩子面前,我们是父母。在老公/老婆面前,我们是老婆/老公。在老师面前,我们是学生或家长。在医生面前,我们是病人或家属。在理发师Tony面前,我们是理发的客户。在地狱油锅面前,我们是天妇罗的原料。在天使面前,我们是吃天妇罗的呆子。我们称自己为程序猿。但在项目经理面前,我们是研发。在老板面前,我们是员工。在客户面前,我们是龚工或周工。到了朋友那里,我们又是修电脑的。

我们的每一面都对应了一种或一组交流方式。在父母面前,我们可以撒娇,可以衣来伸手饭来张口。但是我们肯定不能将对父母的撒娇用在老师身上,除非你的父母同时也是你的老师。同样是撒娇,对男朋友的撒娇和对父母的撒娇又是不同的。

有许多“面”,我们是同时具有的。我们可以同时是孩子和家长,可以同时是学生和老师,可以同时是医生和病人。还可以同时是病人和老师,可以同时是Tony和男朋友。也有一些正常情况下不会兼具的,比如一般不会同时是老公和老婆,正常逻辑下也不会同时在地狱油锅中翻滚和在天堂里发呆。

我们的每一面都意味着一种能力、职责或角色。外部的人可以通过我们的某一个面或某几个面与我们交流。比如,具有老师角色的人,可能会通过我们的学生面或家长面与我们交流。具有医生角色的人,会通过我们的病人面或家属面与我们交流。而Tony在给我们理发的同时,可能正琢磨着怎么通过自己的“销售”角色向我们推销699的套餐,或惦记着下班后怎么通过“男朋友”这个角色跟妹子去哪家餐厅。

一种角色都会包含一组行为。作为学生,最主要的行为是学习和做作业,当然还会有考试这种噩梦级的行为。作为老师,主要行为是上课和布置作业,对应的还有批改作业和阅卷等行为。作为医生,主要行为是治疗,当然也会有研究这种行为。同时,医生还可能带研究生,所以同时兼具医生和老师这两个角色中的行为能力。

成为一种角色,或具有某种能力,通常是需要付出成本的。比如成为医生,可能要经过5年本科和3年硕士的煎熬。成为Tony则要去蓝翔深造。

我们的一个方面,在外部会存在对应的其他人身上的另一个方面。比如我们的学生面与其他人的老师面之间,医生面和病人面之间,父母和孩子之间等等。这对应的两个面之间形成了一种契约关系。潜在的,一个具有老师面的人,能够与所有具有学生面的人构成教学关系,而不论Ta的年龄、性别、高矮、胖瘦等。一个具有医生面的人,能够与所有具有病人面的人构成医疗关系,而不管Ta的出身、籍贯、人种、职业等。契约关系的两个面,少了任何一个,都会让另一个变得没有意义。

即便人与物,或其他对象之间也是如此。我们开的汽车,对我们来说它的主要职责是运输。单从运输的角度,我们并不需要关心它的材质,也不需要关心制造工艺和流程。这些方面,是制造工人在制造过程中要关心的。对于流浪的猫咪来说,汽车可以遮风挡雨,引擎的余温可以温暖身体。你看,汽车也具有多面性。

什么是接口?

这里的“面”就是接口(interface,interactive+face)。如果“面”、“角色”、“职责”等是我们现实世界中对象关系的描述,那么“接口”就是这种对象关系在计算机世界中的抽象。当然,对象之间不是只具有接口关系。这一点我将在下文中进行说明。

我们听到过许多关于接口的定义或理解,本文中的定义也是这些理解中的一种。有许多是在计算机科学范畴内的定义,看上去有些晦涩。我试图跳出这个范畴,将其定义为:接口是对象为行使一种职责而与外界交互时,从外部看到的一组相关行为的集合。汇总一下前文的例子,用一个看上去不太恰当,但确实可以协助表述其意义的俗语描述就是:接口能够让我们“见人说人话,见鬼说鬼话”。或者说,接口让我们每一个人都具有“多面性”。

对照前面的示例,我们总结一下接口应该具有的普遍特性:

1、 接口是一个对象对外交互用的。如果一个对象不需要跟外部进行交互,那么这个对象就不需要任何接口。或者说,如果一个对象没有定义任何接口,那么这个对象将无法与外部进行任何交互。我们无法想象这样的对象有什么存在意义,事实上,根本无法感受到这种对象的存在。所以,对个体而言,接口总是呈现出“对外”的特性。当然,这个“外部”可以是有范围的,最大的范围就是public。

2、 “交互”这个词意味着,接口中通常只包含行为,即接口通常只关心行为。与对象个体生存相关的内部表示,接口大多并不关心。甚至该对象个体是否存活也不被关心。如果让计算机单纯从理发关系上模拟理发师和顾客,那么Tony是不会关心构成顾客生命个体的心肺等各种身体参数的,甚至不关心某一位顾客是否存活。理发师存在的意义是,世界上有需要理发的顾客,具体是谁,是不需要关心的。所以,定义接口时,我们不会定义与个体实例相关的内部字段。当然,在一些特殊情况下,我们需要暴露一些参数。比如医生可能需要知道某个器官的参数,但一般不会是通过将器官直接血淋淋展现给医生的方式,而是通过某种手段“获取”属性值。

3、 一个对象可以实现多个接口。这是对象多面性的根本。每多一个接口,就意味着对象多一种能力或职责,使得它能够与外部构成契约关系的其他对象进行交互。

4、 角色或职责等是一种类别的概念,是行为规范的集合,显然,它是不能被实例化的。

现在,回头看看计算机语言中的接口。相对来说,比起直接阅读规则化的接口语言规范可能要容易理解一些。比如:为什么接口不包含字段?为什么接口上只包含行为?为什么接口的实例成员不会是私有的?为什么一个对象可以实现多个接口?为什么许多接口使用类似 Convertible、Disposable这样以“ble”表达动作和能力的方式命名?

面向对象与接口

面向对象有三大特征:封装、继承和多态。这三个特征并没有(很好地)概括或表达接口的概念。面向对象描述或抽象了现实世界中对象之间类别(class)的关系,这种关系是一种静态的关系。比如,生物分为动物和植物,动物中包含人类和鸟类。人类分为男人和女人。还可以说人类分为婴儿、少年、青年、中年和老年。看上去,这些关系是在某一个层级上,为满足某种需求,通过某种或某些性质或行为对类别进行的进一步划分。这种划分使得我们更容易理解、学习、专注和管理,从而更容易在计算机中进行建模。即便是行为,在面向对象中,我们关注的也是行为之间的继承以及多样化,也就是父类和子类之间行为的差异,而不是它们之间的交互。

但是接口不同。它描述了一个对象在某一方面的能力、职责或角色,通过这种能力、职责或角色,外部其他对象可以与它进行什么样的交互,以及如何进行交互,这是我们所关心的。彼此交互的对象之间可以没有面向对象的级别关系。因此,接口实际上是定义了对象之间的一种动态关系。

我们甚至应该提出并全面研究“面向接口”这种方法,比如“极端地”建立一种面向接口的语言,并要求所有的行为都必须通过接口实现。然后,将面向对象(OO:Object Oriented)方法和面向接口(IO:Interface Oriented)方法结合使用,形成OIO(Object &Interface Oriented)方法。当两者结合时,面向对象中人们发现的一些问题就有可能得到解决,让人头大的设计模式中的各种原则可能也更容易理清。

比如单一职责原则。我们很难仅在面向对象这个上下文中要求一个类只有一个职责,在开发实践中我们通常也不容易这么做。因为,这不是我们世界的真实样子,多面性才是它的真正面貌。而对于我们人类来说,许多场景都要求我们“八面玲珑”,情商要高。貌似程序猿们经常被这么教育来着。

但是,如果是在接口上实施单一职责就非常自然,非常容易理解了。这就是为什么,接口通常(应该)只有一个或寥寥数个方法。比如IDisposable只有一个方法Dispose。尽管IConvertible有不少方法,但是它也只关心数据类型和转换,理论上可以合并为一个。

再比如接口不变性以及扩展之间的矛盾也就很容易通过接口继承等解决了。因为,我们确实很难仅在面向对象的框架下解决这个问题。万事万物时时刻刻都在变化,才是不变的真理。怎么能够要求一个对象不变呢?怎么能够期望用户需求不变呢?

单纯的面向对象并不能很好地模拟现实世界。这是迄今为止,在使用面向对象方法时,有时候会觉得混乱的主要原因之一。不过,OO和IO的结合将可能极大地改观这种状况。

面向过程与接口

事实上,函数就是一类接口,它定义了调用方和被调用方的契约关系。面向过程(Procedure Oriented)的方法是从函数的角度来为对象之间动态关系建模的,这与接口是完全一致的。适用于接口的设计原则同样适用于函数。

因此,面向过程的方法是一种面向接口的方法。单纯的面向过程同样不能很好地模拟我们的现实世界。对于面向过程的研究,应该提高到接口这个层次上,而不是仅仅局限于传统的“狭义函数”。之所以称其为“狭义函数”,是因为我将CPU指令以及老板购买我们的某种能力赚取利润也看作是函数,或者称为“广义函数”。这个广义函数就是接口。

接口与权限

“历史上”有许多人跟我探讨过权限的问题,这实际上也是设计模式等相关的一类问题。大体上,权限可以分为两类:一个是数据权限,另一个是功能权限。

数据权限是指一个人或一类人对某一数据是否拥有访问权限,拥有何种访问权限。比如在Windows、macOS或Linux上,一个用户是否有权查看一个文件,或是否有权修改一个文件的内容或删除该文件。在数据库订单表中,我们必须验证当前用户是不是一个订单的下单用户,以防止订单被其他用户查看、修改或删除,也就是所有权的问题。

功能权限是指一个人或一类人是否能够执行某个或某些功能。对于一个电商系统,所有的匿名用户都可以注册、登录,所有的登录用户都可以注销、下单、查看订单和支付等。而在一个企业信息化系统中,对同一个数据(比如人员工资单),经理与财务可能使用不同的功能查看或管理。这些功能可能体现为一个或多个页面,一个或多个API调用(通过HTTP、RPC等协议)。尽管对数据的权限有时候可以通过功能权限实现,但数据权限的管理通常仍然不可或缺,比如上述的订单所有权的例子中。对权限的问题,我们不做过多阐述。单纯来看这个功能权限。

一些开发团队会在功能页面上,通过区分当前用户的角色来实施权限控制,例如控制某个按钮或菜单显示或隐藏。我不太认同这种方式。这会让页面的逻辑变得非常复杂,难以维护。而且这种方式可能并没有从根本上解决权限的问题,它只是对最终用户隐藏了入口。在过去使用JSP、ASPX等开发的应用系统中,JS、CSS、HTML、Java、C#等各种语言混杂,再加上按角色判断时的各种分支,可以想象软件质量会如何,开发和维护人员的难度会有多大。

还有一种办法就是,将相关的功能封装为模块,然后按模块进行授权。仔细分析一下,不难发现,这种模型跟接口模型是一致的。一个模块就是一个接口,一个功能就是一个行为。模块跟接口一样,表达了数据或应用系统的一种能力或职责,只有契约关系另一端相应角色的用户才能通过这个接口,与数据或应用系统进行交互。这样,对权限的管理就变成了对“模块接口”和“用户角色”关系的管理。

围绕这种关系的两个方面,软件开发就可以分为两种基本模式:一种是以业务模块为中心,一种是以用户为中心。过去主要是以业务模块为中心,用户次之。老一辈评审专家们特别喜欢业务功能模块的层次结构图。现在则开始以用户为中心,遵循以人为本的原则。相对于传统软件行业,互联网行业是以用户为中心的典范。

当把模块和接口这样映射时,如果我们把面向接口(IO)中的单一职责、隔离、耦合等原则(设计模式原则)应用于模块设计和权限管理,那么我们的应用系统,想不是高内聚、低耦合的都不容易,对应的权限管理系统也就可以非常自然和简洁。

后语

软件开发工作是一种创造性的工作,也是一种翻译性的工作。创造,体现在将现实世界中的事物以及关系在计算机世界中进行建模。翻译,体现在用语言将模型编译为产品,以便最终用户能够与计算机世界相互沟通和交流。这两个过程分别就是设计实现

面向对象是现实世界到计算机世界的一种抽象方法,它在帮助我们建立对象之间的静态关系上立下了汗马功劳。而接口则是对象之间动态交互关系在计算机世界中的抽象,并在计算机软硬件中广泛使用,包括:硬件接口、用户界面(UI)、以及语言中的函数和接口等。

通过重新认识和反思现实世界中事物之间的动态关系和静态关系,然后映射到计算机世界中,就有可能帮助我们更好的理解接口和面向对象等相关的概念。

希望这篇文章可以帮助大家更好地理解和使用接口。

6861ea35f3efae7ef84d3610a0cf188a.png
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值