被迫重構代碼,此次我幹掉了 if-else

本文收錄在我的博客:www.chengxy-nds.top,技術資源共享,一塊兒進步web

最近公司貌似融到資了!開始發了瘋似的找渠道推廣,如今終於明白爲啥前一段大肆的招人了,原來是在下一盤大棋,對員工總的來看是個好事,或許是時候該跟boss提一提漲工資的話題了。算法

不過,漲工資還沒下文,隨之而來的倒是一車一車的需求,天天都有新渠道接入,並且每一個渠道都要提供個性化支持,開發量陡增。最近都沒什麼時間更文,準點下班都成奢望了!設計模式

因爲推廣渠道的激增,而每個下單來源在下單時都作特殊的邏輯處理,可能每兩天就會加一個來源,已經把以前的下單邏輯改的面目全。出於長遠的考慮,我決定對現有的邏輯進行重構,畢竟長痛不如短痛。安全

傳統的實現方式

咱們看下邊的僞代碼,大體就是重構前下單邏輯的代碼,因爲來源比較少,簡單的作if-else邏輯判斷足以知足需求。編輯器

如今每種訂單來源的處理邏輯都有幾百行代碼,看着已經比較臃腫,可我愣是遲遲沒動手重構,一方面業務方總像催命鬼同樣的讓你趕工期,想快速實現需求,這樣寫是最快;另外一方面是不敢動,面對古董級代碼,仍是想求個安穩。ide

但此次來源一會兒增長几十個,再用這種方式作已經沒法維護了,想象一下那種臃腫的if-else代碼,別說開發想一想都頭大!工具

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中比較著名的設計模式之一,對方法行爲的抽象。oop

策略模式定義了一個擁有共同行爲的算法族,每一個算法都被封裝起來,能夠互相替換,獨立於客戶端而變化。post

1、策略模式的使用場景:

  • 針對同一問題的多種處理方式,僅僅是具體行爲有差異時;
  • 須要安全地封裝多種同一類型的操做時;
  • 同一抽象類有多個子類,而客戶端須要使用 if-else 或者 switch-case 來選擇具體子類時。

這個是用策略模式修改後代碼:性能

@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

不只如此在分配任務時,每一個人負責開發幾種訂單來源邏輯,均可以作到互不干擾,並且很大程度上減小了合併代碼的衝突。

2、具體的實現過程:

一、定義註解

定義一個標識訂單來源的註解@OrderHandlerType

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface OrderHandlerType {  int value() default 0; } 複製代碼

二、抽象業務處理器

向上抽象出來一個具體的業務處理器

public abstract class AbstractHandler {
 abstract public void handle(OrderBO orderBO); } 複製代碼

三、項目啓動掃描 handler 入口

@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<Integer,Class> map = new HashMap<Integer,Class>();   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())));  } } 複製代碼

四、掃描須要用到的工具類

public class ClassScaner {  private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();   private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();   private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();   private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);   //添加包含的Fiter  public void addIncludeFilter(TypeFilter includeFilter) {  this.includeFilters.add(includeFilter);  }   //添加排除的Fiter  public void addExcludeFilter(TypeFilter excludeFilter) {  this.excludeFilters.add(excludeFilter);  }   //掃描指定的包,獲取包下全部的Class  public static Set<Class<?>> 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  public static Set<Class<?>> 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<Class<?>> classes = new HashSet<Class<?>>();  for (String s : basePackages){  classes.addAll(cs.doScan(s));  }  return classes;  }   //掃描指定的包,獲取包下全部的Class  public Set<Class<?>> doScan(String [] basePackages) {  Set<Class<?>> classes = new HashSet<Class<?>>();  for (String basePackage :basePackages) {  classes.addAll(doScan(basePackage));  }  return classes;  }   //掃描指定的包,獲取包下全部的Class  public Set<Class<?>> doScan(String basePackage) {  Set<Class<?>> classes = new HashSet<Class<?>>();  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;  }   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;  } }  複製代碼

五、根據類型實例化抽象類

