如果说继承是面向对象程序设计中承前启后的特质,那么接口就是海纳百川的体现了。它们都是对数据和行为的抽象,都是对性质和关系的概括。只不过前者是纵向角度,而后者是横向角度罢了。今天呢,我想从设计+语法角度说一说我感受到的面向接口编程,从而初探设计与实现分离的模式。

(本文所使用的面向对象语言为java,相关代码都是java代码)

设计——接口抽象设计

继承的思想很容易理解,提取几类相近数据中的公共部分为基类,各个独立部分在基类的基础上做自己专属的延伸。接口是抽象概括输入和输出,而具体的实现交由具体实现接口的类来完成,从而达到一样的接口不一样的实现方式,使得管理统一化,实现多样化。

概念扯了那么多,还是先上个例子吧,以课程中的出租车调度项目为例。

该项目是模拟出租车运行,地图为 的正方形网格图,每个点的四个邻接点不一定都连通,但保证整个图是连通的,共有100辆出租车运行。
任意两个结点之间有道路或者无道路。
出租车未接单时为随机游走,即随机向可行方向之一运动一步。接单之后选择最短路径运行。

看到这个版本一的需求,我当时的第一想法是什么呢?出租车的行为可概括成两种模式,随机游走和最短距离寻路,这两种行为都是要基于图数据的,那么就开个邻接矩阵存储图,连通为1不连通为0,然后去做相应的实现即可。这样听起来似乎没什么问题,完全是基本操作嘛。但是,看到我说版本一,相信聪明的人一定猜到还有后续的版本。是的,变化的需求是程序设计者最大的敌人。版本二的需求改动如下:

新增道路打开关闭功能,连通的路可以被关闭,关闭之后也可以选择再次打开,道路的状态变成了三种,普通的出租车无法通过关闭后的道路。新增VIP出租车,VIP出租车可以通过被关闭的道路。

关闭道路?嗯…面对这样的需求改动,以大一时的蠢习惯,那就开个flag数组,对于所有的连通边初始化为1,关闭道路就把对应的flag置为0,每次访问图的同时访问flag数组,想法是很美好的,但如果需求又变了呢,道路的状态再次增加了呢,总不可能继续开更多的flag吧。所以,应该先定义好各种状态对应的值,通过一个邻接矩阵来存储对应的状态值,使用一种数据结构来管理。为简化说明我们就设置关闭道路代号为2。

数据存储解决之后,就要做相应的逻辑处理了,两种出租车,对于图中的道路有不同的访问权限,那是不是应该每个出租车写一个最短路径搜索呢?又或者是给最短路搜索方法新传入一个出租车类型参数,根据类型参数的不同选择不同的分支去执行。这个时候,就轮到接口出场了。我们来细细梳理逻辑,两种出租车都是要搜索最短路径,所使用的算法是相同的,唯一的不同点在于两种出租车对于“连通”的判断逻辑不同,其他的代码部分应该都是可复用的。被C语言腐蚀的我第一时间想到了什么——函数指针,如果是使用C语言的话,我们需要为两种出租车定义各自的连通性判断函数,然后通过一个函数指针传入最短路径搜索函数(类似stdlib.h中的qsort函数一样)。那么在java中有异曲同工之妙的就是使用接口来实现了,这正好符合面向接口编程的目的——实现不同,接口内容相同。所以我们应该对于每种类型的出租车实现专属的连通性判断接口,在任何需要访问图的时候传入该接口即可。下面附上代码:

版本一:

// 普通出租车
if(inRange(u)&&graph[v][u]==1){ do something } // VIP出租车 if(inRange(u)&&graph[v][u]==1||graph[v][u]==2){ do something } 

版本二:

if(inRange(u)&&inter.isConnected(v,u)){ do something } 

试想你的代码中有多处需要判断连通性,你是选择一处一处写“graph[v][u]==XXX”,还是选择使用接口来管理呢?所有需要使用的地方使用一样的模式,代码可读性高,复用性好。需求改变修改代码时仅需修改或新增接口实现即可,不用在文件中各处修补,维护起来也方便。同样将具体的实现逻辑作为保存在类中,外部只能调用无法修改,提高了安全性。

语法——动态接口

听到这里肯定有人会想:明白了明白了赶紧代码走起。不过先别急,在最基本的接口实现语法之外,还有一种更加高级的写法——动态接口。

  基本的接口实现是在类中实现重写接口的具体实现,然后将其作为该类的实例化对象的方法使用,说到这里聪明的你一定发现了:这样的做法传参数的时候还是必须将对象传进去,我们的目的是仅仅使用这一个方法,但是却不得不将整个对象传进去,这又扩大了对象的共享范围,难道就不能像C语言一样只是传个方法进去吗?答案是肯定的,那就是动态接口。具体的代码如下:

