第一部分 設計模式概述
企業開發中,除了功能性需求(Functional Requirement)之外,非功能性需求(Non Functional Requirement)也同樣重要。在非功能性需求中描述了項目的諸多系統性質量(Systemic Quality)。這些質量包括了靈活性、可維護性、可擴展性。大師編寫的代碼,質量往往比較高。很多新手在編寫項目的同時,bug定位難、經常返工,歸根結底在于:不注重設計。
如果新手與大師之間必然要有一道分水嶺,那么這一道分水嶺,唯系統設計
這一個能力,就足以說明問題了。然而 系統設計
能力,又應該怎樣去提升呢?實際上不論任何行業,學習技能的訣竅不外乎三點: 守、破、離,也就是: 模仿、突破、開拓。那么首先要做的,就是模仿。
所謂設計,我們要模仿的,是前輩們在系統設計時,為我們總結好的經驗;以及前輩們在系統設計時,遇到問題后,如何利用模式解決這些問題的。也就是——設計模式。
什么叫設計呢?設計的英文——Design。
在旅游之前,需要規劃好出行路線;需要準備好常用藥;需要打包好換洗衣服、洗漱化妝用品。
開始一天的工作之前,列好今日工作任務清單
演講之前,寫好草稿
...
以上的這些,都叫做設計。
那么程序員們所說的設計,又是什么呢?這里的設計,即OOD(Object-Oriented Design)——面向對象設計。也就是在實際地編寫代碼之前,先要規劃好思路,不管你的思路到底有沒有落到圖紙上,哪怕只是打一個腹稿,這也叫做設計!
注: OOD與之對應的又有OOA(Object-Oriented Analysis)——面向對象分析,以及OOP(Object-Oriented Programming)——面向對象編程。它們之間的順序是: 先分析;再設計;最后編碼
什么又叫模式呢?模式的英文——Pattern
- 當籃球運動員帶著籃球,到達了三分線的位置時,為了能夠讓籃球投到籃框里,手指、手臂將使用多大的力量——這已經形成了他自己的模式
- 廚師在在做菜時,要先煮米飯,再炒菜;要先把菜、肉、各類食材調料準備好,再熱鍋放油
- ...
這些都是生活以及工作、學習中,你去解決某些問題時,所形成的模式。
那么如何一句話概括:什么是模式?模式就是: 通用問題的通用解決方案。
RESTful接口,想必大部分的Java程序員都接觸過。當我們使用大篇幅的if - else
解決問題時、當我們大段大段地復制粘貼我們的代碼時,我們有沒有思考過,是否可以更優雅地解決這些問題呢?
本篇文章,將籍由常見的RESTful接口,為大家講解三個設計模式:
- 裝飾器模式
- 策略模式
- 簡單工廠模式
第二部分 搭建開發環境
本篇使用的項目,將采用Maven + Spring Boot來搭建,其中maven配置文件及Spring Boot啟動類如下
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.futureweaver</groupId>
<artifactId>sample</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- Application.java
package com.futureweaver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第三部分 裝飾器模式
1 version 0.1
需求
比如一個系統里有三個接口:
-
/user/login
登錄
-
/user/signout
注冊
-
/user/forget
忘記密碼
對客戶端調用后的響應信息來說,不論調用的是哪個方法,系統中都會有兩個通用的字段:
-
code
響應碼。
方法執行正確,即為0
方法執行如果出錯,即非0值。
客戶端在接收到響應碼之后,如果是0,走常規流程;如果非0,走異常流程。
-
msg
響應信息
每個響應碼對應一條響應信息。一般情況下是英文。
客戶端可以利用國際化技術,將英文轉換成中文
響應信息便于客戶端調試
設計
基于以上的信息,大部分程序員們的設計將會是這樣的:
- 在每個ResponseBody里面,都寫上兩條屬性:
code
、msg
- 在每個接口方法里面,手動填充code,及msg
類圖
代碼
- UserController.java
package com.futureweaver.controller;
import com.futureweaver.response.ForgetPasswordResp;
import com.futureweaver.response.LoginResp;
import com.futureweaver.response.SignoutResp;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/user")
public class UserController {
// 忘記密碼接口
@RequestMapping(path = "/forget_password", method = RequestMethod.POST)
@ResponseBody
public ForgetPasswordResp forgetPassword() {
ForgetPasswordResp result = new ForgetPasswordResp();
// 業務處理
// if (用戶名不存在) {
// result.code = 2230;
// result.msg = "user not found";
// return result;
// } else if (...) {
// ...
// return result;
// }
// ...
result.code = 0;
result.msg = "success";
return result;
}
// 登錄接口
@RequestMapping(path = "/login", method = RequestMethod.POST)
@ResponseBody
public LoginResp login() {
LoginResp result = new LoginResp();
// 業務處理
// if (用戶名不存在) {
// result.code = 2230;
// result.msg = "user not found";
// return result;
// } else if (...) {
// ...
// return result;
// }
// ...
result.code = 0;
result.msg = "success";
return result;
}
// 注冊接口
@RequestMapping(path = "/signout", method = RequestMethod.POST)
@ResponseBody
public SignoutResp signout() {
SignoutResp result = new SignoutResp();
// 業務處理
// if (用戶名不存在) {
// result.code = 2230;
// result.msg = "user not found";
// return result;
// } else if (...) {
// ...
// return result;
// }
// ...
result.code = 0;
result.msg = "success";
return result;
}
}
- ForgetPasswordResp.java
package com.futureweaver.response;
// 除了msg和code之外,無需向客戶端響應任何信息
// 客戶端如果接收到了code=0,則默認為接收到了重置密碼的郵件
public class ForgetPasswordResp {
public String msg;
public int code;
public ForgetPasswordResp() {
}
public ForgetPasswordResp(String msg, int code) {
this.msg = msg;
this.code = code;
}
@Override
public String toString() {
return "ForgetPasswordResp{" +
"msg='" + msg + '\'' +
", code=" + code +
'}';
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- LoginResp.java
package com.futureweaver.response;
public class LoginResp {
public String token;
public String msg;
public int code;
public LoginResp() {
}
public LoginResp(String token, String msg, int code) {
this.token = token;
this.msg = msg;
this.code = code;
}
@Override
public String toString() {
return "LoginResp{" +
"token='" + token + '\'' +
", msg='" + msg + '\'' +
", code=" + code +
'}';
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- SignoutResp.java
package com.futureweaver.response;
public class SignoutResp {
public String token;
public String msg;
public int code;
public SignoutResp() {
}
public SignoutResp(String token, String msg, int code) {
this.token = token;
this.msg = msg;
this.code = code;
}
@Override
public String toString() {
return "SignoutResp{" +
"token='" + token + '\'' +
", msg='" + msg + '\'' +
", code=" + code +
'}';
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
問題
接口約束如果想換成:
- code為200是正常值
- msg所對應的信息換成了"operate success"
所有的接口函數都得重新改一遍
2 version 0.2
解決方案
- 當接口函數沒有任何的異常拋出時,即默認為成功。利用
Spring切面
,對所有的正常返回施加干預 - 將
code
及msg
抽離出來,封裝成ResponseCommon
。 - 所有的
Response
,復用ResponseCommon
。在組合復用與繼承復用兩個選項中,選擇組合復用 - 使用了組合復用后,會破壞數據結構,利用
jackson
中的JsonUnwrapped
緩解
類圖
代碼
- UserController.java
package com.futureweaver.controller;
import com.futureweaver.response.ForgetPasswordResp;
import com.futureweaver.response.LoginResp;
import com.futureweaver.response.SignoutResp;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/user")
public class UserController {
@RequestMapping(path = "/forget_password", method = RequestMethod.POST)
@ResponseBody
public ForgetPasswordResp forgetPassword() {
ForgetPasswordResp result = new ForgetPasswordResp();
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
return result;
}
@RequestMapping(path = "/login", method = RequestMethod.POST)
@ResponseBody
public LoginResp login() {
LoginResp result = new LoginResp();
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
return result;
}
@RequestMapping(path = "/signout", method = RequestMethod.POST)
@ResponseBody
public SignoutResp signout() {
SignoutResp result = new SignoutResp();
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
return result;
}
}
- ForgetPasswordResp.java
package com.futureweaver.response;
// 除了msg和code之外,無需向客戶端響應任何信息
// 客戶端如果接收到了code=0,則默認為接收到了重置密碼的郵件
public class ForgetPasswordResp {
public ResponseCommon responseCommon;
public ForgetPasswordResp() {
}
public ForgetPasswordResp(ResponseCommon responseCommon) {
this.responseCommon = responseCommon;
}
@Override
public String toString() {
return "ForgetPasswordResp{" +
"responseCommon=" + responseCommon +
'}';
}
public ResponseCommon getResponseCommon() {
return responseCommon;
}
public void setResponseCommon(ResponseCommon responseCommon) {
this.responseCommon = responseCommon;
}
}
- LoginResp.java
package com.futureweaver.response;
public class LoginResp {
public String token;
public ResponseCommon responseCommon;
public LoginResp() {
}
public LoginResp(String token, ResponseCommon responseCommon) {
this.token = token;
this.responseCommon = responseCommon;
}
@Override
public String toString() {
return "LoginResp{" +
"token='" + token + '\'' +
", responseCommon=" + responseCommon +
'}';
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public ResponseCommon getResponseCommon() {
return responseCommon;
}
public void setResponseCommon(ResponseCommon responseCommon) {
this.responseCommon = responseCommon;
}
}
- SignoutResp.java
package com.futureweaver.response;
public class SignoutResp {
public String token;
public ResponseCommon respnoseCommon;
public SignoutResp() {
}
public SignoutResp(String token, ResponseCommon respnoseCommon) {
this.token = token;
this.respnoseCommon = respnoseCommon;
}
@Override
public String toString() {
return "SignoutResp{" +
"token='" + token + '\'' +
", respnoseCommon=" + respnoseCommon +
'}';
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public ResponseCommon getRespnoseCommon() {
return respnoseCommon;
}
public void setRespnoseCommon(ResponseCommon respnoseCommon) {
this.respnoseCommon = respnoseCommon;
}
}
- ResponseCommon.java
package com.futureweaver.response;
public class ResponseCommon {
private String msg;
private int code;
public ResponseCommon() {
}
public ResponseCommon(String msg, int code) {
this.msg = msg;
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- ResponseCommoneAssembler.java
package com.futureweaver.config;
import java.lang.reflect.Field;
import com.futureweaver.response.ResponseCommon;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseCommonAssembler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (returnType.getMethod().getReturnType().equals(void.class)) {
return true;
}
Field[] fields = returnType.getMethod().getReturnType().getDeclaredFields();
for (Field fieldItem : fields) {
if (fieldItem.getType().equals(ResponseCommon.class)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
Field[] fields = returnType.getMethod().getReturnType().getDeclaredFields();
for (Field fieldItem : fields) {
if (fieldItem.getType().equals(ResponseCommon.class)) {
fieldItem.setAccessible(true);
ResponseCommon responseCommon = (ResponseCommon) ReflectionUtils.getField(fieldItem, body);
if (responseCommon == null) {
responseCommon = new ResponseCommon();
ReflectionUtils.setField(fieldItem, body, responseCommon);
}
if (responseCommon.getCode() == 0) {
responseCommon.setMsg("success");
}
}
}
return body;
}
}
問題
回過頭再查看一下ForgetPasswordResp.java
,除了一個responseCommon
屬性之外,再無其它;對于UserController.java
中的forgetPassword
接口方法,也無需處理其他的業務。非要給它安排一個ForgetPasswordResp
響應體,是不是多此一舉?
3 version 0.3
按正常的套路,把方法的返回值改成void就可以了。但是在ResponseCommonAssembler
中,我們響應體所有的屬性,找到ResponseCommon
字段,再將0
和success
填充進去。把響應體改成了void
之后,就沒有ResponseCommon
字段了,怎么玩?
解決方案
在ResponseCommonAssembler
中,添加"函數返回類型是否為void
"的條件判斷,直接創建一個新的ResponseCommon
對象,將code
填充為0,將msg
填充為"success",并將這個對象直接返回。
代碼
- UserController.java
package com.futureweaver.controller;
import com.futureweaver.response.ForgetPasswordResp;
import com.futureweaver.response.LoginResp;
import com.futureweaver.response.SignoutResp;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/user")
public class UserController {
@RequestMapping(path = "/forget_password", method = RequestMethod.POST)
@ResponseBody
public void forgetPassword() {
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
}
@RequestMapping(path = "/login", method = RequestMethod.POST)
@ResponseBody
public LoginResp login() {
LoginResp result = new LoginResp();
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
return result;
}
@RequestMapping(path = "/signout", method = RequestMethod.POST)
@ResponseBody
public SignoutResp signout() {
SignoutResp result = new SignoutResp();
// 業務處理
// 將以下代碼刪除
// result.code = 0;
// result.msg = "success";
return result;
}
}
- ResponseCommonAssembler.java
package com.futureweaver.config;
import java.lang.reflect.Field;
import com.futureweaver.response.ResponseCommon;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseCommonAssembler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (returnType.getMethod().getReturnType().equals(void.class)) {
return true;
}
Field[] fields = returnType.getMethod().getReturnType().getDeclaredFields();
for (Field fieldItem : fields) {
if (fieldItem.getType().equals(ResponseCommon.class)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (returnType.getMethod().getReturnType().equals(void.class)) {
ResponseCommon responseCommon = new ResponseCommon();
responseCommon.setMsg("success");
return responseCommon;
}
Field[] fields = returnType.getMethod().getReturnType().getDeclaredFields();
for (Field fieldItem : fields) {
if (fieldItem.getType().equals(ResponseCommon.class)) {
fieldItem.setAccessible(true);
ResponseCommon responseCommon = (ResponseCommon) ReflectionUtils.getField(fieldItem, body);
if (responseCommon == null) {
responseCommon = new ResponseCommon();
ReflectionUtils.setField(fieldItem, body, responseCommon);
}
if (responseCommon.getCode() == 0) {
responseCommon.setMsg("success");
}
}
}
return body;
}
}
4 version 1.0
現在的問題是,我們并不能保證,客戶端的每一次請求,都會得到一個成功的響應。那么當用戶請求出問題時,又該如何解決呢?
解決方案
異常,并不是系統或者框架才能夠使用的。當用戶請求后,
參數填寫錯誤
、用戶名不存在
等錯誤亦可使用異常機制處理。依然利用
Spring切面
,對所有的異常返回施加干預
代碼
- GlobalExceptionHandler.java
package com.futureweaver.config;
import com.futureweaver.exception.ExceptionCommon;
import com.futureweaver.response.ResponseCommon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 異常攔截器
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 自定義異常
*
* @see <code>com.futureweaver.exception.*</code>
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = ExceptionCommon.class)
@ResponseBody
public ResponseCommon commonExceptionHandler(HttpServletRequest req, ExceptionCommon e) {
return e.getResponseCommon();
}
}
- ExceptionCommon.java
package com.futureweaver.exception;
import com.futureweaver.response.ResponseCommon;
public class ExceptionCommon extends Exception {
private static final long serialVersionUID = 3867913775058901502L;
private ResponseCommon responseCommon;
public ExceptionCommon() {
super();
}
public ExceptionCommon(ResponseCommon responseCommon) {
super();
this.responseCommon = responseCommon;
}
public ExceptionCommon(Integer respCode, String respMsg) {
super();
responseCommon = new ResponseCommon();
responseCommon.setCode(respCode);
responseCommon.setMsg(respMsg);
}
public ResponseCommon getResponseCommon() {
return responseCommon;
}
public void setResponseCommon(ResponseCommon responseCommon) {
this.responseCommon = responseCommon;
}
}
- UserController.java
package com.futureweaver.controller;
import com.futureweaver.response.ForgetPasswordResp;
import com.futureweaver.response.LoginResp;
import com.futureweaver.response.SignoutResp;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/user")
public class UserController {
@RequestMapping(path = "/forget_password", method = RequestMethod.POST)
@ResponseBody
public void forgetPassword() throws ExceptionCommon {
// 異常校驗,如果校驗失敗,則拋出異常
// 業務處理
}
@RequestMapping(path = "/login", method = RequestMethod.POST)
@ResponseBody
public LoginResp login() throws ExceptionCommon {
LoginResp result = new LoginResp();
// 異常校驗,如果校驗失敗,則拋出異常
// 業務處理
return result;
}
@RequestMapping(path = "/signout", method = RequestMethod.POST)
@ResponseBody
public SignoutResp signout() throws ExceptionCommon {
SignoutResp result = new SignoutResp();
// 異常校驗,如果校驗失敗,則拋出異常
// 業務處理
return result;
}
}
5 裝飾器模式
科普一下裝飾器模式吧
動態地給一個對象添加一些額外的職責?!对O計模式 - 可復用面向對象軟件的基礎》
第四部分 策略模式 & 簡單工廠
1 version 0.1
需求
- 制作一個企業通訊錄,用戶可以對通訊錄信息進行增刪改查
設計
想必大部分的程序員在設計功能的時候,直接把增刪改查做成四個接口方法了。設計得再稍微復雜一點,可以批量增、批量刪、批量改。
查詢功能,用戶提交的請求數據,只需要帶一個token過來就可以了。所以暫只考慮增、刪、改功能
類圖
代碼
- ManageController.java
package com.futureweaver.controller;
import com.futureweaver.exception.ExceptionCommon;
import com.futureweaver.request.Contact;
import com.futureweaver.request.ContactModifyRequest;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/manage")
public class ManageController {
@Autowired
private UserMapper userMapper;
@Autowired
private ContactMapper contactMapper;
@ResponseBody
@RequestMapping(path = "/contact_add", method = RequestMethod.POST)
public void contactAdd(@RequestBody ContactModifyRequest model) throws ExceptionCommon {
User user = userMapper.queryByToken(model.getUserToken());
// 判斷用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserNotNull(user);
// 判斷用戶是管理員,若不是管理員,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserIsManager(user);
for (Contact contact : model.getContacts()) {
// 判斷被添加的用戶不存在,若已存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactIsNull(contact);
contactMapper.addContact(contact);
}
}
@ResponseBody
@RequestMapping(path = "/contact_del", method = RequestMethod.POST)
public void contactDel(@RequestBody ContactModifyRequest model) throws ExceptionCommon {
User user = userMapper.queryByToken(model.getUserToken());
// 判斷用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserNotNull(user);
// 判斷用戶是管理員,若不是管理員,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserIsManager(user);
for (Contact contact : model.getContacts()) {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.delContact(contact);
}
}
@ResponseBody
@RequestMapping(path = "/contact_mod", method = RequestMethod.POST)
public void contactMod(@RequestBody ContactModifyRequest model) throws ExceptionCommon {
User user = userMapper.queryByToken(model.getUserToken());
// 判斷用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserNotNull(user);
// 判斷用戶是管理員,若不是管理員,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserIsManager(user);
for (Contact contact : model.getContacts()) {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.modContact(contact);
}
}
}
- Contact.java
package com.futureweaver.request;
public class Contact {
private String contactId;
private String mobile;
private String email;
private String avatar;
private String birthday;
private String birthplace;
public Contact() {
}
@Override
public String toString() {
return "Contact{" +
"userId='" + contactId + '\'' +
", mobile='" + mobile + '\'' +
", email='" + email + '\'' +
", avatar='" + avatar + '\'' +
", birthday='" + birthday + '\'' +
", birthplace='" + birthplace + '\'' +
'}';
}
public String getContactId() {
return contactId;
}
public void setContactId(String contactId) {
this.contactId = contactId;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getBirthplace() {
return birthplace;
}
public void setBirthplace(String birthplace) {
this.birthplace = birthplace;
}
}
- ContactModifyRequest.java
package com.futureweaver.request;
import java.util.List;
public class ContactModifyRequest {
private String token;
private List<Contact> contacts;
public ContactModifyRequest() {
}
public ContactModifyRequest(String token, List<Contact> contacts) {
this.token = token;
this.contacts = contacts;
}
@Override
public String toString() {
return "ContactModifyRequest{" +
"token='" + token + '\'' +
", contacts=" + contacts +
'}';
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public List<Contact> getContacts() {
return contacts;
}
public void setContacts(List<Contact> contacts) {
this.contacts = contacts;
}
}
2 version 0.2
問題
- 對于客戶端,通訊錄信息的增刪改,完全可以調用同一個接口。目前讓客戶端調用了多個接口
- 對于服務端,增刪改接口的大部分業務代碼是重合的。完全可以合并到一起
解決方案
- 在Contact類中,添加一個枚舉值,由客戶端決定:此條Contact是添加、還是修改、或者刪除
- 在ManageController中,合并增、刪、改的接口方法為一個
- 在ManageController中,遍歷Contact,判斷枚舉是什么,進入增刪改流程
設計
代碼
- Contact.java
package com.futureweaver.request;
public class Contact {
private String contactId;
private String mobile;
private String email;
private String avatar;
private String birthday;
private String birthplace;
private String operationType;
public Contact() {
}
@Override
public String toString() {
return "Contact{" +
"userId='" + contactId + '\'' +
", mobile='" + mobile + '\'' +
", email='" + email + '\'' +
", avatar='" + avatar + '\'' +
", birthday='" + birthday + '\'' +
", birthplace='" + birthplace + '\'' +
", operationType='" + operationType + '\'' +
'}';
}
public String getContactId() {
return contactId;
}
public void setContactId(String contactId) {
this.contactId = contactId;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public String getBirthplace() {
return birthplace;
}
public void setBirthplace(String birthplace) {
this.birthplace = birthplace;
}
public String getOperationType() {
return operationType;
}
public void setOperationType(String operationType) {
this.operationType = operationType;
}
}
- ManageController.java
package com.futureweaver.controller;
import com.futureweaver.exception.ExceptionCommon;
import com.futureweaver.request.Contact;
import com.futureweaver.request.ContactModifyRequest;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/manage")
public class ManageController {
@Autowired
private UserMapper userMapper;
@Autowired
private ContactMapper contactMapper;
@ResponseBody
@RequestMapping(path = "/contact_mod", method = RequestMethod.POST)
public void contactMod(@RequestBody ContactModifyRequest model) throws ExceptionCommon {
User user = userMapper.queryByToken(model.getUserToken());
// 判斷用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserNotNull(user);
// 判斷用戶是管理員,若不是管理員,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserIsManager(user);
for (Contact contact : model.getContacts()) {
if (contact.getOperationType().equals("add")) {
// 判斷被添加的用戶不存在,若已存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactIsNull(contact);
contactMapper.addContact(contact);
} else if (contact.getOperationType().equals("del")) {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.delContact(contact);
} else /* if (contact.getOperationType().equals("mod")) */ {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.modContact(contact);
}
}
}
}
3 version 0.3
問題
-
if - else
搞得這么多,菜鳥們總是很愿意這么搞,經常會一個方法寫得很長??删S護性、可讀性都會很差。
解決方案
利用
策略模式
,將每一個條件分支封裝成策略。利用
簡單工廠模式
,由所有策略類的父類,根據操作類別,創建具體策略。
類圖
代碼
- ManageController.java
package com.futureweaver.controller;
import com.futureweaver.controller.strategy.AbstractContactModifyStrategy;
import com.futureweaver.exception.ExceptionCommon;
import com.futureweaver.request.Contact;
import com.futureweaver.request.ContactModifyRequest;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path = "/manage")
public class ManageController {
@Autowired
private UserMapper userMapper;
@Autowired
private ContactMapper contactMapper;
@ResponseBody
@RequestMapping(path = "/contact_mod", method = RequestMethod.POST)
public void contactMod(@RequestBody ContactModifyRequest model) throws ExceptionCommon {
User user = userMapper.queryByToken(model.getUserToken());
// 判斷用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserNotNull(user);
// 判斷用戶是管理員,若不是管理員,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateUserIsManager(user);
for (Contact contact : model.getContacts()) {
AbstractContactModifyStrategy strategy = AbstractContactModifyStrategy.strategyWithOperationType(contact.getOperationType());
strategy.operate(contact);
}
}
}
- AbstractContactModifyStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
public abstract class AbstractContactModifyStrategy {
public final static AbstractContactModifyStrategy strategyWithOperationType(String operationType) throws ExceptionCommon {
if (operationType.equals("add")) {
return new AbstractContactAddStrategy();
} else if (operationType.equals("del")) {
return new AbstractContactDelStrategy();
} else /* if (operationType.equals("mod")) */ {
return new AbstractContactModStrategy();
}
}
public abstract void operate(Contact contact);
}
- ContactAddStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
public class ContactAddStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) throws ExceptionCommon {
// 判斷被添加的用戶不存在,若已存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactIsNull(contact);
contactMapper.addContact(contact);
}
}
- ContactDelStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
public class ContactDelStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) throws ExceptionCommon {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.delContact(contact);
}
}
- ContactModStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
public class ContactModStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) throws ExceptionCommon {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.modContact(contact);
}
}
4 version 1.0
問題
- 雖然在ManageController當中,不需要寫長段的
if - else
條件判斷了。但是依然把if - else
延遲到了AbstractContactModifyStrategy當中。即: 每添加一個策略,都需要對AbstractContactModifyStrategy進行修改
解決方案
- 在AbstractContactModifyStrategy當中,添加一個注冊方法,每個AbstractContactModifyStrategy的子類,都需要調用這一個注冊方法。將具體的實現類注冊到AbstractContactModifyStrategy當中。
- 對于每個具體的策略類,添加static靜態方法。在static靜態方法中,調用父類的注冊方法,將自己注冊進父類中
- static靜態方法只在類加載的時候才會被調用,因為本次項目使用的是Spring-Boot,直接添加類注解
@Component
,當Spring掃描到此組件時,會自動加載這個類。這個類的static就會被執行了
代碼
- AbstractContactModifyStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public abstract class AbstractContactModifyStrategy {
private static Map<String, Class<? extends AbstractContactModifyStrategy>> map = new HashMap<>();
public final static AbstractContactModifyStrategy strategyWithOperationType(String operationType) throws IllegalAccessException, InstantiationException {
return map.get(operationType).newInstance();
}
public final static void registerClass(String operateType, Class<? extends AbstractContactModifyStrategy> clz) {
map.put(operateType, clz);
}
public abstract void operate(Contact contact);
}
- ContactAddStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ContactAddStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) {
// 判斷被添加的用戶不存在,若已存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactIsNull(contact);
contactMapper.addContact(contact);
}
static {
AbstractContactModifyStrategy.registerClass("add", ContactAddStrategy.class);
}
}
- ContactDelStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
public class ContactDelStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.delContact(contact);
}
static {
AbstractContactModifyStrategy.registerClass("del", ContactDelStrategy.class);
}
}
- ContactModStrategy.java
package com.futureweaver.controller.strategy;
import com.futureweaver.request.Contact;
import com.futureweaver.validation.LogicValidation;
import org.springframework.beans.factory.annotation.Autowired;
public class ContactModStrategy extends AbstractContactModifyStrategy {
@Autowired
private ContactMapper contactMapper;
@Override
public void operate(Contact contact) {
// 判斷被添加的用戶已存在,若不存在,則拋異常(由LogicValidation負責拋異常)
LogicValidation.validateContactContactNotNull(contact);
contactMapper.modContact(contact);
}
static {
AbstractContactModifyStrategy.registerClass("mod", ContactModStrategy.class);
}
}
5 策略模式 & 工廠模式模式
- 策略模式
定義一系列的算法,把它們一個個封裝起來,并且使它們可相互替換?!对O計模式 - 可復用面向對象軟件的基礎》
- 簡單工廠模式
將“類實例化的操作”與“使用對象的操作”分開,讓使用者不用知道具體參數就可以實例化出所需要的“產品”類,從而避免了在客戶端代碼中顯式指定,實現了解耦。
本文中,第二部分 version 0.3,多條
if - else
,即使用了簡單工廠,用于創建策略類。
第五部分 總結
本篇介紹了三個模式: 裝飾器模式、策略模式、簡單工廠模式
- 利用裝飾器模式,動態地為每一個響應體,添加了code及msg。
- 當無異常時,響應體填充code為0; msg為"success"
- 當有異常時,為每一個程序內部的異常,取出填充到響應體中
- 利用策略模式,為通訊錄的增、刪、改,分別封裝了算法。
- 利用簡單工廠模式,根據請求體中的枚舉,生產了相應的策略。