來吧,用設計模式來幹掉 if-else 啊~

前言

物流行業中,通常會涉及到EDI報文(XML格式文件)傳輸和回執接收,每發送一份EDI報文,後續都會收到與之關聯的回執(標識該數據在第三方系統中的流轉狀態)。

這裏枚舉幾種回執類型:MT1101、MT2101、MT4101、MT8104、MT8105、MT9999,系統在收到不同的回執報文後,會執行對應的業務邏輯處理。當然,實際業務場景並沒有那麼籠統,這裏以回執處理爲演示案例。

模擬一個回執類

@Data
public class Receipt {

    /**
     * 回執信息
     */
    String message;

    /**
     * 回執類型(`MT1101、MT2101、MT4101、MT8104、MT8105、MT9999`)
     */
    String type;

}

模擬一個回執生成器

public class ReceiptBuilder {

    public static List<Receipt> generateReceiptList(){
        //直接模擬一堆回執對象
        List<Receipt> receiptList = new ArrayList<>();
        receiptList.add(new Receipt("我是MT2101回執喔","MT2101"));
        receiptList.add(new Receipt("我是MT1101回執喔","MT1101"));
        receiptList.add(new Receipt("我是MT8104回執喔","MT8104"));
        receiptList.add(new Receipt("我是MT9999回執喔","MT9999"));
        //......
        return receiptList;
    }
}

傳統做法-if-else分支

List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
//循環處理
for (Receipt receipt : receiptList) {
    if (StringUtils.equals("MT2101",receipt.getType())) {
        System.out.println("接收到MT2101回執");
        System.out.println("解析回執內容");
        System.out.println("執行業務邏輯");
    } else if (StringUtils.equals("MT1101",receipt.getType())) {
        System.out.println("接收到MT1101回執");
        System.out.println("解析回執內容");
        System.out.println("執行業務邏輯");
    } else if (StringUtils.equals("MT8104",receipt.getType())) {
        System.out.println("接收到MT8104回執");
        System.out.println("解析回執內容");
        System.out.println("執行業務邏輯");
    } else if (StringUtils.equals("MT9999",receipt.getType())) {
        System.out.println("接收到MT9999回執");
        System.out.println("解析回執內容");
        System.out.println("執行業務邏輯");
        System.out.println("推送郵件");
    }
    // ......未來可能還有好多個else if
}

在遇到if-else的分支業務邏輯比較複雜時,我們都習慣於將其抽出一個方法或者封裝成一個對象去調用,這樣整個if-else結構就不會顯得太臃腫。

就上面例子,當回執的類型越來越多時,分支else if 就會越來越多,每增加一個回執類型,就需要修改或添加if-else分支,違反了開閉原則(對擴展開放,對修改關閉)

策略模式+Map字典
我們知道, 策略模式的目的是封裝一系列的算法,它們具有共性,可以相互替換,也就是說讓算法獨立於使用它的客戶端而獨立變化,客戶端僅僅依賴於策略接口 。

在上述場景中,我們可以把if-else分支的業務邏輯抽取爲各種策略,但是不可避免的是依然需要客戶端寫一些if-else進行策略選擇的邏輯,我們可以將這段邏輯抽取到工廠類中去,這就是策略模式+簡單工廠,代碼如下

策略接口

/**
 * @Description: 回執處理策略接口
 * @Auther: wuzhazha
 */
public interface IReceiptHandleStrategy {

    void handleReceipt(Receipt receipt);

}

策略接口實現類,也就是具體的處理者

public class Mt2101ReceiptHandleStrategy implements IReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析報文MT2101:" + receipt.getMessage());
    }

}

public class Mt1101ReceiptHandleStrategy implements IReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析報文MT1101:" + receipt.getMessage());
    }

}

public class Mt8104ReceiptHandleStrategy implements IReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析報文MT8104:" + receipt.getMessage());
    }

}

public class Mt9999ReceiptHandleStrategy implements IReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析報文MT9999:" + receipt.getMessage());
    }

}

策略上下文類(策略接口的持有者)

/**
 * @Description: 上下文類,持有策略接口
 * @Auther: wuzhazha
 */
public class ReceiptStrategyContext {

    private IReceiptHandleStrategy receiptHandleStrategy;

    /**
     * 設置策略接口
     * @param receiptHandleStrategy
     */
    public void setReceiptHandleStrategy(IReceiptHandleStrategy receiptHandleStrategy) {
        this.receiptHandleStrategy = receiptHandleStrategy;
    }

    public void handleReceipt(Receipt receipt){
        if (receiptHandleStrategy != null) {
             receiptHandleStrategy.handleReceipt(receipt);
        }
    }
}

策略工廠

/**
 * @Description: 策略工廠
 * @Auther: wuzhazha
 */