// 接口定义
public interface TaxiInterface { boolean isConnected(int x,int y); } // 接口在类中的实现 public TaxiInterface setTaxiInterface(){ return new TaxiInterface() { @Override public boolean isConnected(int x, int y) { int temp; temp=map.getGraphInfo(x,y); return temp==MapHelper.getOpen()||temp==MapHelper.getSamePoint(); } }; } 

  什么?在方法里重写方法。是的你没有看错,随时随处重写,哪里有需求,哪里就有接口的实现,非常的灵活。语法提炼一下,就是在新建接口对象的时候重写其实现内容。对于我们的问题,我们对于每个出租车类定义一个接口类型成员变量,然后通过set方法定义具体内容。在传递的时候使用相应的get方法,只是将此接口变量传递出去。外部的方法只能使用接口中定义的内容,关于该类的其他所有内容都无权访问。这种写法既方便快捷,又保证了数据的隐私性和安全性。不过提醒一点,在没有熟练掌握前不要乱用哦。  

语法——default和static接口方法

  现在我们跳跃到下一个问题。假如说现在你有成吨的类,都要实现某一个接口,而其中很多类对于接口中某个方法的实现是相同的,仅有少数不同。但是要修改的类太多了,按照传统的路子,你得实现一个,然后不停的人肉ctrl+c,这种事光是想一下就觉得痛苦,程序猿明明是最擅长偷懒的人啊!不要担心,在Java 8 之后,接口拥有了default和static方法,拯救了这个问题。

  我们都知道接口中定义的抽象方法都是自带public abstract属性的,但是在方法声明最前面加上default关键字,就可以在接口中完成此方法的缺省实现,其他实现该接口的类都可以通用该方法,有特殊需求类的单独重写就可以,调用时直接通过方法名调用即可。举个例子,Iterable.java源码中的forEach遍历方法就是这样实现的,提供了一个通用的迭代方法。

default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } 

  P.S. 有时间可以多读读相关类库源码。我读了部分TensorFlow源码和java类库源码发现自己相关能力都有很大提高。

  话说回来,那static又能干什么呢,这个就很类似类中的static修饰的方法,即不需要实现接口(implement XXX),使用接口名.方法名即可调用。

  注意:一个接口中可以有多个default和static修饰的方法,但是一旦使用这两个关键字该方法就必须实现。

设计——传入对象 or 传入接口

  在初学OOP的时候,很令人苦恼的一点就是对象的传递,每个类负责自己的数据,各个类实例化的对象之间又要共享数据传递信息,但是将整个对象传来传去的话又会造成数据隐私的暴露,说不定还会产生奇奇怪怪的错误,很难追溯原因。那么借由之前使用接口传递连通性判断方法的思路,我们能不能变传入对象为传入接口呢?

  传入对象,就可以使用对象所有public的数据和方法(一个package的话当然default也可以,不过一个package这么反工程的事情可干不得)。既然有可以使用的可能性那么就有了各种错误和安全问题的可能性,设计的初衷是交给它几个方法的使用权,实际上却搞成了一键root?可能有人会想开发时保证不乱调用方法即可,但是潜在的危险始终存在,我们最好还是将所有问题扼杀在摇篮里。

  如果我们对于每个类想传递的方法(信息交流内容)定义专门的接口,将接口作为参数传递进去,则就是另一番景象。由于接口对象只能使用接口中定义的方法,相当于我们已经定义好了条条框框,接收者只能使用规定的内容,配合每个方法中的规约定义和异常检测,这样就将危险的可能性降到了零。同时,将一个接口作为类之间的交流通道,信息传递必须按照接口定义的规则来,这是不是一瞬间感觉有点像操作系统中的系统调用syscall或是网络中的通信协议?这一点很好的符合了“封闭-开放原则”,即对修改封闭,对扩展开放。任何类无法修改传递信息的方式,而每个类自身可以任意的进行扩展,只要不影响传递信息的相关方法想怎么扩展怎么扩展,两边互不关心对方的发展,只要满足传递信息接口的要求即可。

  面向接口编程说到底是将设计和实现分离,这是其核心。同时,这里的“接口”并不是单单指java中的interface或是其他语言的类似语法,这是一种思想,先规约设计,再具体实现。

