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

Spring 里 Service 层分成接口+Impl 的好处是什么?

  •  
  •   AAASUKA · 2021-01-02 18:17:37 +08:00 · 8828 次点击
    这是一个创建于 1454 天前的主题,其中的信息可能已经有所发展或是发生改变。

    分开写感觉很累赘,为什么很多人这么选择?
    为什么不直接用一个 Service 类实现

    70 条回复    2023-07-26 17:20:28 +08:00
    hcx0
        1
    hcx0  
       2021-01-02 18:19:20 +08:00   ❤️ 16
    现在对 99%的项目来说没有好处,就是机械的遵照惯例
    dorothyREN
        2
    dorothyREN  
       2021-01-02 18:30:40 +08:00
    我就懒的写 impl,直接 service 真香
    jzmws
        3
    jzmws  
       2021-01-02 18:39:15 +08:00   ❤️ 1
    1. 接口说要做什么
    2.impl 说明如何做
    现在大部分都是一个接口一个 impl ,最早的设计思想应该是接口对应多个 impl

    用接口的好处是我无需关注他是如何实现的,你一个接口可以有多重实现类 , 后期方便维护, 屏蔽底层实现细节


    不好的地方是 代码量变多了
    araaaa
        4
    araaaa  
       2021-01-02 18:46:38 +08:00
    业务代码里没卵用的设计
    tidezyc
        5
    tidezyc  
       2021-01-02 19:01:22 +08:00 via Android
    首先面向接口设计肯定是一种合理和标准的编程规范,其次当十几个人维护同一份代码的时候,遵守这样的标准规范是保证代码质量和可维护性的前提,当然你要自己一个人写着玩就随意了
    AlisaDestiny
        6
    AlisaDestiny  
       2021-01-02 19:09:52 +08:00
    如果你能明白 java.util.List 接口存在的意义你自然就知道为什么这样设计。
    laminux29
        7
    laminux29  
       2021-01-02 19:14:19 +08:00
    详细原理与实施经验在软件工程课程里,建议找书仔细看看。
    Cbdy
        8
    Cbdy  
       2021-01-02 19:28:15 +08:00 via Android
    这样做很蠢,请不要这么做
    hantsy
        9
    hantsy  
       2021-01-02 20:07:14 +08:00
    1,大型项目,基于接口的设计是比较理想的。(对于中国公司来讲,大部分还是套用三层结构,没有架构设计成分)
    2,在 Spring 中 Proxy 无处不在,早期 Spring 的技术限制,比如使用了某些技术,创建 Proxy 只能用于接口。
    freebird1994
        10
    freebird1994  
       2021-01-02 20:41:36 +08:00 via Android
    没有用。就像楼上说的,其实你的接口只有一个实现。没有任何卵用
    chendy
        11
    chendy  
       2021-01-02 20:49:17 +08:00
    现在没用
    1. 现在的项目很少需要多个实现
    2. 抽接口主要因为 java 自己的动态代理机制需要接口,但是现在用的 cglib 子类的并不需要
    3. 有的时候还是需要的,需要的时候抽就行,IDE 的重构工具抽个接口很快的
    xuanbg
        12
    xuanbg  
       2021-01-02 20:56:25 +08:00   ❤️ 15
    好处是我可以只写 service,impl 交给别人去写。
    quantal
        13
    quantal  
       2021-01-02 21:52:14 +08:00
    你在问这个东西的时候就说明不需要,需要也就不用问了
    tedzhou1221
        14
    tedzhou1221  
       2021-01-02 22:32:20 +08:00 via iPhone
    举个我现实的栗子

    我一个功能接口,有三套逻辑,根据类型去判断到底用哪套逻辑。不同逻辑就是不同实现类

    所以我要用接口。

    再使用策略模式+spring 的注入机制。我调用层只注入接口就可以。

    但很多时候,还是一个接口一个实现类。

    有很多同事都习惯把不同逻辑的全放到一个实现类。各种 if 判断……
    tedzhou1221
        15
    tedzhou1221  
       2021-01-02 22:44:35 +08:00 via iPhone
    接受上,如果有增减实现类需求,可以通过 @congfiguratong 去从配置里控制,或者用 spring spi 去实现。
    tedzhou1221
        16
    tedzhou1221  
       2021-01-02 22:45:29 +08:00 via iPhone
    手机打字,打错了几个字……
    yetone
        17
    yetone  
       2021-01-02 23:16:22 +08:00
    为了可以写测试啊同学们……
    ElmerZhang
        18
    ElmerZhang  
       2021-01-02 23:24:56 +08:00
    Java 写得不多,只想请教一下,不用 Interface 的话,单测怎么写呢?
    cheng6563
        19
    cheng6563  
       2021-01-02 23:38:45 +08:00 via Android
    curd 的业务没用,其他的局部功能才有用。
    extreme
        20
    extreme  
       2021-01-02 23:46:54 +08:00
    面向接口而不是具体实现进行开发
    一方面解耦
    另一方面,我觉得也是最重要的一方面,就是方便单元测试
    hantsy
        21
    hantsy  
       2021-01-02 23:56:46 +08:00
    @chendy 多个实现的场景太多。

    只是国内的架构师,做软件设计的角色,没有起到相应的作用,其根源也是国内的公司都是猴急一样的做项目,没有回到软件系统设计本身,各种文档,各种形式很足,做出来的东西一团糟。

    对于复杂的业务流程,都是可以完全接口调用方式设计,完全先不管具体实现,用一个 Dummy 实现先去验证设计上的逻辑是否能够合理。

    我在国内公司呆那么多年,从来没有哪个公司会让你花时间写 POC 去验证一套流程的合理性。如果做国外的产品项目就完全不一样,你有很多机会去想如何才能做到真正的逻辑上的解耦。
    hantsy
        22
    hantsy  
       2021-01-03 00:00:11 +08:00
    @extreme
    @ElmerZhang
    @yetone 早期写测试,Mocking 的时候,的确需要接口。但现在最流行的 Mockito 一样是可以 Mock 类的,Spring 进行了集成,可以直接 @MockBean,@SpyBean 。
    hantsy
        23
    hantsy  
       2021-01-03 00:02:02 +08:00   ❤️ 1
    Lxxyx
        25
    Lxxyx  
       2021-01-03 00:03:25 +08:00   ❤️ 1
    理论上这种面向接口编程可以做到先思考,再实现等诸多优点。

    实际上很多业务的接口都是一对一的,不会复用,也不会留足时间给你慢慢先思考再上线。。更多的是 CRUD 莽代码。

    但自己还是要知道这种编程范式和带来的优势的(楼上都说过了),用不用那是另外一回事。
    hantsy
        26
    hantsy  
       2021-01-03 00:04:20 +08:00
    不过这里本身使用了 SpringData,Repsoitory 就是接口,用具体的类也是一样的测试。
    hantsy
        27
    hantsy  
       2021-01-03 00:14:14 +08:00
    真正的软件产品开发,你应该花一半时间去思考如何实现(接口抽象的阶段),另外一半时间去写实现和测试。
    young1lin
        28
    young1lin  
       2021-01-03 00:15:22 +08:00
    1. 面向接口编程,而非实现(其实这个是屁话,更多的是代码遗留问题,因为以前的项目都是贫血模式开发的,这个也适合中小项目)。

    2. 如果你实际编程中由于某些原因想替换掉其中一个方法的话,可以替代 @Lookup 注解,直接换另一个实现类。

    3. 还有一个是 RPC 调用,引入接口即可,然后通过 Annotation*BeanPostProcessor 之类的将接口装饰成具体代码调用。例如 @DubboService 这种。你定义入参就行了,不用关心其具体的实现,你在其他 Service 或者 Controller 直接构造方法注入就 ok 了。
    ruoxie
        29
    ruoxie  
       2021-01-03 00:46:26 +08:00
    跟创建代理有关吧,没写过 java,Spring 里应该是一堆的 IOC,AOP 。之前写过 c#,c# 里创建代理要么基于接口实现,要么是重写虚方法,一般都是基于接口实现。
    simonlu9
        30
    simonlu9  
       2021-01-03 10:57:09 +08:00
    c 语言也是这样的,先声明下头文件,然后再写实现,后来 spring 的引入,让这个更加有说服力
    hantsy
        31
    hantsy  
       2021-01-03 10:57:38 +08:00
    @ruoxie 早期 Proxy 都是需要接口的。
    anzu
        32
    anzu  
       2021-01-03 11:13:38 +08:00
    1. 可以让技术负责人之类的专注于业务框架的搭建,具体实现让组员来做。
    2. 微服务,调用者需要知道接口参数,但不需要知道接口实现。
    3. 安全性,算是微服务的延伸,比如 UserAuthService 之类的,无关人员只需以 git submodule 引入接口项目即可使用,做到了代码的保密性。
    hantsy
        33
    hantsy  
       2021-01-03 12:09:52 +08:00
    @anzu Microservice 一个原则,各服务应该自治,根本就不关心其他服务的代码层面的实现(不管是接口还是 Impl ),每个服务都是可以用不同的语言和框架实现。

    服务之间交互通过轻量协议( Http,Message Broker,gRPC,RSocket 等)定义。
    tianlan
        34
    tianlan  
       2021-01-03 12:21:35 +08:00
    最近在看设计模式。 书上说 桥接模式 是将一个工程的 抽象与实现分离,从而在开发的时候独立使用。 我想 这个是一个扩展
    idoggy
        35
    idoggy  
       2021-01-03 12:24:39 +08:00 via Android
    可以建议团队内部放弃这种写法,很好推动的,根本不会有阻力。除非是很老早的 spring 那没办法得带上接口,现在项目里接口也就用在多态上,普通流程都是 controller 调 service 调 dao 完事。
    taowen
        36
    taowen  
       2021-01-03 13:26:56 +08:00 via Android
    你感受不到好处,就是没好处,不要怀疑。
    312ybj
        37
    312ybj  
       2021-01-03 13:30:08 +08:00
    首先这个是有用的。
    面向接口编程的优势很大,策略模式可以借助接口实现具体策略的导入。
    你慢慢实践设计模式,你就会发现设计模式里都是这种设计,
    Cbdy
        38
    Cbdy  
       2021-01-03 13:49:30 +08:00 via Android
    @ElmerZhang mockito
    charlie21
        39
    charlie21  
       2021-01-03 14:17:02 +08:00
    保障 JAVA 大军的就业人口
    zxCoder
        40
    zxCoder  
       2021-01-03 14:37:18 +08:00
    不知道 学的时候大家都这样写,去公司实习,发现大佬们也是这样写,就跟着写了 🐕
    Hadoop270
        41
    Hadoop270  
       2021-01-03 14:39:14 +08:00
    这个问题 在我刚学 java 的时候也想到了- -
    xiangyuecn
        42
    xiangyuecn  
       2021-01-03 14:49:06 +08:00
    以前也写过 XXXService XXXImpl,还有 XXXMapper.java XXXMapper.xml , 还有 XXXEntity 还有 XXXController

    繁琐,堆砌,复用说不上,难看才是真的

    现在只剩 XXXController,其他的除非确实必要,否则一概不写😂
    zhazi
        43
    zhazi  
       2021-01-03 15:14:07 +08:00 via Android
    粘一段 Jhispter 对这里的理解
    zhazi
        44
    zhazi  
       2021-01-03 15:15:39 +08:00 via Android   ❤️ 1
    Should we use interfaces with our Service Beans?

    Short answer: No.

    If you want the long answer, here it is:

    One of the main interests of using Spring is AOP. This is the technology that allows Spring to add new behaviors on top of your Beans: for instance, this is how transactions or security work.

    In order to add those behaviors, Spring needs to create a proxy on your class, and there are two ways of creating a proxy:

    If your class uses an interface, Spring will use a standard mechanism provided by Java to create a dynamic proxy.

    If your class doesn’t use an interface, Spring will use CGLIB to generate a new class on the fly: this is not a standard Java mechanism, but it works as well as the standard mechanism.

    Some people will also argue that interfaces are better for writing tests, but we believe we shouldn’t modify our production code for tests, and that all the new mocking frameworks (like EasyMock) allow you to create very good unit tests without any interfaces.

    So, in the end, we find that interfaces for your Service beans are mostly useless, and that’s why we don’t recommend them (but we leave you with the option to generate them!).
    loongwang
        45
    loongwang  
       2021-01-03 15:16:01 +08:00
    降低其他人的阅读难度, 例如你这个 service 代码很多,有很多 private 方法, 当其他人用到或者阅读你的代码时, 可以直接从接口中获取到必要的信息,而不是去读一遍你的代码才能获得必要的知识,
    asanelder
        46
    asanelder  
       2021-01-03 16:59:24 +08:00
    @hantsy #21 "猴急" 这个词用的真实。。。

    "如果做国外的产品项目就完全不一样,你有很多机会去想如何才能做到真正的逻辑上的解耦。”

    国外真的这样么?

    @hantsy #27 俺遇到的基本都是:设计和测试是最不给时间的,每天都是写写写,然后早会汇报完成了多少多少。

    你要是连着几天汇报在搞设计,上面会认为你在没有任何产出。。。

    有时真不知道这些“上面”当初是如何写软件的。难道他们也不是过来人么?难道他们体会不到这东西也要设计,也要 demo 的么。。。
    asanelder
        47
    asanelder  
       2021-01-03 17:06:03 +08:00   ❤️ 2
    俺感觉楼主面对的项目是这样子的。

    1. 贫血模型:从 dao 层查出来的只是数据,而且是和具体 dao 层字段绑定的格式(如 mysql )
    2. 所有的业务都在 service 层( service 中各种对贫血模型字段的判断和处理)

    因为"业务"本身就不是抽象的,不需要多实现,所以,导致的就是 service 接口和对应一个 service impl 这种鸡肋的形式存在。你想想,同一 service 接口,你还能有其它什么实现么?

    真正需要接口的是需要抽象时,需要和具体实现无关时,如

    一个接口 UserRepository 表示用户数据仓库

    而实现可以是

    FileUserRepository
    DBUserRepository
    XXXUserRepository

    这样多实现是有意义的。

    而业务都写在 service 类里,那么业务的多实现指的又是什么呢?

    造成楼主的困惑原因,可能就是"前人都这样写,那我也这样写吧"

    于是,就这样了
    qiumaoyuan
        48
    qiumaoyuan  
       2021-01-03 17:10:09 +08:00
    高级
    fkdog
        49
    fkdog  
       2021-01-03 17:39:40 +08:00
    我觉得我们应该用发展的眼光看问题.
    service-impl 以及设计模式不是啥圣经, 大部分项目开发搞这些玩意纯属没事找事.
    liuxey
        50
    liuxey  
       2021-01-03 19:38:27 +08:00
    我的个人见解:就是历史糟粕
    xcstream
        51
    xcstream  
       2021-01-03 20:06:14 +08:00
    可以按代码量算 kpi
    NilXuan
        52
    NilXuan  
       2021-01-03 20:38:40 +08:00   ❤️ 2
    从数量上来看,一个 Service 可以对应一个多个 Impl ;面向接口编程可以使多个 Impl 之间的替换变得极为简单;比如,开发时,我把验证码发送到控制台;上线后切换到真实运营商服务,只需改变注入的 Bean,而无需到处修改;这就是好处(之一);
    需要考虑的问题是,是用不到还是没有用?如果用不到,是设计没设计好,还是根本没设计?是本来就用不到,还是应该用到,但是没有用到?我认为不能简单地说有用还是没用;个人不认同“历史糟粕”或者“历史遗留”这种看法;至少这种一对多的关系,就是比一对一要灵活;
    kkeep
        53
    kkeep  
       2021-01-03 20:56:40 +08:00
    对我来说只有一个好处,就是强迫自己设计接口。而不是 impl 里面随意加函数。
    joecqupt
        54
    joecqupt  
       2021-01-04 00:24:42 +08:00
    接口可以用 JDK 做动态代理,方便之后暴露 RPC 或者其他什么逻辑处理
    Aresxue
        55
    Aresxue  
       2021-01-04 10:35:17 +08:00
    老月经贴了,继承->接口实现->容器->注解,当你能清晰弄清楚它们的依赖程度并能在项目中选择合适的时候你就不会有困惑了。当然这是对于需要精细打磨的项目而言,而大多数的项目本身就是对业务,本身就是一种快消品罢了
    reed2020
        56
    reed2020  
       2021-01-04 11:50:41 +08:00
    我接触到的一些这样写的项目,它们的 service 和 impl 都是一一对应的,没啥卵用。
    Zhancha
        57
    Zhancha  
       2021-01-04 13:30:53 +08:00
    项目经历应该会很爽,直接看就行,不需要看里面的。
    jk1030
        58
    jk1030  
       2021-01-04 13:37:33 +08:00
    面向接口,重构的时候及其舒爽
    另外还有一个就是代理呵 rpc 吧
    jason19659
        59
    jason19659  
       2021-01-04 13:59:18 +08:00
    @loongwang #36 但是现在所有 ide 都有 schema 啊
    hyqCrystal
        60
    hyqCrystal  
       2021-01-04 14:21:13 +08:00
    @joecqupt 但实际上即便是暴露给 RPC 大多数是 API 层暴露给 RPC
    iceneet
        61
    iceneet  
       2021-01-04 14:24:17 +08:00
    增加代码量
    hantsy
        62
    hantsy  
       2021-01-04 14:33:55 +08:00
    @NilXuan 大部分人没这么用过吧。可能是对于很多就用完全两个不同的 Service 类吧,Console 和运营两个类呗,见太多了。

    对于很多人用类,接口跟 PHP4 的函数没差别,只是堆代码,没有从语言和软件的角度考虑为什么有接口和类这些不同的玩意。

    我说过,我以前很多时候写 POC,比如银行对账的一个简单的流程,将请求类,响应类,各种异常路径包装成异常,所有的行为用接口来描述。用简单的 Dummy/Fake 实现类(伪数据填充出来的),用测试验证一个流程中各接口的合理性,一步步连接起来是否通畅,是否所有异常路径都是可以顾及到。

    经过反复讨论过后,再把接口抽出来 ,切换到真实场景去实现( Bank Credit,Stripe 等)。
    securityCoding
        63
    securityCoding  
       2021-01-04 15:28:02 +08:00
    多数情况下业务 service 这样写基本没啥卵用 , 多态你得有多个实现类的场景撒 .

    比如你想写个缓存小组件,可以基于 memory/redis ,这时你定义一个接口就有用了...
    caizs320525
        64
    caizs320525  
       2021-01-04 17:08:43 +08:00
    现在的感受是,为了写注释能够更清晰,与业务代码分离
    qwerthhusn
        65
    qwerthhusn  
       2021-01-04 18:04:17 +08:00
    增加代码量,代码统计的时候,你写的代码不比别人少
    vanishxiaoma
        66
    vanishxiaoma  
       2021-01-05 08:56:33 +08:00
    我之前提问过一个相似的问题
    https://www.v2ex.com/t/592647#reply36
    可以参考下大佬的回答
    hhyygg
        67
    hhyygg  
       2021-01-05 10:29:46 +08:00 via Android
    @asanelder #47 这个例子不错,学习了
    KarmaWu
        68
    KarmaWu  
       2021-01-05 15:55:19 +08:00
    我司的项目,不同版本会根据策略模式跳转到不同的 Impl 类,高版本的继承低版本的 Impl
    iXInbo
        69
    iXInbo  
       2023-03-17 21:25:55 +08:00
    如果你有 impl 有很多 private 方法,接口里只暴露给外面的就行了,如果这个项目会提供给别人使用,用接口就好了,不然照你的想法,接口文件就没有存在的必要,所有的都是 class 对象就可以了
    jefferyJQ
        70
    jefferyJQ  
       2023-07-26 17:20:28 +08:00
    cglib 代理类会出现一些问题。

    A 类注入了 B 类,且 B 属性的修饰符为 public 。
    此时 C 类注入了 A 类,然后直接调用 A.b 时,此时 A.b 一定会为 null 。这是由于 cglib 代理机制决定的。
    为了避免团队中遇到这种情况,通过接口的方式屏蔽掉通过属性调用方法
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3839 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 50ms · UTC 05:10 · PVG 13:10 · LAX 21:10 · JFK 00:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.