@Component public class HandlerContext {   @Autowired  private ApplicationContext beanFactory;   public AbstractHandler getInstance(Integer type){   Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());   return (AbstractHandler)beanFactory.getBean(map.get(type));  }  } 複製代碼

六、調用入口

我這裏是在接受到MQ消息時,處理多個訂單來源業務,不一樣訂單來源路由到不一樣的業務處理類中。

@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 類代碼

public class MessageBean implements Serializable {
 private static final long serialVersionUID = 5454831432308782668L;  private String cachKey;  private List<Integer> type;  private String orderBO;   public MessageBean(List<Integer> type, String orderBO) {  this.type = type;  this.orderBO = orderBO;  } } 複製代碼

以上設計模式方式看着略顯複雜,很些小夥伴提出質疑:「你爲了個if-else,弄的如此的麻煩,又是自定義註解,又弄這麼多類不麻煩嗎?」 還有一些小夥伴糾結於性能問題,策略模式的性能可能確實不如if-else

但我以爲吧增長一點複雜度、犧牲一丟丟性能,換代碼的整潔和可維護性仍是值得的。不過,一我的一個想法,怎麼選仍是看具體業務場景吧!

策略模式的優缺點

優勢

  • 易於擴展,增長一個新的策略只須要添加一個具體的策略類便可,基本不須要改變原有的代碼,符合開放封閉原則
  • 避免使用多重條件選擇語句,充分體現面向對象設計思想 策略類之間能夠自由切換,因爲策略類都實現同一個接口,因此使它們之間能夠自由切換
  • 每一個策略類使用一個策略類,符合單一職責原則 客戶端與策略算法解耦,二者都依賴於抽象策略接口,符合依賴反轉原則
  • 客戶端不須要知道都有哪些策略類,符合最小知識原則

缺點

  • 策略模式,當策略算法太多時,會形成不少的策略類
  • 客戶端不知道有哪些策略類,不能決定使用哪一個策略類,這點能夠經過封裝common公共包解決,也能夠考慮使 IOC容器依賴注入的方式來解決。

如下是訂單來源策略類的一部分,不得不說策略類確實比較多。 在這裏插入圖片描述

總結

凡事都有他的兩面性,if-else多層嵌套和也都有其各自的優缺點:

  • if-else的優勢就是簡單,想快速迭代功能,邏輯嵌套少且不會持續增長,if-else更好些,缺點也是顯而易見,代碼臃腫繁瑣不便於維護。

  • 策略模式 將各個場景的邏輯剝離出來維護,同一抽象類有多個子類,須要使用if-else 或者 switch-case 來選擇具體子類時,建議選策略模式,他的缺點就是會產生比較多的策略類文件。

兩種實現方式各有利弊,如何選擇仍是要依據具體業務場景,仍是那句話設計模式不是爲了用而用,必定要用在最合適的位置。

閒聊

日常和粉絲私下聊天,好多人對於學設計模式的感覺:設計模式背了一大堆,可日常開發還不是整天寫if-else業務邏輯,根本就用不到。

學設計模式也不是用不到,只是有時候沒有合適它的場景而已,像咱們今天說的這種業務場景,用設計模式就能夠完美的解決嘛。

學了N多技術可工做用不到是一種很常見的事情,一個穩定的項目使用一種技術會有諸多考量的,新技術會不會提高系統複雜度?它有哪些性能瓶頸?這些都必須考慮到,畢竟項目穩定纔是最重要,誰也不敢輕易冒險嘗試。

而咱們學習技術可不只爲了眼下項目中是否會用到,是要作一個技術積累,作長遠打算,人往高處走,沒點能力可不行。


原創不易,燃燒秀髮輸出內容,但願你能有一丟丟收穫!

整理了幾百本各種技術電子書,送給小夥伴們。關公衆號回覆【666】自行領取。和一些小夥伴們建了一個技術交流羣,一塊兒探討技術、分享技術資料,旨在共同窗習進步,若是感興趣就掃碼加入咱們吧!

相關文章
相關標籤/搜索