被迫重構(gòu)代碼,這次我干掉了 if-else
最近公司貌似融到資了!開始發(fā)了瘋似的找渠道推廣,現(xiàn)在終于明白為啥前一段大肆的招人了,原來是在下一盤大棋,對(duì)員工總的來看是個(gè)好事,或許是時(shí)候該跟boss提一提漲工資的話題了。
不過,漲工資還沒下文,隨之而來的卻是一車一車的需求,每天都有新渠道接入,而且每個(gè)渠道都要提供個(gè)性化支持,開發(fā)量陡增。最近都沒什么時(shí)間更文,準(zhǔn)點(diǎn)下班都成奢望了!
由于推廣渠道的激增,而每一個(gè)下單來源在下單時(shí)都做特殊的邏輯處理,可能每兩天就會(huì)加一個(gè)來源,已經(jīng)把之前的下單邏輯改的面目全。出于長遠(yuǎn)的考慮,我決定對(duì)現(xiàn)有的邏輯進(jìn)行重構(gòu),畢竟長痛不如短痛。
傳統(tǒng)的實(shí)現(xiàn)方式
我們看下邊的偽代碼,大致就是重構(gòu)前下單邏輯的代碼,由于來源比較少,簡單的做if-else
邏輯判斷足以滿足需求。
現(xiàn)在每種訂單來源的處理邏輯都有幾百行代碼,看著已經(jīng)比較臃腫,可我愣是遲遲沒動(dòng)手重構(gòu),一方面業(yè)務(wù)方總像催命鬼一樣的讓你趕工期,想快速實(shí)現(xiàn)需求,這樣寫是最快;另一方面是不敢動(dòng),面對(duì)古董
級(jí)代碼,還是想求個(gè)安穩(wěn)。
但這次來源一下子增加幾十個(gè),再用這種方式做已經(jīng)無法維護(hù)了,想象一下那種臃腫的if-else
代碼,別說開發(fā)想想都頭大!
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 "處理團(tuán)購訂單";
} else if ("3".equals(type)) {
return "處理促銷訂單";
}
return null;
}
}
策略模式的實(shí)現(xiàn)方式
思來想去基于當(dāng)前業(yè)務(wù)場景重構(gòu),還是用策略模式比較合適,它是oop
中比較著名的設(shè)計(jì)模式之一,對(duì)方法行為的抽象。
策略模式定義了一個(gè)擁有共同行為的算法族,每個(gè)算法都被封裝起來,可以互相替換,獨(dú)立于客戶端而變化。
一、策略模式的使用場景:
-
針對(duì)同一問題的多種處理方式,僅僅是具體行為有差別時(shí); -
需要安全地封裝多種同一類型的操作時(shí); -
同一抽象類有多個(gè)子類,而客戶端需要使用 if-else
或者switch-case
來選擇具體子類時(shí)。
這個(gè)是用策略模式修改后代碼:
@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{
@Autowired
private OrderStencilledService orderStencilledService;
@Override
public void handle(OrderBO orderBO) {
/**
* 訂單完結(jié)廣播通知(1 - 支付完成)
*/
orderStencilledService.dispatchModeFanout(orderBO);
/**
* SCMS 出庫單
*/
orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());
}
}
每個(gè)訂單來源都有自己單獨(dú)的邏輯實(shí)現(xiàn)類,而每次需要添加訂單來源,直接新建實(shí)現(xiàn)類,修改@OrderHandlerType(16)
的數(shù)值即可,再也不用去翻又臭又長的if-lese
。
不僅如此在分配任務(wù)時(shí),每個(gè)人負(fù)責(zé)開發(fā)幾種訂單來源邏輯,都可以做到互不干擾,而且很大程度上減少了合并代碼的沖突。
二、具體的實(shí)現(xiàn)過程:
1、定義注解
定義一個(gè)標(biāo)識(shí)訂單來源的注解@OrderHandlerType
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
int value() default 0;
}
2、抽象業(yè)務(wù)處理器
向上抽象出來一個(gè)具體的業(yè)務(wù)處理器
public abstract class AbstractHandler {
abstract public void handle(OrderBO orderBO);
}
3、項(xiàng)目啟動(dòng)掃描 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())));
}
}
4、掃描需要用到的工具類
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
* @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 需要指定的目標(biāo)類型,可以是pojo,可以是注解
* @return Set<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
* @param basePackages 包名,多個(gè)
* @param targetTypes 需要指定的目標(biāo)類型,可以是pojo,可以是注解
* @return Set<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
* @param basePackages 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String [] basePackages) {
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String basePackage :basePackages) {
classes.addAll(doScan(basePackage));
}
return classes;
}
/**
* 掃描指定的包,獲取包下所有的Class
* @param basePackages 包名
* @return Set<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;
}
/**
* 處理 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、根據(jù)類型實(shí)例化抽象類
@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));
}
}
6、調(diào)用入口
我這里是在接受到MQ消息時(shí),處理多個(gè)訂單來源業(yè)務(wù),不同訂單來源路由到不同的業(yè)務(wù)處理類中。
@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);
}
}
}
}
接收實(shí)體 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;
}
}
以上設(shè)計(jì)模式方式看著略顯復(fù)雜,很些小伙伴提出質(zhì)疑:“你為了個(gè)if-else
,弄的如此的麻煩,又是自定義注解,又弄這么多類不麻煩嗎?” 還有一些小伙伴糾結(jié)于性能問題,策略模式的性能可能確實(shí)不如if-else
。
但我覺得吧增加一點(diǎn)復(fù)雜度、犧牲一丟丟性能,換代碼的整潔和可維護(hù)性還是值得的。不過,一個(gè)人一個(gè)想法,怎么選還是看具體業(yè)務(wù)場景吧!
策略模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
-
易于擴(kuò)展,增加一個(gè)新的策略只需要添加一個(gè)具體的策略類即可,基本不需要改變?cè)械拇a,符合開放封閉原則 -
避免使用多重條件選擇語句,充分體現(xiàn)面向?qū)ο笤O(shè)計(jì)思想 策略類之間可以自由切換,由于策略類都實(shí)現(xiàn)同一個(gè)接口,所以使它們之間可以自由切換 -
每個(gè)策略類使用一個(gè)策略類,符合單一職責(zé)原則 客戶端與策略算法解耦,兩者都依賴于抽象策略接口,符合依賴反轉(zhuǎn)原則 -
客戶端不需要知道都有哪些策略類,符合最小知識(shí)原則
缺點(diǎn)
-
策略模式,當(dāng)策略算法太多時(shí),會(huì)造成很多的策略類 -
客戶端不知道有哪些策略類,不能決定使用哪個(gè)策略類,這點(diǎn)可以通過封裝common公共包解決,也可以考慮使 IOC容器
和依賴注入
的方式來解決。
以下是訂單來源策略類的一部分,不得不說策略類確實(shí)比較多。
總結(jié)
凡事都有他的兩面性,if-else
多層嵌套和也都有其各自的優(yōu)缺點(diǎn):
-
if-else
的優(yōu)點(diǎn)就是簡單,想快速迭代功能,邏輯嵌套少且不會(huì)持續(xù)增加,if-else
更好些,缺點(diǎn)也是顯而易見,代碼臃腫繁瑣不便于維護(hù)。 -
策略模式
將各個(gè)場景的邏輯剝離出來維護(hù),同一抽象類有多個(gè)子類,需要使用if-else
或者switch-case
來選擇具體子類時(shí),建議選策略模式,他的缺點(diǎn)就是會(huì)產(chǎn)生比較多的策略類文件。
兩種實(shí)現(xiàn)方式各有利弊,如何選擇還是要依據(jù)具體業(yè)務(wù)場景,還是那句話設(shè)計(jì)模式不是為了用而用,一定要用在最合適的位置。
閑聊
平常和粉絲私下聊天,好多人對(duì)于學(xué)設(shè)計(jì)模式的感受:設(shè)計(jì)模式背了一大堆,可平常開發(fā)還不是成天寫if-else
業(yè)務(wù)邏輯,根本就用不到。
學(xué)設(shè)計(jì)模式也不是用不到,只是有時(shí)候沒有合適它的場景而已,像我們今天說的這種業(yè)務(wù)場景,用設(shè)計(jì)模式就可以完美的解決嘛。
學(xué)了N多技術(shù)可工作用不到是一種很常見的事情,一個(gè)穩(wěn)定的項(xiàng)目使用一種技術(shù)會(huì)有諸多考量的,新技術(shù)會(huì)不會(huì)提升系統(tǒng)復(fù)雜度?它有哪些性能瓶頸?這些都必須考慮到,畢竟項(xiàng)目穩(wěn)定才是最重要,誰也不敢輕易冒險(xiǎn)嘗試。
而我們學(xué)習(xí)技術(shù)可不僅為了眼下項(xiàng)目中是否會(huì)用到,是要做一個(gè)技術(shù)積累,做長遠(yuǎn)打算,人往高處走,沒點(diǎn)能力可不行。
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!