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,也许实际上的封装应该是对轮子再做进一步的封装,那么就要用内部类的方式。
总之看书的时候对有些想很久也不能明白的东西不用太钻牛角尖,先照着书上说的做,等写了足够的代码,经验多一点了,自然就能理解一些东西为什么要那样做了。
欢迎关注
dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!