如何將不同型別的商品按它們不同的屬性進行排序(工廠方法 + 簡單工廠的綜合解決方案)
這是最近實習的時候老闆給的一個需求,具體要求:將商品按照它的進貨價,零售價,銷量等11個屬性分別進行升降序排序;將倉庫商品按照它的庫存等5個屬性進行升降序排序。
注:商品和倉庫商品沒有什麼聯絡,可以把它們認為是兩個物件:
Product
類和RepositoryProduct
類。
ps:前臺傳遞的引數是兩個狀態碼,一個表示按照哪個屬性進行排序,另一個區分升降序。
解決思路:
-
對物件集合排序可以使用
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 層就會顯得異常的臃腫,而且擴充套件性也不好,因此考慮新的解決方案。 -
分析上面的程式碼,我們發現排序所呼叫的介面是不變的,即都需要呼叫
Collections.sort
,變的是需要傳入不同的比較器,比較器有很多,馬上想到可以使用工廠模式來優化上面的業務邏輯。 -
工廠模式有三種:簡單工廠(或者說是靜態工廠)模式,工廠方法模式,抽象工廠模式,用哪種呢?
-
最終採用工廠方法 + 簡單工廠相結合的解決方案:
工廠方法模式:定義一個建立比較器物件的工廠介面或抽象類,具體的建立比較器這一動作由該抽象類的子類來實現。針對該需求的工廠方法的 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; }
這樣,當客戶端的排序方式變了,這一部分的程式碼就不需要有太大的變化了。別的產品需要排序的時候,只需要編寫對應的列舉和新的比較器工廠就可以了,變化的只是簡單工廠那一部分的邏輯,對於呼叫方基本是透明的。體現出來的思想就是:隱藏後端業務邏輯,暴露出簡單的介面。