public class ReceiptHandleStrategyFactory {

    private ReceiptHandleStrategyFactory(){}

    public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
        IReceiptHandleStrategy receiptHandleStrategy = null;
        if (StringUtils.equals("MT2101",receiptType)) {
            receiptHandleStrategy = new Mt2101ReceiptHandleStrategy();
        } else if (StringUtils.equals("MT8104",receiptType)) {
            receiptHandleStrategy = new Mt8104ReceiptHandleStrategy();
        }
        return receiptHandleStrategy;
    }
}

客戶端

public class Client {

    public static void main(String[] args) {
        //模擬回執
        List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
        //策略上下文
        ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();
        for (Receipt receipt : receiptList) {
            //獲取並設置策略
            IReceiptHandleStrategy receiptHandleStrategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
            receiptStrategyContext.setReceiptHandleStrategy(receiptHandleStrategy);
            //執行策略
            receiptStrategyContext.handleReceipt(receipt);
        }
    }
}

解析報文MT2101:我是MT2101回執報文喔 解析報文MT8104:我是MT8104回執報文喔

由於我們的目的是消除if-else,那麼這裏需要將ReceiptHandleStrategyFactory策略工廠進行改造下,採用字典的方式存放我的策略,而Map具備key-value結構,採用Map是個不錯選擇。

稍微改造下,代碼如下

/**
 * @Description: 策略工廠
 * @Auther: wuzhazha
 */
public class ReceiptHandleStrategyFactory {

    private static Map<String,IReceiptHandleStrategy> receiptHandleStrategyMap;

    private ReceiptHandleStrategyFactory(){
        this.receiptHandleStrategyMap = new HashMap<>();
        this.receiptHandleStrategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
        this.receiptHandleStrategyMap.put("MT8104",new Mt8104ReceiptHandleStrategy());
    }

    public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
        return receiptHandleStrategyMap.get(receiptType);
    }
}

經過對策略模式+簡單工廠方案的改造,我們已經消除了if-else的結構,每當新來了一種回執,只需要添加新的回執處理策略,並修改ReceiptHandleStrategyFactory中的Map集合。

如果要使得程序符合開閉原則,則需要調整ReceiptHandleStrategyFactory中處理策略的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandleStrategy實現類,然後放到字典Map中去。

責任鏈模式

責任鏈模式是一種對象的行爲模式。在責任鏈模式裏,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任

回執處理者接口

/**
 * @Description: 抽象回執處理者接口
 * @Auther: wuzhazha
 */
public interface IReceiptHandler {

    void handleReceipt(Receipt receipt,IReceiptHandleChain handleChain);

}

責任鏈接口

/**
 * @Description: 責任鏈接口
 * @Auther: wuzhazha
 */
public interface IReceiptHandleChain {

    void handleReceipt(Receipt receipt);
}

責任鏈接口實現類

/**
 * @Description: 責任鏈實現類
 * @Auther: wuzhazha
 */
public class ReceiptHandleChain implements IReceiptHandleChain {
    //記錄當前處理者位置
    private int index = 0;
    //處理者集合
    private static List<IReceiptHandler> receiptHandlerList;

    static {
        //從容器中獲取處理器對象
        receiptHandlerList = ReceiptHandlerContainer.getReceiptHandlerList();
    }

    @Override
    public void handleReceipt(Receipt receipt) {
        if (receiptHandlerList !=null && receiptHandlerList.size() > 0) {
            if (index != receiptHandlerList.size()) {
                IReceiptHandler receiptHandler = receiptHandlerList.get(index++);
                receiptHandler.handleReceipt(receipt,this);
            }
        }
    }
}

具體回執處理者

public class Mt2101ReceiptHandler implements IReceiptHandler {

    @Override
    public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
        if (StringUtils.equals("MT2101",receipt.getType())) {
            System.out.println("解析報文MT2101:" + receipt.getMessage());
        }
        //處理不了該回執就往下傳遞
        else {
            handleChain.handleReceipt(receipt);
        }
    }
}

public class Mt8104ReceiptHandler implements IReceiptHandler {

    @Override
    public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
        if (StringUtils.equals("MT8104",receipt.getType())) {
            System.out.println("解析報文MT8104:" + receipt.getMessage());
        }
        //處理不了該回執就往下傳遞
        else {
            handleChain.handleReceipt(receipt);
        }
    }
}

責任鏈處理者容器(如果採用spring,則可以通過依賴注入的方式獲取到IReceiptHandler的子類對象)

/**
 * @Description: 處理者容器
 * @Auther: wuzhazha
 */
public class ReceiptHandlerContainer {

    private ReceiptHandlerContainer(){}