设计规约(JSF)

  之前的三次作业我并没有出现JSF问题,可能是由于主要是使用自然语言书写表意比较完整,那么对于同样的内容,如何使用逻辑语言达到完备的表达效果同时又十分简洁呢,我觉得一个办法是通过阅读好的写法来学习,下面上几个例子:

1.

    private synchronized int selectTaxi(){/*** @REQUIRES: None* @MODIFIES: None* @EFFECTS: \exist taxi in response;taxi has the highest credit;select taxi;*            if taxi.num>1;select the shortest current distance to passenger one;*            if not \exist taxi in response, return -1;* @THREAD_EFFECTS: \locked()*/}

  该方法是从response队列中选择出信用最高的出租车,如果有多辆车信用相同选择到乘客距离最近的一辆,返回其对应的索引值,如果队列为空返回-1.(其实应该抛出异常更好,这是出租车代码中最古老的部分了还没来得及重构)。可以看到我之前的写法主要使用了自然语言辅以部分逻辑语言,那么改进版如下:

    private synchronized int selectTaxi(){/*** @REQUIRES: None* @MODIFIES: None* @EFFECTS: (response.size == 0) ==> \result = -1;       *           (response.size > 0) ==> ((\result = index) ==>        *       (selected_taxi.index == index) && (\all taxi response.contain(taxi);taxi.credit <= selected_taxi.credit;) &&       *       (\all taxi taxi.credit == selected_taxi.credit; taxi.distance >= selected_taxi.distance;))         * @THREAD_EFFECTS: \locked()*/}

2.

   public boolean runPermission(Point src, Point now, Point dst){/*** @REQUIRES: src.inRange && now.inRange && dst.inRange && src is neighbour of now && now is neighbour of dst;* @MODIFIES: None;* @EFFECTS: \result = whether the current light state permits taxi passing through;*/}

  该方法的作用是在路口判断是否可以直接通行或是等待红绿灯,初始版是标准的“白话文”,那么改进版如下:

   public boolean runPermission(Point src, Point now, Point dst){/*** @REQUIRES: traffic.state in {0,1,2} && graph.contain(src) && graph.contain(now) && graph.contain(dst) && traffic.locate == now         *            \exist edge in edges;edge.begin == src && edge.end == now &&         *            \exist edge in edges;edge.begin == now && edge.end == dst;* @MODIFIES: None;* @EFFECTS: (\result == true) ==> trace.contain(src,now,dst) && trace.runDirection obey traffic.state;         *           (\result == false) ==> trace.contain(src,now,dst) && trace.runDirection disobey traffic.state;   */}

  首先,对于逻辑语言JSF的书写,不要从主观角度去描述行为,谁做了什么谁拥有什么,而是要从客观出发,描述客观对象的性质和状态,类似于数学定义的方法,状态A就能对应到反馈A1,状态B就能对应到反馈B1。在书写格式角度正确之后,则应该着重注意逻辑的严密性,单单的A==>B是很弱的,这仅仅描述了事物的一部分。完整来看,应该是A==>B,B==>A,!A==>!B,!B==>!A四个环节的关系,当然一般为了简化仅使用前两个,但是我们考虑问题就应该多想一点,要做到正确条件一定导致正确结果,不正确条件一定导致不正确结果,要使整个规约定义是完备的,这样才能使设计毫无漏洞。

  规约定义配合之前说的面向接口思想,将设计和实现分离开来,用接口来设计功能,用规约定义来规范每个接口和方法的内容,保证每次运行使用给定的正确的方法,每个方法的执行符合规格定义的内容,对于符合前置条件的输入进行对应的后置条件处理,对不符合的做相应的异常检查和处理。当做完这些设计工作,完成了规约层的事,这时候再开始实现层的工作就会事半功倍!这样,才叫程序设计。

转载于:https://www.cnblogs.com/swainz/p/9091255.html

