如何设计Service层


声明:本文转载自https://my.oschina.net/u/3070387/blog/1559856,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

在初学Spring时曾被Service绕晕,为何MVC模式下会多出一个Service层?设计Service时候为何需要先写一个接口,然后再去实现?Service之间是否可以相互调用?而这篇文章就是当初疑问的解决,也是对MVC模式深入理解。

Service从何而来

Spring MVC,是一个MVC框架,提到MVC,大家都不陌生,简单说一下,M为模型层,处理数据逻辑,V为视图层,负责展示,而C为控制层,负责M与V的交互。
在我大学的课堂上,也有学习到MVC模式,最简单的javabean+jsp+serlvet构成MVC模式,而老师千叮万嘱我们,Servlet中的逻辑一定要少,逻辑部分应该放在javabean,即模型层处理,但是模型层还肩负着存储数据的任务(其实这个就是Model所负责的数据逻辑,要实现数据逻辑,要有数据,要有处理),而Service就是将处理,也可以说业务,抽离出来,可以说是服务层,也可以说是业务层。MVC的划分概念,更加细致。

为什么设计Service时候需要先写接口

业务层接口

public interface IUserService {     boolean login(User user); } 

业务层实现类

@Service("userService") public class UserServiceImpl implements IUserService {     public boolean login(User user) {         //登录判断逻辑     } } 

在初学时,我写Service时大概就是这种感觉,但并没有想过为什么要这样做。要想理解为什么要这样写Service,首先要理解,接口的作用是什么。

接口的作用

接口主要用于描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。——《Java核心技术卷一》

这句话所描述的就是接口所带来的扩展性。而用我的话去概括,就是「统一的接入」与「统一的暴露」,举一个例子来解释,数据源的接口DataSource
统一的接入是对于数据源的开发者来说,要实现数据源,就需要去实现DataSource接口,然后实现其方法。对于DBCP、c3p0、Druid数据源来说,不同的开发者,实现同一套东西,这就是统一的接入。
统一的暴露是对于数据源的使用者来说。对于数据源的使用者,使用时所要关注的是如何获取数据库连接的动作,即getConnection方法,至于这个动作的具体实现,不需要知道也不关心。
统一的接入与统一暴露,将实现与使用分离开来,也就是接口所带来的好处。
开头所写的IUserService的例子,所做的就是将登陆的实现与使用分离,举一个具体的例子

@Service("databaseUserService") public class DatabaseUserServiceImpl implements IUserService {     public boolean login(User user) {         //从数据库查询用户信息,判断是否账号/密码是否一致     } } 
@Service("oauthUserService") public class OauthUserServiceImpl implements IUserService {     public boolean login(User user) {         //使用Oauth登陆,如微信登陆     } } 

对于登陆的实现,就可以分为从数据库查询和使用第三方授权登陆两种方式分别实现,对于使用者来说,所关注的就是login这一动作,整个登陆操作变得十分灵活,根据不同的场景使用不同的方式登陆。
看起来这种实现Service的方式非常不错,以后写Service都这样写吧。
想一下之前所写的代码,通常都是一个Service接口对应一个实现,基本上是没有第二个实现的必要,也就是所获取到接口的好处非常少,但是却付出了实现接口的代价,即对动作的抽象,这是一个成本,例如你想为Service添加功能,你需要先将这个功能抽象出来,其中的参数,返回值都要定好,如果需求改动,改动这个功能的功夫是不少的。这样的话,为何不去除这个借口,直接实现Service类呢?

去除接口,让Service成为业务

将接口去除,其实是将Service层,从服务改变为业务,即专注于业务,Service中的都是业务逻辑。有何区别呢?简单点来说,不用考虑向外提供服务,只为自己系统的业务功能提供服务。
对于一个中小项目来说,脱掉Service层接口的枷锁,实现起业务来,十分流畅,不再用抽象,只关注于业务即可。
在学校时,一位师兄经常提醒我,知其然知其所以然,只有清楚为何写Service时需要先写接口,才能明白为何要去除接口。

Service的互相调用

Service间是否可以相互调用,这个很久当时是在困扰了我很久,现在可以明确的说,Service层不应该相互调用,特别是我上面提到的去除接口,让Service完全成为一个业务体的设计下,更加不应该相互调用Service。
你会问,一个Service的实现的业务,确实可以在另一个Service中用到,难道要重新写?要清楚,这个情况是出于Service间有通用的逻辑,而不是通用的业务,每个Service对应一个业务,业务之间应该有明确的分界,不然会出现业务间的耦合,这是设计的不合理。
既然是Service间的逻辑通用,我们大可创建一个ServiceHelper类,里面放的,就是Service间的通用逻辑,各自调用这个逻辑即可。当然如果系统庞大起来,这种情况会经常出现,这时再抽象一层,可以叫provider层,提供操作逻辑,例如发短信功能,provider里放的是如何发短信的操作逻辑,而Service层放的是什么时候发短信,发多少,发给谁的业务逻辑。

业务逻辑只由Service自己知道

上面所写的UserService例子

@Service("userService") public class UserService {     public boolean login(User user) {         //登录判断逻辑     } } 

这样写,我觉得还不是最好。这个Service最终会被一个Controller所调用,当Controller调用该方法时,还需要判断返回值是true还是false,再返回结果,其逻辑“泄露”了,因为Controller要了解业务是true是登陆成功,false是登陆失败。如下

@Controller @RequestMapping("/user") public class UserController extends BaseController {      @Autowired     private UserService userService;      @RequestMapping(value = "/login", method = RequestMethod.POST)     public     @ResponseBody ResponseResult login(@RequestBody User form) {         if(userService.login(form)) {             return ResponseResult.getOk("登陆成功");         } else {             return ResponseResult.getError("登陆失败");         }     } } 

Controller不应该知道怎么才算登陆成功,登陆失败,它应该只需要知道调用那个业务。
我们可以这样写

@Service("userService") public class UserService {     /** * @return ResponseResult响应结果.为{"status":"状态码","msg":"响应信息","data":"响应数据"} */     public ResponseResult login(User entity) {         //用MD5加密密码         entity.setPassword(MD5Utils.getMD5(entity.getPassword()));         //用账号和加密后的密码为查询条件,查询数据库中是否有对应的数据         Optional<User> optional = userRepository.selectOne(entity);         return optional.map(ResponseResult::getOk).orElseGet(() -> ResponseResult.getError("账号/密码错误"));     } } 
@Controller @RequestMapping("/user") public class UserController extends BaseController {      @Autowired     private UserService userService;      @RequestMapping(value = "/login", method = RequestMethod.POST)     public     @ResponseBody ResponseResult login(@RequestBody User form) {         return userService.login(form);     } }

这样写,不就向Controller屏蔽了登陆是否成功的判断逻辑。
平时我们一直说,Controller一定要尽量少的逻辑,其实反过来说,是指Service的逻辑应该高内聚,这样Controller如Service的耦合自然就是最低,Controller真真正正的坐到,不用理会Service的实现,只需要调用即可。
当然这个登陆的例子可能有点小题大做,但是如果这个业务是查找一批用户,如果查找不到需要返回错误码呢?难道要在Controller层去判断返回的集合是否为空,然后构造返回信息吗?这样其实是将Service的部分逻辑放在了Controller去完成。

本文发表于2017年11月03日 06:34
(c)注:本文转载自https://my.oschina.net/u/3070387/blog/1559856,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2130 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1