    public static List<IReceiptHandler> getReceiptHandlerList(){
        List<IReceiptHandler> receiptHandlerList = new ArrayList<>();
        receiptHandlerList.add(new Mt2101ReceiptHandler());
        receiptHandlerList.add(new Mt8104ReceiptHandler());
        return receiptHandlerList;
    }

}

客戶端

public class Client {

    public static void main(String[] args) {
        //模擬回執
        List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
        for (Receipt receipt : receiptList) {
            //回執處理鏈對象
            ReceiptHandleChain receiptHandleChain = new ReceiptHandleChain();
            receiptHandleChain.handleReceipt(receipt);
        }
    }
}

解析報文MT2101:我是MT2101回執報文喔 解析報文MT8104:我是MT8104回執報文喔

通過責任鏈的處理方式,if-else結構也被我們消除了,每當新來了一種回執,只需要添加IReceiptHandler實現類並修改ReceiptHandlerContainer處理者容器即可,如果要使得程序符合開閉原則,則需要調整ReceiptHandlerContainer中處理者的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandler實現類。

這裏使用到了一個反射工具類,用於獲取指定接口的所有實現類

/**
 * @Description: 反射工具類
 * @Auther: wuzhazha
 */
public class ReflectionUtil {

    /**
     * 定義類集合(用於存放所有加載的類)
     */
    private static final Set<Class<?>> CLASS_SET;

    static {
        //指定加載包路徑
        CLASS_SET = getClassSet("com.yaolong");
    }

    /**
     * 獲取類加載器
     * @return
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加載類
     * @param className 類全限定名稱
     * @param isInitialized 是否在加載完成後執行靜態代碼塊
     * @return
     */
    public static Class<?> loadClass(String className,boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className,isInitialized,getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return cls;
    }

    public static Class<?> loadClass(String className) {
        return loadClass(className,true);
    }

    /**
     * 獲取指定包下所有類
     * @param packageName
     * @return
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath().replace("%20","");
                        addClass(classSet,packagePath,packageName);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null) {
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")) {
                                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                        doAddClass(classSet,className);
                                    }
                                }
                            }
                        }
                    }
                }
            }


        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return classSet;
    }

    private static void doAddClass(Set<Class<?>> classSet, String className) {
        Class<?> cls = loadClass(className,false);
        classSet.add(cls);
    }

    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
        final File[] files = new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile()) {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (StringUtils.isNotEmpty(packageName)) {
                    className = packageName + "." + className;
                }
                doAddClass(classSet,className);
            } else {
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet,subPackagePath,subPackageName);
            }
        }
    }


    public static Set<Class<?>> getClassSet() {
        return CLASS_SET;
    }

    /**
     * 獲取應用包名下某父類(或接口)的所有子類(或實現類)
     * @param superClass
     * @return
     */
    public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> cls : CLASS_SET) {
            if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 獲取應用包名下帶有某註解的類
     * @param annotationClass
     * @return
     */
    public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> cls : CLASS_SET) {
            if (cls.isAnnotationPresent(annotationClass)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

}

接下來改造ReceiptHandlerContainer

public class ReceiptHandlerContainer {

    private ReceiptHandlerContainer(){}

    public static List<IReceiptHandler> getReceiptHandlerList(){
        List<IReceiptHandler> receiptHandlerList = new ArrayList<>();
        //獲取IReceiptHandler接口的實現類
        Set<Class<?>> classList = ReflectionUtil.getClassSetBySuper(IReceiptHandler.class);
        if (classList != null && classList.size() > 0) {
            for (Class<?> clazz : classList) {
                try {
                    receiptHandlerList.add((IReceiptHandler)clazz.newInstance());
                } catch ( Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return receiptHandlerList;
    }

}

至此,該方案完美符合了開閉原則,如果新增一個回執類型,只需要添加一個新的回執處理器即可,無需做其它改動。如新加了MT6666的回執,代碼如下

public class Mt6666ReceiptHandler implements IReceiptHandler {

    @Override
    public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
        if (StringUtils.equals("MT6666",receipt.getType())) {
            System.out.println("解析報文MT6666:" + receipt.getMessage());
        }
        //處理不了該回執就往下傳遞
        else {
            handleChain.handleReceipt(receipt);
        }
    }
}

策略模式+註解

此方案其實和上述沒有太大異同,爲了能符合開閉原則,通過自定義註解的方式,標記處理者類,然後反射獲取到該類集合,放到Map容器中,這裏不再贅述

小結

if-else或switch case 這種分支判斷的方式對於分支邏輯不多的簡單業務,還是直觀高效的。對於業務複雜,分支邏輯多,採用適當的模式技巧,會讓代碼更加清晰,容易維護,但同時類或方法數量也是倍增的。我們需要對業務做好充分分析,避免一上來就設計模式,避免過度設計!

來源:cnblogs.com/DiDi516/p/11787257.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章