设计与实现分离——面向接口编程(OO博客第三弹)相关推荐

  1. 前后端分离简单项目--蚂蚁博客--后端部分

    原文网址:前后端分离简单项目--蚂蚁博客--后端部分_IT利刃出鞘的博客-CSDN博客 简介 说明         本文介绍我从0开发的前后端分离的简单项目--蚂蚁博客.本博文介绍后端部分. 本项目是 ...

  2. 前后端分离简单项目--蚂蚁博客--简介

    原文网址:前后端分离简单项目--蚂蚁博客--简介_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍我从0开发的前后端分离的简单项目--蚂蚁博客. 本项目是一个全栈项目,使用主流.前沿的技术栈开发 ...

  3. “进度条”博客——第三周

    "进度条"博客 --第三周 第三周 所花时间(包括上课) 上课时间:一周两节课,共3个小时左右 课下时间:周二晚上8::30到晚上10点左右(熟悉同伴.制定规范.搜集相关资料并构思 ...

  4. django model对象修改_从0到1搭建个人博客-Django(三)

    ​你好,我是goldsunC 让我们一起进步吧! 从0到1搭建个人博客-Django(三) 在以下链接快速回顾系列文章内容 从0到1搭建个人博客-Django(一) 从0到1搭建个人博客-Django ...

  5. 【Python开发】Flask开发实战:个人博客(三)

    Flask开发实战:个人博客(三) 在[Python开发]Flask开发实战:个人博客(一) 中,我们已经完成了 数据库设计.数据准备.模板架构.表单设计.视图函数设计.电子邮件支持 等总体设计的内容 ...

  6. Hexo + Butterfly 从零开始搭建个人博客(三)

    有些效果无法在这儿体现,如果想看完整的效果,请移步个人站点. 原文链接:基于 Hexo 从零开始搭建个人博客(三) 阅读本篇前,请确保已经完成下面两篇文章的步骤: 基于 Hexo 从零开始搭建个人博客 ...

  7. springboot前后端分离项目(图书+博客+聊天室)

    一.项目简介 项目名称:blc management system(blc MS) 基于Vue CLI4 + SpringBoot开发的前后端分离项目. 基本功能:对博客和书籍进行增删改查,在聊天室点 ...

  8. 大一html网页制作期末网页设计 HTML5+CSS大作业——个人旅游图片博客(7页)

    HTML5+CSS大作业--个人旅游图片博客(7页) 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. 家居. 酒店. 舞蹈. 动漫. 明星. 服装. 体 ...

  9. 个人博客网站的设计与实现_新手建立个人博客网站后如何提高回访率?

    我们新手刚建立的个人博客网站人气比较少,收录也很少,所以我们需要主动出击,主动去拜访其他的个人博客网站,积极与其他博客交流互访,今天你发布了新文章我过去拜读并留言,今天我更新了文章你也过来回访并留言, ...

  10. 博客园三款APP的分析

    分析对象(仅为安卓端):1.cnblogs-博客园客户端(开发者:张林),以下称为张端:2.博客园(开发者:杭州语程信息技术有限公司),以下称为语端:3.博客园(开发者:陈睿),以下称为陈端. 一.分 ...

最新文章

  1. EBS报表 查看输出 FNDWRR.exe
  2. [雪峰磁针石博客]计算机视觉opcencv工具深度学习快速实战1人脸识别
  3. 自动化工程师与python_软件测试自动化工程师用案例带你进入Python数据类型,数据结构等代码实现...
  4. 好程序员Web前端教程分享JavaScript开发技巧
  5. Elasticsearch和solr的区别
  6. 牛逼!Python函数和文件操作(长文系列第3篇)
  7. python学习笔记5—数据类型转换
  8. 异常处理 Exception
  9. android camera2 qcom,lineage编译环境里,编译QCamera2的技术总结
  10. ORA-01034: ORACLE not available
  11. 技术中台构建思路及进展_半年中台实践思考:落地中台,贵在其神,活用其形...
  12. java direct memory_第一讲  JVM内存四大类型:Heap,Stack,Contant,DirectMemory等
  13. Python学习 Day 1-简介 安装 Hello world
  14. sql获取字符串长度函数
  15. vbs模拟post请求上传文件
  16. gamemaker学习笔记:拖拽
  17. 离线地图-geoserver
  18. windows2008修改3389端口
  19. python_faker使用
  20. 浅谈node结合express第三方插件使用跨域

热门文章

  1. 侯策:如何突破前端开发技术瓶颈
  2. Looper中的睡眠等待与唤醒机制
  3. 每个人心中都有一片极乐净土
  4. 敏捷无敌(11)之兵不厌诈
  5. 46多项式01——一元多项式和运算
  6. 3分钟tips:泛函中,什么是开映像定理?
  7. pandas小记:pandas数据规整化-正则化、分组合并及重塑
  8. 'scipy._lib.messagestream' 以及 'scipy.interpolate.interpnd.array' 解决办法
  9. ref改变样式 vue_我用React和Vue构建了同款应用,对比看看(2020版)
  10. labVIEW学习笔记(三)簇,局部、全局变量