V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
awesomeMen
V2EX  ›  Java

在 Java 业务系统的开发中, service 层有必要写个接口吗?

  •  1
     
  •   awesomeMen · 2021-03-12 10:15:29 +08:00 · 5949 次点击
    这是一个创建于 1350 天前的主题,其中的信息可能已经有所发展或是发生改变。
    感觉没啥卵用啊?有大佬出来解答一下吗?
    44 条回复    2022-04-13 09:48:53 +08:00
    RedBeanIce
        1
    RedBeanIce  
       2021-03-12 10:18:32 +08:00
    大部分系统没用。。。
    hello2060
        2
    hello2060  
       2021-03-12 10:21:32 +08:00
    接口那肯定是因为要复用啊,把公用的部分抽取出来,没这个需求就不用呗。
    justNoBody
        3
    justNoBody  
       2021-03-12 10:22:35 +08:00   ❤️ 2
    如果你这个类的工作简单,且业务上目前没有什么需要扩展的,没有什么变化点,我觉得是可以不写接口的。
    但是,如果这个 service 会存在多种实现的时候,比如你的 service 是需要调用运营商的短信接口,然后你们对接了多个运营商,这个时候接口就非常有必要了。
    有了这个接口以后,其他同学只需要根据你定义的接口,传入必要的参数,选择一个合理的实现(或者你用策略模式封装一下),就可以非常简单的发出短信。
    个人见解,欢迎讨论
    awesomeMen
        4
    awesomeMen  
    OP
       2021-03-12 10:22:59 +08:00
    @hello2060 为什么复用就要加个接口?直接在 service 层写方法不行吗?
    yeqizhang
        5
    yeqizhang  
       2021-03-12 10:24:21 +08:00
    面向接口编程... 主要是接口代理、接口继承、极小的可能直接改成 rpc...
    wakzz
        6
    wakzz  
       2021-03-12 10:24:59 +08:00
    绝大多数系统用不上接口抽象,后来就很少用接口了,除非一开始就确定这快逻辑有多个实现类所以要抽象个接口出来。
    hafuhafu
        7
    hafuhafu  
       2021-03-12 10:27:48 +08:00
    你要是保证项目所有 service 从现在到将来都只有一个实现,那肯定没用啊。
    这个就是个规范而已,约定优于配置。你喜欢的话不要 service 都行。而且业务系统这种基本类和接口都是自动生成的,在意这个干吗...不就接口加一个方法,实现类实现一下的事。
    KarmaWu
        8
    KarmaWu  
       2021-03-12 10:28:01 +08:00
    看情况,假设要区分多个版本的接口,须有不同的实现类
    securityCoding
        9
    securityCoding  
       2021-03-12 10:29:01 +08:00
    1. 如果都是内部自己调用,那没有必要,一个 class 撸到底就行
    2. 如果有外部 rpc 调用的话,还是定义一个接口方便打包到 client-sdk 给别人
    yeqizhang
        10
    yeqizhang  
       2021-03-12 10:34:40 +08:00
    @yeqizhang 才发现说错了,接口哪有继承....修正:接口多个实现
    chendy
        11
    chendy  
       2021-03-12 10:35:07 +08:00
    大部分业务场景用不上,能想到的需要用到的场合:
    1. 上古系统没有 cglib 之类的只能用 Proxy 所以必须接口 ​
    2. 需要多个实现
    3. 要分发 service 层代码
    soupu626
        13
    soupu626  
       2021-03-12 10:38:03 +08:00
    基本原理就是,大家先约定好,要什么参数,什么返回,然后就可以不关心底层细节和上层逻辑,各干各的就行了,还有就是结对的时候,便于测试,定好接口,一个写实现,一个写测试,本质上是为了解耦
    但是现在大家都是一个人一把梭,就是个象征意义,可以不加
    Macolor21
        14
    Macolor21  
       2021-03-12 10:43:21 +08:00 via iPhone
    提问之前善用搜索引擎,了解下提问的艺术,这个问题在 StackOverFlow 已经有人提问,并且多个答案很好的解释了。/questions/55087578/do-i-really-need-to-create-interfaces-in-spring

    总结来说,这样的写法是用于扩展用(利用 DI ),你常规理解的用法,这个 service 有不同实现,然后就是 DI 的好处了。

    但如果你的 service 不需要另外的实现,你可以不用写,Spring IOC 也能将你的 service 注入到其他类中。现在微服务盛行,没啥机会让你为一个 service 写多个实现了。

    但是基于接口,你可以在测试时,不使用生产的业务,通过写一个 Mock 实现,配置不同的配置文件,来测试你的这个 service 接口。
    zjsxwc
        15
    zjsxwc  
       2021-03-12 10:45:47 +08:00   ❤️ 9
    用 interface,是为了能够方便地在屎山上拉屎啊,

    不用 interface,你需要挖开老屎,看老屎里面有啥,用来 interface,你只管在老屎上面拉新屎。
    xlui
        16
    xlui  
       2021-03-12 10:53:19 +08:00
    先说结论,有必要。

    就算只有一个实现类,也应该保留接口。这种情况下,通过查看接口,我能很快的找到对外暴露的方法,后续维护重构都很省心。

    如果不保留接口,那我在查看类的功能时候就得遍历所有的 public 方法,时间一长,转手的人一多,这个类就会无限膨胀下去,最终变成屎山。
    zjsxwc
        17
    zjsxwc  
       2021-03-12 10:58:00 +08:00
    interface 无非就是给屎打上标记而以,这是黑色的屎、这是黄色的屎,这是硬的屎,这是稀的屎,
    这个坑只能拉 又黄有稀的屎,我就拉 又黄有稀的屎,我 tm 才不管你这个坑里面有什么屎,以前有谁在这个坑里面拉屎。



    如果屎没有各种标记分类,就相当于,这是张三拉的屎,这是赵四拉的屎,tm 的这个坑居然只能张三来拉屎,我要拉屎,只能让张三来代替我拉屎了,张三又不在,怎么办啊,只能把我变成张三,很累的好不。
    zjsxwc
        18
    zjsxwc  
       2021-03-12 11:01:38 +08:00
    当然,我可以继承张三啊,我就又可以拉屎了,可是很多时候,我虽然继承了张三,但我看不惯张三,张三吃素,我吃肉的,根本就是格格不入
    sleeepyy
        19
    sleeepyy  
       2021-03-12 11:01:48 +08:00
    @zjsxwc 直接看呕吐了。。。
    zjsxwc
        20
    zjsxwc  
       2021-03-12 11:13:18 +08:00
    当然,你可以一个屎填坑,这个坑永远只能拉这个屎,有需求,就改造这个屎本身,也就是楼主主题里说的。

    我明明能一个屎完事,这个 final 屎就是牛逼!没有毛病。
    uselessVisitor
        21
    uselessVisitor  
       2021-03-12 11:24:51 +08:00
    接口顶层,比如说 IDeviceService 然后可以有多个实现类 DefaultDeviceServiceImpl 、xxxDeviceServiceImpl ... 引用的时候用 @Resauce 指定具体哪个实现类?我觉得这样比较好。但是实际工作中,都是一个接口实现类撸到底
    zjsxwc
        22
    zjsxwc  
       2021-03-12 11:28:37 +08:00
    先贤们是拒绝改造这个屎本身的,
    改造本身意味着你之前拉的这个屎,“有 bug”,只有不完美的屎才需要被再次改造,
    这是对你拉屎技术的侮辱,

    所以讲究的先贤们提倡用 interface,避免改造屎本身,除非真有 bug 才去改造屎本身,别动老屎!

    当然不讲究的我,改个屎而以,给钱就行!
    clf
        23
    clf  
       2021-03-12 11:46:50 +08:00
    JDK8 接口可以使用 default 关键字在接口里提供默认的抽象方法的实现:
    依据这几个特性,我可以封装一些泛型接口,提供可复用的泛型方法,实现类里只需要写业务部分的代码,由于大部分的服务都是继承于相同逻辑的接口,这样无论是基本的异常处理、日志记录、逻辑删除控制等等都能统一,并且在未来对这种统一的逻辑进行调整的时候,也只需要修改一个地方。如果某种业务有其它的逻辑,可以重写接口的默认实现。

    接口可以多重继承(一个类实现多个接口):
    在微服务场景中,一个服务可能被其它微服务调用,也可能被自己这个微服务里的其它 Service 类或者 Controller 调用,两边能调用的方法有相同的,也有不同的,通过提供不同的接口来控制方法的可见性。
    HelloWorld556
        24
    HelloWorld556  
       2021-03-12 11:54:00 +08:00   ❤️ 3
    这是一篇有味道的文章
    xuanbg
        25
    xuanbg  
       2021-03-12 11:55:20 +08:00
    没有逻辑上的必要性,但有工程上的必要性。一来我可以只写接口,别人去写实现。二来方便复制粘贴生成其他代码的基本结构及注释。
    miv
        26
    miv  
       2021-03-12 12:27:02 +08:00 via iPhone
    @zjsxwc 哈哈哈哈哈哈哈笑死我了
    ychost
        27
    ychost  
       2021-03-12 12:36:26 +08:00
    个人觉得最佳实践,当需要提供 RPC 调度的时候可以加接口(毕竟 RPC 要提供 二方包),当内部相互调用的 Service 就没必要写,啰里啰嗦,改起来也麻烦,除非有那种根据配置切换 Service 的需求,但是这种一般通过 SPI 来做
    lff0305
        28
    lff0305  
       2021-03-12 12:41:05 +08:00
    写个接口再写实现是基于以下的原因:
    1. "面向接口编程"的实践
    2. spring aop, 很久以前基于 jdk 动态代理的实现必须要接口,但是后来 cglib 稳定之后就可以不需要。所以接口-实现这个习惯就保留下来了。
    liuzhaowei55
        29
    liuzhaowei55  
       2021-03-12 12:58:37 +08:00 via Android
    给自己业务用就没必要,对外开放就有必要。
    CrazyBoyFeng
        30
    CrazyBoyFeng  
       2021-03-12 13:48:10 +08:00
    我给三楼的举例补充一下代码说明:
    ```
    运营商 Interface 运营商=null;
    switch(用户.get 运营商()):
    case 移动:
    运营商=new 移动();
    break;
    case 联通:
    运营商=new 联通();
    break;
    try{
    运营商.发短信();
    }catch(短信失败 Exception e){
    运营商.打电话();
    }
    ```

    这段代码不用接口的话,实现相同功能可能需要写出许多重复代码。
    cmsyh29
        31
    cmsyh29  
       2021-03-12 13:50:24 +08:00
    额。我以为这是以前只支持 JDK PROXY 的遗留...
    vate32
        32
    vate32  
       2021-03-12 14:21:55 +08:00
    我觉得,如果只是一个定义只有一个实现这种方式的话,没有必要搞了,费事也令人费解。但是倒是可以使用这种接口+实现的结构,实现一些设计模式,比如说这两天就在用的责任链模式。。。
    kifile
        33
    kifile  
       2021-03-12 14:47:04 +08:00
    多人开发的时候是有意义的,找一个人把接口定义写了,后续别的同学基于接口定义,各自写各自的逻辑,挺好的。

    各种测试逻辑也可以同步开始
    oneisall8955
        34
    oneisall8955  
       2021-03-12 15:30:54 +08:00
    emmm,上个月才看到类似帖子 /t/747463
    robinWu
        35
    robinWu  
       2021-03-12 15:33:11 +08:00
    你需要 mock 单测吗?
    litchinn
        36
    litchinn  
       2021-03-12 15:59:50 +08:00
    这是个历史遗留,完全可以不用
    litchinn
        37
    litchinn  
       2021-03-12 16:09:06 +08:00
    @litchinn 当然如果使用 SPI 之类的机制则另说
    sha851092391
        38
    sha851092391  
       2021-03-12 16:20:02 +08:00   ❤️ 1
    首先看你的 service 是什么类型,是业务逻辑类型还是外部集成类型(例如调用 xx 短信渠道发短信)。

    业务逻辑类型其实是基本上不需接口,如果非得考虑什么接口多半是因为考虑到以后 service 变成 rpc 调用,但是实际上就算变成 rpc 调用,业务逻辑类型的上层也会再包装一层,所以基本上不会用接口。

    外部集成类型因为会有多种实现的可能性,考虑到扩展实现所以会使用接口来做。
    superliuliuliu1
        39
    superliuliuliu1  
       2021-03-12 16:24:28 +08:00
    @zjsxwc 味道十足
    hehe12980
        40
    hehe12980  
       2021-03-12 17:40:24 +08:00
    我个人认为 service 这层一定是要写接口的,最主要是为了抽象,当然有很多开发的人根本不明白写接口背后的意义,大部分都是要求写就写,先说结论写接口是为了抽象(可能你觉得是废话),随便假定一个场景把,比如我有一个 IA service 接口这个接口,处理一个很纯粹的业务逻辑。
    场景 1:比如说步骤 1.是操作数据(必须要调 IBService,ICservice) 步骤 2.是保存数据,一开始小张这么写,它一个个调 IBServcie.operate,ICService.operate,保存也是如此,有一天业务变更,突然 IDService 也要存,继续改代码,这个时候就没有想过把公共方法抽出来比如叫 ISaveService,IOperateService,注入到 list 里,遍历去操作,去存。以后再加其他的 IEservice 这一块的代码不用动了。只需要写 IEservice 的逻辑
    场景 2:再比如说步骤 1,是操作数据(根据类型,可能调 IBService,也可能要调 ICService,但是只调其中一个就能把业务完成了), 步骤 2 是保存数据,小张写了个 if,else 去干 后面随便类型的增多 if,else 越来越长就跟屎山一样,如果一开始就把类型做成特征比如说 IOperateService 定义两个方法 supportType ( type )以及 operate(params) 然后 IBService,ICService 去实现这个 IOperateService 接口,注入这个 List<IOperateService> list 遍历去找适配的类型的 Service, 去 operate 。 后面再加更多的类型 这一块也是不需要动的。
    例子有很多很多,抽象很重要,觉得不重要的,武断的说一句,大概率是个菜鸡。
    sha851092391
        41
    sha851092391  
       2021-03-12 17:42:44 +08:00
    @sha851092391 还有一种情况业务方法也要使用接口,就是你的 service 写太多乱七八糟的方法了,这时候可以通过接口去规范提供对外的调用方法。
    54kael
        42
    54kael  
       2021-03-13 16:05:32 +08:00 via Android
    @zjsxwc 求你换个比喻吧
    linbiaye
        43
    linbiaye  
       2021-03-22 13:12:41 +08:00
    在 spring 的和 cglib 的加持下,没有必要。如果一个 interface 只有一个实现,这种抽象的意义在哪里?
    Nois9527
        44
    Nois9527  
       2022-04-13 09:48:53 +08:00
    @zjsxwc 太形象太对了,我终于知道该怎么对别人描述这个事了!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3188 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 00:04 · PVG 08:04 · LAX 16:04 · JFK 19:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.