前幾天看到有一篇不錯的文章減少該死的if-else嵌套,覺得寫得很不錯,整理了一下后准備在團隊內部簡單分享一下。
寫在前面
大家在接手項目的時候,應該有遇到過下面這種結構的代碼
if (true) {
.....
if (true) {
if (true) {
....
if (true) {
if (true) {
......
}
}
// do something
}
// do something
if (true) {
.........
}
}
// do something
}
看到這些代碼,第一反應是腦殼痛,N多的if-else已經將這段代碼的邏輯變得十分復雜,代碼的可讀性和可維護性也會變得極差。下面舉個實際案例來介紹如何優化這種代碼。
實際案例
業務需求:提供一個服務,可以讓用戶分享鏈接、圖片、文本和圖文,並將分享結果回調給用戶。
初看這個業務很需求很簡單,先寫一個簡單的demo
public class ShareItem {
public static final int TYPE_LINK = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_TEXT = 2;
public static final int TYPE_IMAGE_TEXT = 3;
public int type;
public String title;
public String content;
public String imagePath;
public String link;
}
public void share (ShareItem item, ShareListener listener) {
if (item != null) {
if (item.type == TYPE_LINK) {
// 分享鏈接
if (!StringUtils.isEmpty(item.link) && !StringUtils.isEmpty(item.title)) {
doShareLink(item.link, item.title, item.content, listener);
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
}
} else if (item.type == TYPE_IMAGE) {
// 分享圖片
if (!StringUtils.isEmpty(item.imagePath)) {
doShareImage(item.imagePath, listener);
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
}
} else if (item.type == TYPE_TEXT) {
// 分享文本
if (!StringUtils.isEmpty(item.content)) {
doShareText(item.content, listener);
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
}
} else if (item.type == TYPE_IMAGE_TEXT) {
// 分享圖文
if (!StringUtils.isEmpty(item.imagePath) && !StringUtils.isEmpty(item.content)) {
doShareImageAndText(item.imagePath, item.content, listener);
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
}
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享類型");
}
}
} else {
if (listener != null) {
listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能為 null");
}
}
}
上面這段代碼,寫完后好像沒啥問題,至少思路是清晰的,但是如果經過幾輪需求的摧殘,這個代碼可能就會變得很爆炸。比如說,新增了好幾個分享方式,那if-else就會一直往下疊,更不用說如果產品來了一些定制化的需求,if-else里面就會再嵌if-else,最終極可能會得和最上面的代碼例子一樣了。
分層、衛語句
分層
接口分為外部和內部接口,所有空值判斷放在外部接口完成,只處理一次;而內部接口傳入的變量由外部接口保證不為空,從而減少空值判斷。
Controller(簡單校驗) -> Service(核心業務處理) -> Data
Controller -> InternalService(簡單校驗) -> Service(核心業務處理) -> Data
衛語句
將異常流提前返回,好處是可以將異常流和正常流的代碼分開,讓開發人員將精力更聚焦在正常的業務流程上面,而且這種方式也能有效地較少if—else的嵌套層數。
if (a != null) {
// 正常流
} else {
// 異常流
}
if (a == null) {
// 異常流
return;
}
// 正常流
改造后的代碼
public boolean share(ShareItem item, ShareListener listener) {
// 前置校驗
if (listener == null) {
log.error("listener 不能為空!");
return false;
}
if (item == null) {
log.error("shareItem 不能為空!");
return false;
}
// 核心業務處理
shareProcess(item, listener);
return true;
}
public void shareProcess(ShareItem item, ShareListener listener) {
if (item.type == TYPE_LINK) {
// 分享鏈接
if (!StringUtils.isEmpty(item.link) && !StringUtils.isEmpty(item.title)) {
doShareLink(item.link, item.title, item.content, listener);
} else {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
} else if (item.type == TYPE_IMAGE) {
// 分享圖片
if (!StringUtils.isEmpty(item.imagePath)) {
doShareImage(item.imagePath, listener);
} else {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
} else if (item.type == TYPE_TEXT) {
// 分享文本
if (!StringUtils.isEmpty(item.content)) {
doShareText(item.content, listener);
} else {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
} else if (item.type == TYPE_IMAGE_TEXT) {
// 分享圖文
if (!StringUtils.isEmpty(item.imagePath) && !StringUtils.isEmpty(item.content)) {
doShareImageAndText(item.imagePath, item.content, listener);
} else {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
}
} else {
listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享類型");
}
}
多態
利用多態,每種業務單獨處理,在接口不再做任何業務判斷。把ShareItem抽象出來,作為基礎類,然后針對每種業務各自實現其子類:
public abstract class ShareItem {
public static final int TYPE_LINK = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_TEXT = 2;
public static final int TYPE_IMAGE_TEXT = 3;
public int type;
public ShareItem(int type) {
this.type = type;
}
public abstract boolean validate();
public abstract void doShare(ShareListener listener);
}
public class LinkShareItem extends ShareItem {
private String title;
private String content;
private String link;
public LinkShareItem(String title, String content, String link) {
super(TYPE_LINK);
this.title = title;
this.content = content;
this.link = link;
}
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
// do share
}
}
public class ImageShareItem extends ShareItem {
private String imagePath;
public ImageShareItem(String imagePath) {
super(ShareItem.TYPE_IMAGE);
this.imagePath = imagePath;
}
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
// do share
}
}
public class ImageTextShareItem extends ShareItem {
private String content;
private String imagePath;
public ImageTextShareItem(String content, String imagePath) {
super(ShareItem.TYPE_IMAGE_TEXT);
this.content = content;
this.imagePath = imagePath;
}
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
// do share
}
}
public class TextShareItem extends ShareItem {
private String content;
public TextShareItem(String content) {
super(ShareItem.TYPE_TEXT);
this.content = content;
}
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
// do share
}
}
將實現分離出來后,方法就變得很簡潔了,if-else基本都已經被消除,以后如果新增一種分享方式,就只需要編寫對應的實現方式就可以了。
public void share(ShareItem item, ShareListener listener) {
// 前置校驗
if (listener == null) {
log.error("listener 不能為空!");
return;
}
if (item == null) {
log.error("shareItem 不能為空!");
return;
}
// 核心業務處理
shareProcess(item, listener);
}
public void shareProcess(ShareItem item, ShareListener listener) {
boolean validateRet = item.validate();
if (!validateRet) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
return;
}
item.doShare(listener);
}
策略模式
上面的多態模式,是不是看上去已經很完美了?不過在我眼里其實不然,有一點不太合理的是,ShareItem作為POJO,里面不適應做過多的業務實現。目前貧血模式下,ShareItem應該作為一個BO,里面只放基礎的屬性值,而業務實現應該都放到各自的service里面。
這時候我們可以考慮使用策略模式對其進行優化。
將原ShareItem的業務邏輯代碼移除,僅保留基礎的屬性
public abstract class ShareItem {
public static final int TYPE_LINK = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_TEXT = 2;
public static final int TYPE_IMAGE_TEXT = 3;
public int type;
}
public class ImageShareItem extends ShareItem {
private String imagePath;
}
public class ImageTextShareItem extends ShareItem {
private String content;
private String imagePath;
}
public class LinkShareItem extends ShareItem {
private String title;
private String content;
private String link;
}
public class TextShareItem extends ShareItem {
private String content;
}
編寫策略類
public abstract class AbstractShareHandler {
public abstract boolean validate();
public abstract void doShare(ShareListener listener);
public abstract int getShareType();
}
@Service
public class ImageShareHandle extends AbstractShareHandler {
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
}
@Override
public int getShareType() {
return ShareItem.TYPE_IMAGE;
}
}
@Service
public class ImageTextShareHandler extends AbstractShareHandler {
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
}
@Override
public int getShareType() {
return ShareItem.TYPE_IMAGE_TEXT;
}
}
@Service
public class LinkShareHandler extends AbstractShareHandler {
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
}
@Override
public int getShareType() {
return ShareItem.TYPE_LINK;
}
}
@Service
public class TextShareHandler extends AbstractShareHandler {
@Override
public boolean validate() {
return false;
}
@Override
public void doShare(ShareListener listener) {
}
@Override
public int getShareType() {
return ShareItem.TYPE_TEXT;
}
}
創建一個Manager類用來獲取和管理策略
@Service
public class ShareManager implements InitializingBean, ApplicationContextAware {
/**
* 策略集合
*/
private Map<Integer, AbstractShareHandler> shareHandlerMap = new HashMap<>();
private ApplicationContext applicationContext;
/**
* 獲取處理的策略
* @param shareType
* @return
*/
public AbstractShareHandler getHandler(Integer shareType) {
AbstractShareHandler shareHandler = shareHandlerMap.get(shareType);
if (shareHandler == null) {
throw new IllegalArgumentException("shareType handler not found, shareType=" + shareType);
}
return shareHandler;
}
@Override
public void afterPropertiesSet() {
// 自動注入策略
Map<String, AbstractShareHandler> shareImplMap = applicationContext.getBeansOfType(AbstractShareHandler.class);
for (AbstractShareHandler handler : shareImplMap.values()) {
shareHandlerMap.put(handler.getShareType(), handler);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
最后調整后的share方法就會變為下圖那樣
@Resource
private ShareManager shareManager;
public void share(ShareItem item, ShareListener listener) {
// 前置校驗
if (listener == null) {
log.error("listener 不能為空!");
return;
}
if (item == null) {
log.error("shareItem 不能為空!");
return;
}
// 核心業務處理
shareProcess(item, listener);
}
private void shareProcess(ShareItem item, ShareListener listener) {
AbstractShareHandler handler = shareManager.getHandler(item.getType());
if (!handler.validate()) {
listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
return;
}
handler.doShare(listener);
}
至此,我們就用策略模式對這段代碼進行了優化,看上去是不是比之前的版本好多了呢?