被迫重構程式碼,這次我幹掉了 if-else
阿新 • • 發佈:2020-06-10
>本文收錄在個人部落格:[www.chengxy-nds.top](http://www.chengxy-nds.top),技術資源共享,一起進步
最近公司貌似融到資了!開始發了瘋似的找渠道推廣,現在終於明白為啥前一段大肆的招人了,原來是在下一盤大棋,對員工總的來看是個好事,或許是時候該跟boss提一提漲工資的話題了。
不過,漲工資還沒下文,隨之而來的卻是一車一車的需求,每天都有新渠道接入,而且每個渠道都要提供個性化支援,開發量陡增。最近都沒什麼時間更文,準點下班都成奢望了!
![](https://imgkr.cn-bj.ufileos.com/071164ca-3b9b-41b9-89e2-f325b23cdea6.png)
由於推廣渠道的激增,而每一個下單來源在下單時都做特殊的邏輯處理,可能每兩天就會加一個來源,已經把之前的下單邏輯改的面目全。出於長遠的考慮,我決定對現有的邏輯進行重構,畢竟長痛不如短痛。
### 傳統的實現方式
我們看下邊的虛擬碼,大致就是重構前下單邏輯的程式碼,由於來源比較少,簡單的做`if-else`邏輯判斷足以滿足需求。
現在每種訂單來源的處理邏輯都有幾百行程式碼,看著已經比較臃腫,可我愣是遲遲沒動手重構,一方面業務方總像催命鬼一樣的讓你趕工期,想快速實現需求,這樣寫是最快;另一方面是不敢動,面對`古董`級程式碼,還是想求個安穩。
但這次來源一下子增加幾十個,再用這種方式做已經無法維護了,想象一下那種臃腫的`if-else`程式碼,別說開發想想都頭大!
```javascript
public class OrderServiceImpl implements IOrderService {
@Override
public String handle(OrderDTO dto) {
String type = dto.getType();
if ("1".equals(type)) {
return "處理普通訂單";
} else if ("2".equals(type)) {
return "處理團購訂單";
} else if ("3".equals(type)) {
return "處理促銷訂單";
}
return null;
}
}
```
### 策略模式的實現方式
思來想去基於當前業務場景重構,還是用策略模式比較合適,它是`oop`中比較著名的設計模式之一,對方法行為的抽象。
策略模式定義了一個擁有共同行為的演算法族,每個演算法都被封裝起來,可以互相替換,獨立於客戶端而變化。
#### 一、策略模式的使用場景:
- 針對同一問題的多種處理方式,僅僅是具體行為有差別時;
- 需要安全地封裝多種同一型別的操作時;
- 同一抽象類有多個子類,而客戶端需要使用`if-else` 或者 `switch-case` 來選擇具體子類時。
這個是用策略模式修改後程式碼:
```java
@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{
@Autowired
private OrderStencilledService orderStencilledService;
@Override
public void handle(OrderBO orderBO) {
/**
* 訂單完結廣播通知(1 - 支付完成)
*/
orderStencilledService.dispatchModeFanout(orderBO);
/**
* SCMS 出庫單
*/
orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());
}
}
```
每個訂單來源都有自己單獨的邏輯實現類,而每次需要新增訂單來源,直接新建實現類,修改`@OrderHandlerType(16)`的數值即可,再也不用去翻又臭又長的`if-lese`。
不僅如此在分配任務時,每個人負責開發幾種訂單來源邏輯,都可以做到互不干擾,而且很大程度上減少了合併程式碼的衝突。
#### 二、具體的實現過程:
#### 1、定義註解
定義一個標識訂單來源的註解`@OrderHandlerType`
```javascript
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
int value() default 0;
}
```
#### 2、抽象業務處理器
向上抽象出來一個具體的業務處理器
```javascript
public abstract class AbstractHandler {
abstract public void handle(OrderBO orderBO);
}
```
#### 3、專案啟動掃描 `handler` 入口
```javascript
@Component
@SuppressWarnings({"unused","rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {
private String basePackage = "com.ecej.order.pipeline.processor";
public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Map map = new HashMap();
ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{
int type = x.getAnnotation(OrderHandlerType.class).value();
map.put(type,x);
});
beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);
log.info("處理器初始化{}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));
}
}
```
#### 4、掃描需要用到的工具類
```javascript
public class ClassScaner {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private final List includeFilters = new ArrayList();
private final List excludeFilters = new ArrayList();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
/**
* 新增包含的Fiter
* @param includeFilter
*/
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
/**
* 新增排除的Fiter
* @param includeFilter
*/
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(excludeFilter);
}
/**
* 掃描指定的包,獲取包下所有的Class
* @param basePackage 包名
* @param targetTypes 需要指定的目標型別,可以是pojo,可以是註解
* @return Set>
*/
public static Set> scan(String basePackage,
Class>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
return cs.doScan(basePackage);
}
/**
* 掃描指定的包,獲取包下所有的Class
* @param basePackages 包名,多個
* @param targetTypes 需要指定的目標型別,可以是pojo,可以是註解
* @return Set>
*/
public static Set> scan(String[] basePackages,
Class>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
Set> classes = new HashSet>();
for (String s : basePackages){
classes.addAll(cs.doScan(s));
}
return classes;
}
/**
* 掃描指定的包,獲取包下所有的Class
* @param basePackages 包名
* @return Set>
*/
public Set> doScan(String [] basePackages) {
Set> classes = new HashSet>();
for (String basePackage :basePackages) {
classes.addAll(doScan(basePackage));
}
return classes;
}
/**
* 掃描指定的包,獲取包下所有的Class
* @param basePackages 包名
* @return Set>
*/
public Set> doScan(String basePackage) {
Set> classes = new HashSet>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(
SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (ClassNotFoundException ignore) {}
}
}
}
} catch (IOException ex) {
throw new RuntimeException("I/O failure during classpath scanning", ex);
}
return classes;
}
/**
* 處理 excludeFilters和includeFilters
* @param metadataReader
* @return boolean
* @throws IOException
*/
private boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}
```
#### 5、根據型別例項化抽象類
```javascript
@Component
public class HandlerContext {
@Autowired
private ApplicationContext beanFactory;
public AbstractHandler getInstance(Integer type){
Map map = (Map) beanFactory.getBean(OrderHandlerType.class.getName());
return (AbstractHandler)beanFactory.getBean(map.get(type));
}
}
```
#### 6、呼叫入口
我這裡是在接受到MQ訊息時,處理多個訂單來源業務,不同訂單來源路由到不同的業務處理類中。
```javascript
@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{
private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);
@Autowired
private HandlerContext HandlerContext;
@Autowired
private OrderValidateService orderValidateService;
@RabbitHandler
public void subscribeMessage(MessageBean bean){
OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);
if(null != orderBO &&CollectionUtils.isNotEmpty(bean.getType()))
{
for(int value:bean.getType())
{
AbstractHandler handler = HandlerContext.getInstance(value);
handler.handle(orderBO);
}
}
}
}
```
接收實體 `MessageBean` 類程式碼
```javascript
public class MessageBean implements Serializable {
private static final long serialVersionUID = 5454831432308782668L;
private String cachKey;
private List type;
private String orderBO;
public MessageBean(List type, String orderBO) {
this.type = type;
this.orderBO = orderBO;
}
}
```
以上設計模式方式看著略顯複雜,很些小夥伴提出質疑:“你為了個`if-else`,弄的如此的麻煩,又是自定義註解,又弄這麼多類不麻煩嗎?” 還有一些小夥伴糾結於效能問題,策略模式的效能可能確實不如`if-else`。
但我覺得吧增加一點複雜度、犧牲一丟丟效能,換程式碼的整潔和可維護性還是值得的。不過,一個人一個想法,怎麼選還是看具體業務場景吧!
![](https://imgkr.cn-bj.ufileos.com/2a93d66a-b330-43a8-a401-3aea4457b18e.png)
### 策略模式的優缺點
#### 優點
- 易於擴充套件,增加一個新的策略只需要新增一個具體的策略類即可,基本不需要改變原有的程式碼,符合開放封閉原則
- 避免使用多重條件選擇語句,充分體現面向物件設計思想 策略類之間可以自由切換,由於策略類都實現同一個介面,所以使它們之間可以自由切換
- 每個策略類使用一個策略類,符合單一職責原則 客戶端與策略演算法解耦,兩者都依賴於抽象策略介面,符合依賴反轉原則
- 客戶端不需要知道都有哪些策略類,符合最小知識原則
#### 缺點
- 策略模式,當策略演算法太多時,會造成很多的策略類
- 客戶端不知道有哪些策略類,不能決定使用哪個策略類,這點可以通過封裝common公共包解決,也可以考慮使`IOC容器`和`依賴注入`的方式來解決。
以下是訂單來源策略類的一部分,不得不說策略類確實比較多。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200117111440450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpbnpoaWZ1MQ==,size_16,color_FFFFFF,t_70)
### 總結
凡事都有他的兩面性,`if-else`多層巢狀和也都有其各自的優缺點:
- `if-else`的優點就是簡單,想快速迭代功能,邏輯巢狀少且不會持續增加,`if-else`更好些,缺點也是顯而易見,程式碼臃腫繁瑣不便於維護。
- `策略模式` 將各個場景的邏輯剝離出來維護,同一抽象類有多個子類,需要使用`if-else` 或者 `switch-case` 來選擇具體子類時,建議選策略模式,他的缺點就是會產生比較多的策略類檔案。
兩種實現方式各有利弊,如何選擇還是要依據具體業務場景,還是那句話設計模式不是為了用而用,一定要用在最合適的位置。
### 閒聊
平常和粉絲私下聊天,好多人對於學設計模式的感受:設計模式背了一大堆,可平常開發還不是成天寫`if-else`業務邏輯,根本就用不到。
學設計模式也不是用不到,只是有時候沒有合適它的場景而已,像我們今天說的這種業務場景,用設計模式就可以完美的解決嘛。
學了N多技術可工作用不到是一種很常見的事情,一個穩定的專案使用一種技術會有諸多考量的,新技術會不會提升系統複雜度?它有哪些效能瓶頸?這些都必須考慮到,畢竟專案穩定才是最重要,誰也不敢輕易冒險嘗試。
而我們學習技術可不僅為了眼下專案中是否會用到,是要做一個技術積累,做長遠打算,人往高處走,沒點能力可不行。
* * *
原創不易,燃燒秀髮輸出內容,希望你能有一丟丟收穫!
整理了幾百本各類技術電子書,送給小夥伴們。關公眾號回覆【666】自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就掃碼加入我們吧!
![](https://user-gold-cdn.xitu.io/2020/6/3/17279e17f0e0c9bd?w=900&h=500&f=png&s