前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >接口隔离原则带来的复杂性

接口隔离原则带来的复杂性

作者头像
码农戏码
发布2023-03-06 14:42:44
3050
发布2023-03-06 14:42:44
举报
文章被收录于专栏:DDD

ISP

什么是ISP,之前总结过,详细内容可回顾《SOLID之ISP》[1]

简单总结:多餐少吃,不要大接口,使用职责单一的小接口。

just so easy!

不就是把大接口拆成小接口嘛!

然而,最近在review之前的代码时,发现了点问题。

简单介绍下背景业务知识,项目是处理发票业务,在公司报销过的人都了解,我们团建、出差,公办支出都会让商家开具一张发票,作为报销凭证。

那么一张发票在被上传到报销软件,行为分为几个部分:

1、上传识别:从一张发票图片,被OCR,识别出一份结构化数据

2、修改:修改发票信息,包括删除、编辑识别出的发票内容,甚至手工填写一张发票信息

3、验真:会调用国税接口,验证一下发票的真伪

4、查询:查看发票详情

每一部分都会有几个方法,为了避免胖接口,自然会拆分成职责更专注的小接口

使用IDEA绘制出类结构:

InvoiceVerifyService:表示发票验真职责

InvoiceDiscernService:表示发票识别职责

InoviceService:表示发票查询、编辑等职责

思路清晰,结构中正。

可在项目中却出现了一段这样的代码:

代码语言:javascript
复制
if(invoiceService instanceof InvoiceVerifyService){
    InvoiceVerifyService verifyService = (InvoiceVerifyService)invoiceService;
}

看着instanceof关键字,就倍感别扭。要么抽象得不对,要么结构不对。

如果没有拆分成三个接口,肯定不需要这样的判断。

所以还得重新审视一下ISP。

ISP:接口隔离原则,里面两个关键词:“接口”和“隔离”;“隔离”相对比较简单,从单一职责角度,把职责不相关的行为拆分开。而“接口”则需要重新审视一下。

接口

其实每个人对接口的理解是不一样的,从分类上讲,大该两类,一是狭义:常被理解为像Java语言中的interface,或者模块内部的使用;二是广义:系统间交互契约。

Martin Fowler给了两种类型接口:RoleInterface和HeaderInterface

A role interface is defined by looking at a specific interaction between suppliers and consumers. A supplier component will usually implement several role interfaces, one for each of these patterns of interaction. This contrasts to a HeaderInterface, where the supplier will only have a single interface

大致也是这个意思。

广义

主要是系统间交互的契约。类似于一个系统的facade对外提供的交互方式。

就算你不设计接口,并不代表没有接口。不局限于语言层面的interface,而是一种契约。

最重要的原则是KISS原则,最小依赖原则或者叫最少知识原则,让人望文知义。

追求简单自然,符合惯例。

比如一个微服务用户系统提供了一组跟用户相关的 API 给其他系统使用,比如:注册、登录、获取用户信息等。

还包含了后台管理系统需要的删除用户功能,如果接口不作隔离,具体代码如下所示:

代码语言:javascript
复制

public interface UserService {
  boolean register(String cellphone, String password);
  boolean login(String cellphone, String password);
  UserInfo getUserInfoById(long id);
  UserInfo getUserInfoByCellphone(String cellphone);

  boolean deleteUserByCellphone(String cellphone);
  boolean deleteUserById(long id);
}

然而,删除操作只限于管理后台操作,对其他系统来讲,不仅是多余功能,还有危险性。

通过使用接口隔离原则,我们可以将一个实现类的不同方法包装在不同的接口中对外暴露。应用程序只需要依赖它们需要的方法,而不会看到不需要的方法。

代码语言:javascript
复制

public interface UserService {
  boolean register(String cellphone, String password);
  boolean login(String cellphone, String password);
  UserInfo getUserInfoById(long id);
  UserInfo getUserInfoByCellphone(String cellphone);
}

public interface RestrictedUserService {
  boolean deleteUserByCellphone(String cellphone);
  boolean deleteUserById(long id);
}

public class UserServiceImpl implements UserService, RestrictedUserService {
  // ...省略实现代码...
}

狭义

狭义常被理解为像Java语言中的interface,或者模块内部的使用。

单纯某一个接口,与单一职责一样,希望接口的职责单一,不要是胖接口、万能接口。

模块内部设计时,不管是模块调用模块,还是模块调用第三方组件。

我们一般有两种选择:

一、是直接依赖所基于的模块或组件;

二、是将所依赖的组件所有方法抽象成一个接口,让模块依赖于接口而不是实现。

其实这在之前对面向对象反思的文章中,提到过,打开我们90%的项目,所有的service都有对应的service接口和serivceImpl实现,整齐划一,美其名曰,面向接口编程。

然而,到项目生命周期结束,一个service都不会有两种实现。

所以,建议还是直接依赖实现,不要去抽象。如无必要,勿增实体

如果我们大量抽象依赖的组件,意味着我们系统的可配置性更好,但复杂性也激增。

什么时候考虑抽象呢?

1、在需要提供多种选择的时候。比如经典的Logger组件。把选择权交给使用方。

这儿也有过度设计的情况,比如数据库访问,抽象对数据库的依赖,以便在MySQL和MongoDB之间切换,在绝大数情况下,这种属于过度设计。毕竟切换数据库本身就是件小概率事件。

2、需要解除一个庞大的外部依赖。有时我们并不是需要多个选择,而是某个依赖过重。我们在测试或其它场景会选择mock一个,以便降低测试系统的依赖

3、在依赖的外部系统为可选组件时。这个时候可以实现一个mock的组件,并在系统初始化时,设置为mock组件。这样的好处,除非用户关心,否则就当不存在一样,降低学习门槛。



回到文章篇头的问题,每个接口职责都是单一明确的,为什么还需要instanceof来判别类型?其实是更上层混合使用了

类似于:

代码语言:javascript
复制
Map<String,InvoiceService> invoiceServiceMap = SpringUtils.getBeans();

客户端使用时,得拆分开:

代码语言:javascript
复制
Map<String,InvoiceVerifyService> invoiceServiceMap = SpringUtils.getBeans();
Map<String,InvoiceService> invoiceServiceMap = SpringUtils.getBeans();
Map<String,InvoiceDiscernService> discernServiceMap = SpringUtils.getBeans();

当需要具体能力时,可以从对应的集合中获取对应的Service。而不是通过instanceof去判断。通过空间的换取逻辑的明确性。

VS SRP

接口隔离原则跟单一职责原则有点类似,不过稍微还是有点区别。

单一职责原则针对的是模块、类、接口的设计。

而接口隔离原则相对于单一职责原则,一方面它更侧重于接口的设计,另一方面它的思考的角度不同。

它提供了一种判断接口是否职责单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

总结

表达原则的文字都很简单,但在实践时又会陷入落地时的困境。

这些原则的背后,也体现了架构之道,虚实结合之道。从实悟虚,从虚就实。

References

[1] 《SOLID之ISP》: http://www.zhuxingsheng.com/blog/solid-isp.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ISP
  • 接口
  • 广义
  • 狭义
  • VS SRP
  • 总结
    • References
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档