接口的一些疑问

1、Java 开发的 dao 层和 service 层都使用接口,是否是为使用接口而使用接口?

Java 中 dao 层和 service 层都使用接口,是否是为使用接口而使用接口?

个人认为,如果没有搞懂为什么用接口,那么有些人就会逢类就要实现接口……在一些业务不复杂的场景下,真的没有必要这样做,但是心里要明白。

前面都说了很多例子和理论了,不过学习就是站在巨人的肩膀上,不断重复,归纳和升华到方法论的过程,再重复一下,引用软件工程文献:

使用接口是为了把调用与实现解耦,带来的好处是可以各干各的,带来的坏处是从一个概念变成了两个概念,增加了系统的复杂度。

衡量一下在具体场景中是弊大于利还是利大于弊,就可以做选择了。

当然,在大部分场景下还要考虑一个因素,就是你会不会写接口。没有良好接口设计能力的人,写出来的接口抽象不合理,等于没写,什么好处都得不到,只有坏处,这种情况下干脆别写。

那怎么衡量你会不会写接口呢,我的经验是,至少见过一次写了接口后得到明确好处的例子。

1.1、什么情况下需要各干各的?

最简单的场景,写接口的是 A,写实现的是 B。当然大多数类似情况没必要真的建一个 interface,然后再让别人去 implement。

另一种情况,调用代码先于实现代码编写。比如 A 开发的是 struts2 这种框架,那 A 先得搞个 Action 接口,让程序先跑起来。

1.2、回答问题

dao 做数据库读写用的。对应上面那几种情况:

1、作为架构师想写两行代码就让小弟加班干活然则自己去泡妹子的话,可能需要写个 interface ,里面几个抽象的 insert、delete 之类的方法;

2、项目在快速原型阶段如果客户满意就掏钱买oracle,如果客户不满意就用免费MySQL的话,你可能需要定义个 dao 接口然后先用内存数据库写点能让原型跑起来的实现,等一切有定论了再说;

3、每个类都有一个dao,每个 dao 都有 crud 方法的话,你可能需要定义一个通用 Dao 接口,然后搞点代码,不用一个个的去写体力代码从此登上人生巅峰。所以dao接口还是有用的。

service 作为业务逻辑的实现,得具体问题具体分析:

不去抠理论的话,什么是 service——就是一段实现了某个逻辑的代码组合。所以 service 是比 dao 更抽象的概念,严格来讲 dao 就是一种 service。只不过在 java 开发中,dao 是个人人都得写的东西,所以都拿出来单说。因此,service 跟 dao 没有本质分别。

1.2.1、从工序上说

写上一层的时候,会用到下一层提供的逻辑,具体表现形式就是各种各样的 service 类和里面的方法。上一层开始的时候,一定会知道下一层会干什么事,比如 “将传入编号对应的人员信息设置为离职”,但下一层的代码不一定已经实现好。所以需要有个接口,让写上层代码的人先能把代码写下去。有各种理由可以支持这种工序的合理性,比如一般来说,上一层的一行代码会对应下一层的好多行代码,那先让写上层代码的人写一遍,解决高端层面的bug,会提高很多效率。

1.2.2、从抽象角度说

不同业务模块之间的共用,不一定是共用某段代码,也可能是共用某段逻辑,这时候就需要抽象一个接口层出来,再通过不同的注入逻辑实现。

比如模块1是登记学生信息,模块2是新闻发布,看上去风马牛不相及。但分析下来如果两个模块都有共同点,顺序都是;

1、验证是否有权限

2、验证输入参数是否合法

3、将输入参数转化为业务数据

4、数据库存取

那就可以写一个 service 接口,里面有上述 5 个方法,再分别写两个 service 实现。具体执行的时候,通过各种注入方法,直接 new 也好,用 spring 注入也好,实现不同的效果。

java 的各种 mvc 框架都提供机制给你干这个事。每个项目或产品,都应该可以用类似的思路抽象出一些东西,如果抽象合理,会很大程度的提高项目架构的合理性。

这些搞定,写个接口然后实现 mock 用于单元测试这种事,信手拈来。

说实话,总结到这里,都是之前的种种的疑问的解答和对概念理解的升华,也是为了复习用,但是说来说去就是那些东西,高内聚,低耦合,开闭原则,单一职责原则,面向接口编程原则,业务和数据分离,工作内容分离,解耦,封装,多态,代码隐藏……其实就是反复这些东西的举例,早已经在十几年前就让前辈和大牛们玩烂的东西……

2、“接口与具体实现分离”,具体实现到底是指什么?

刚开始阅读《Thinging in Java》一书,有如下的说法:

接口与具体实现分离

这里的具体实现如何理解,这里还是要联系到接口的重要使用目的之一:向上转型。

利用接口可以被多个类去实现的特征来分离工作内容,分离不同的业务逻辑而去灵活的插拔不同的但可以替换的实现方法。

例如,有不同的动物,叫声不一样,只需要定义一个”叫声(xxx)“方法,而让牛、羊、青蛙等等去具体实现这个“叫声(xxx)”方法,调用的时候只需要:

动物.叫声(xxx);

就能发出对应动物的叫声了,还要联系接口的一个重要使用目的之二:提供行为的统一的约束,避免实例化,也就是说和具体实现要分离。

在这里再简单重复一些内容:接口往往定义的是一些行为,在设计原则里面有一条“单一职责“的原则,接口的作用只是提供一些方法给你,它不关心你是怎么使用的。就像电脑的 USB 接口,我们不需要关心这个 USB 接口是怎么实现的,我们只需能够使用这个USB接口。

3、封装之后的类,源代码还是能被使用的程序员看到,怎么就说接口起到隐藏的作用?

客户端不等于客户端程序员,可见性也不是针对程序员的,其实很多问题,站在计算机的角度去看就一目了然了。

客户端是指调用它的类或者具体对象,例如私有域不对具体的对象暴露,封装能够保证外部的对象或者实例不能修改它,从而保证了类的安全。

封装的作用不是真的把所有代码实现都让客户端程序员看不到,这个隐藏的目的是让客户端在调用方法等行为时能够按照编写 API 的程序员制定的规则来:一个变量不能让人家随便修改,也不能随便就能获得值,赋值要通过 setter,获得值需 getter 等方法,变量相应的就要通过 private 来隐藏。

举一个形象的例子,形容程序员给用户留了误操作的坑,用来描述因封装不当、没有对类做好隐藏而导致 API 使用问题也是可以的。

假设有一个自行车类,它有两个成员变量——轮子(wheel)和踏板(pedal),它希望客户端能调用『踩踏板』这个方法前进,也能调用『换轮子』这个方法维修自行车,但决不能让用户在『踩踏板』的同时『换轮子』,甚至像图上那样把棍子插轮子的缝隙里,那么就需要把轮子变量设为 private——隐藏起来,不让用户自由获取,得按照你的规则来。

比如骑车的时候就只返回 null,停止的时候就可以返回正常——让用户可以做换轮子之类的工作。

setter 的作用也是类似的,都是为了保证客户端调用时能够遵循 API 设计者的规则,否则调用时就会出现各种不可控的乱象,轻则让调用者骂『这是哪个 SB 设计的 API,代码写起来太难管理了』,重则会出现很多意想不到的 bug,也许实际上的封装应该是对轮子再做进一步的封装,那么就要用内部类的方式。

总之看书的时候对有些想很久也不能明白的东西不用太钻牛角尖,先照着书上说的做,等写了足够的代码,经验多一点了,自然就能理解一些东西为什么要那样做了。 

猜你喜欢

转载自www.cnblogs.com/kubixuesheng/p/10340267.html