1. 程式人生 > >如何將不同型別的商品按它們不同的屬性進行排序(工廠方法 + 簡單工廠的綜合解決方案)

如何將不同型別的商品按它們不同的屬性進行排序(工廠方法 + 簡單工廠的綜合解決方案)

這是最近實習的時候老闆給的一個需求,具體要求:將商品按照它的進貨價,零售價,銷量等11個屬性分別進行升降序排序;將倉庫商品按照它的庫存等5個屬性進行升降序排序。

注:商品和倉庫商品沒有什麼聯絡,可以把它們認為是兩個物件:Product類和RepositoryProduct類。
ps:前臺傳遞的引數是兩個狀態碼,一個表示按照哪個屬性進行排序,另一個區分升降序。

解決思路:

  1. 對物件集合排序可以使用 Collections.sort(list, comparator)方法。傳入對應的 list 集合以及比較器,共 16 種排序規則就要寫 16 個比較器,再加上 if else 判斷,寫出來的程式碼就是這樣的:

    Integer status = 1; // 1表示按照進貨價排序,2表示...,3...
    if (status.equals(1)) {
    	Collections.sort(list, new Comparator<Product>(){...});
    } else if (status.equals(2)) {
    	Collections.sort(list, new Comparator<RepositoryProduct>(){...});
    }
    ...
    

    如果又增加了一種排序規則就要新增 else if分支,把這套排序邏輯直接放在 Controller 層就會顯得異常的臃腫,而且擴充套件性也不好,因此考慮新的解決方案。

  2. 分析上面的程式碼,我們發現排序所呼叫的介面是不變的,即都需要呼叫Collections.sort,變的是需要傳入不同的比較器,比較器有很多,馬上想到可以使用工廠模式來優化上面的業務邏輯。

  3. 工廠模式有三種:簡單工廠(或者說是靜態工廠)模式,工廠方法模式,抽象工廠模式,用哪種呢?

  4. 最終採用工廠方法 + 簡單工廠相結合的解決方案:

    工廠方法模式:定義一個建立比較器物件的工廠介面或抽象類,具體的建立比較器這一動作由該抽象類的子類來實現。針對該需求的工廠方法的 UML 類圖如下:
    在這裡插入圖片描述

    左半部分是工廠的抽象類以及其實現體系,右半部分是產品的抽象類以及其實現體系。每一種工廠負責建立一種比較器,這樣就不需要繁瑣的if else

    判斷了,呼叫方需要哪一種比較器就建立哪一種比較器工廠。

    抽象產品介面已經由JDK為我們提供了:

    public interface Comparator<T> {
    	int compare(T o1, T o2);
    }
    

    具體的產品:

    /**
     * @author 曲健磊
     * @date 2018年10月21日 下午4:14:53
     * @description 對商品進行排序的比較器
     */
    public class ProductComparator implements Comparator<Product> {
    
        @Override
        public int compare(Product o1, Product o2) {
            Integer num1 = o1.getNum();
            Integer num2 = o2.getNum();
            return num1 == num2 ? 0 : num1 > num2 ? 1 : -1;
        }
    }
    
    /**
     * @author 曲健磊
     * @date 2018年10月21日 下午4:14:54
     * @description 對倉庫商品進行排序的比較器
     */
    public class RepositoryProductComparator implements Comparator<RepositoryProduct> {
    
        @Override
        public int compare(RepositoryProduct o1, RepositoryProduct o2) {
            Integer stock1 = o1.getStock();
            Integer stock2 = o2.getStock();
            return stock1 == stock2 ? 0 : stock1 > stock2 ? 1 : -1;
        }
    }
    

    抽象工廠類:

    /**
     * @author 曲健磊
     * @date 2018年10月20日 下午12:48:17
     * @description 抽象比較器工廠(工廠方法)
     */
    public abstract class ComparatorFactory<T> {
    
        /**
         * 建立比較器的抽象方法
         * @param paramEnum 排序的基準
         * @param orderEnum 升序降序
         * @return
         */
        public abstract Comparator<T> createComparator(Enum<?> paramEnum, Enum<?> orderEnum);
    }
    

    具體的工廠:

    /**
     * @author 曲健磊
     * @date 2018年10月22日 下午2:48:30
     * @description 對商品(Product)進行排序的比較器工(簡單工廠)
     */
    public class ProductComparatorFactory extends ComparatorFactory<Product> {
    
        @Override
        public Comparator<Product> createComparator(Enum<?> paramEnum, Enum<?> orderEnum) {
            RepositoryProductSortEnum productSortEnum = (ProductSortEnum) paramEnum;
            OrderSortEnum orderSortEnum = (OrderSortEnum) orderEnum;
    
            switch(productSortEnum ) {
            case PACK_CODE:
                return new PackCodeComparator(orderSortEnum);
            case SHOP_STOCK:
                return new ShopStockComparator(orderSortEnum);
            case OUT_NUM:
                return new TodoOutNumComparator(orderSortEnum);
            case YEST_OUT_NUM:
                return new YestOutNumComparator(orderSortEnum);
            case LASTWEEK_OUT_NUM:
                return new LastWeekOutNumComparator(orderSortEnum);
            default:
                return new DefaultComparator();
            }
        }
    }
    
    /**
     * @author 曲健磊
     * @date 2018年10月20日 下午6:13:42
     * @description 對商品進行排序的比較器工廠(簡單工廠)
     */
    public class ShopVOComparatorFactory extends ComparatorFactory<ShopVO> {
    
        @Override
        public Comparator<ShopVO> createComparator(Enum<?> paramEnum, Enum<?> orderEnum) {
            ShopSortEnum shopSortEnum = (ShopSortEnum) paramEnum;
            OrderSortEnum orderSortEnum = (OrderSortEnum) orderEnum;
    
            switch(shopSortEnum) {
            case SUPPLY_PRICE:
                return new SupplyPriceComparator(orderSortEnum);
            case SHOP_STOCK:
                return new ShopStockComparator(orderSortEnum);
            case SHOP_CATE:
                return new ShopCateComparator(orderSortEnum);
            case SHOP_BRAND:
                return new ShopBrandComparator(orderSortEnum);
            case SHOP_JWEIGHT:
                return new ShopJWeightComparator(orderSortEnum);
            case SHOP_DWEIGHT:
                return new ShopDWeightComparator(orderSortEnum);
            case CC_CESS:
                return new CcTaxRateComparator(orderSortEnum);
            case RETAIL_PRICE:
                return new ShopLPriceComparator(orderSortEnum);
            case YEST_SALESNUM:
                return new YestSalesNumComparator(orderSortEnum);
            case LASTWEEK_SALESNUM:
                return new LastWeekSalesNumComparator(orderSortEnum);
            case LASTMONTH_SALESNUM:
                return new LastMonthSalesNumComparator(orderSortEnum);
            case UPDATE_TIME:
                return new UpdateTimeComparator(orderSortEnum);
            default:
                return new DefaultComparator();
            }
        }
    }
    

    細心的同學已經發現了,抽象工廠的子類其實是一個簡單工廠,這個簡單工廠可以根據傳入引數的不同,建立不同型別的產品。不同於普通的工廠方法一個工廠只能建立一個產品。

    因為我們的需求是根據不同產品各自不同的屬性進行排序,假設現在有 10 個產品,每一個產品都有七八個屬性,只使用工廠方法的話,那我們至少要建立70個工廠以及70個對應的比較器。但是結合了簡單工廠的方式,雖然要建立的比較器的數量仍然是不變的,但是工廠的建立數量大大減少了。這也是該方案創新的地方。

    因為產品的屬性較多,所以用列舉來封裝這些屬性:

    /**
     * @author 曲健磊
     * @date 2018年10月19日 下午3:16:43
     * @description 商品排序的指標
     */
    @Getter
    public enum ShopSortEnum {
        SUPPLY_PRICE(1, "供貨價"),
        SHOP_STOCK(2, "商品庫存"),
        SHOP_CATE(3, "商品分類"),
        SHOP_BRAND(4, "商品品牌"),
        SHOP_JWEIGHT(5, "商品淨重量"),
        SHOP_DWEIGHT(6, "商品打包重量"),
        CC_CESS(7, "CC稅率"),
        RETAIL_PRICE(8, "商品零售價"),
        YEST_SALESNUM(9, "昨日銷量"),
        LASTWEEK_SALESNUM(10, "過去7天平均銷量"),
        LASTMONTH_SALESNUM(11, "過去30天平均銷量"),
        UPDATE_TIME(12, "商品的更新時間"),
        ;
    
        private Integer code;
    
        private String message;
    
        ShopSortEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    }
    

    升序和降序也用列舉來封裝:

    /**
     * @author 曲健磊
     * @date 2018年10月20日 下午4:55:31
     * @description 排序的順序
     */
    @Getter
    public enum OrderSortEnum {
        ASC(1, "升序"),
        DESC(2, "降序"),
        ;
    
        private Integer code;
    
        private String message;
    
        OrderSortEnum(Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    }
    

    最後在呼叫具體的工廠的createComparator方法建立比較器的時候,傳入對應的列舉,然後在switch語句中判斷需要建立何種型別的比較器,返回即可。

    Controller層呼叫的方式如下:

    @PostMapping("/list")
    @ResponseBody
    public List<Shop> list(@RequestParam(value = "sortAttr", defaultValue = "1") Integer sortAttr,
    					   @RequestParam(value = "ascDesc", defaultValue = "2") Integer ascDesc) {
    	List<Shop> shopList = shopService.getAll();
    	
    	// 1.將對應的狀態碼轉換成列舉
    	ShopSortEnum se = Status2EnumConverter.trans2ShopSort(status);
        OrderSortEnum os = Status2EnumConverter.trans2OrderSort(ascDesc);
        // 2.建立針對Shop的比較器工廠,生成對應的比較器
        ComparatorFactory<Shop> comparatorFactory = new ShopComparatorFactory();
        Comparator<Shop> shopComparator = comparatorFactory.createComparator(se, os);
        // 3.排序
        Collections.sort(shopList , shopComparator);
    	
    	return shopList;
    }
    

    這樣,當客戶端的排序方式變了,這一部分的程式碼就不需要有太大的變化了。別的產品需要排序的時候,只需要編寫對應的列舉和新的比較器工廠就可以了,變化的只是簡單工廠那一部分的邏輯,對於呼叫方基本是透明的。體現出來的思想就是:隱藏後端業務邏輯,暴露出簡單的介面。