背景
有這樣一種需求,要在一個列表的數(shù)據(jù)中進行篩選.篩選規(guī)則和篩選優(yōu)先級時常改變(不得不吐槽產(chǎn)品同學才華橫溢,富有創(chuàng)意).如果不采取合適的方案去應(yīng)對,而且你的代碼功力又不是很強,那很可能每周加班是你的宿命了.
場景
假設(shè)現(xiàn)在有一個產(chǎn)品類,屬性如下,有類型,有單價,有讓利三個字段.
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Product {
private String type;
private String price;
private String benefit;
}
產(chǎn)品類型有AB兩種,產(chǎn)品A的優(yōu)先級大于B,當產(chǎn)品的優(yōu)先級一致時,價格低的優(yōu)先級更高,當價格一致時,讓利大的優(yōu)先級更高.現(xiàn)在給你一個列表的數(shù)據(jù),你要篩選出優(yōu)先級最高的數(shù)據(jù),如果有多個,就取第一個.我們看看有幾種解決方案.
方案一:硬擼
public class Demo01 {
public static void main(String[] args) {
Product p1 = new Product("A", 10, 5);
Product p2 = new Product("A", 10, 6);
Product p3 = new Product("B", 10, 6);
Product p4 = new Product("B", 9, 6);
Product p5 = new Product("A", 10, 6);
List<Product> productList = Lists.newArrayList(p1, p2, p3, p4, p5);
Product resultProduct = new Product("B", Integer.MAX_VALUE, Integer.MAX_VALUE);
for (Product product : productList) {
// A 和 B 比較.A更小
if (product.getType().compareTo(resultProduct.getType()) < 0) {
resultProduct = product;
continue;
}
if (product.getType().equals(resultProduct.getType())) {
// 選擇價格低的
if (product.getPrice() < resultProduct.getPrice()) {
resultProduct = product;
continue;
}
if (product.getPrice().intValue() == resultProduct.getPrice().intValue()) {
// 選擇讓利大的
if (product.getBenefit() > resultProduct.getBenefit()) {
resultProduct = product;
}
}
}
}
System.out.println(resultProduct);
}
}
在這種僅僅只有三個篩選條件的情況,這種方式的代碼已經(jīng)十分臃腫,而且,如果維護這份代碼,在需求頻繁更改的情況下是極其容易出錯的.更何況生產(chǎn)環(huán)境只會更復雜.當然你可以選擇把他們抽成一個方法.然后寫單元測試.但是后期維護工作依然巨大.
方案二: 使用List的sort方法進行排序
使用List.sort方法.傳入Compartor對象,隨后取出排序后的第一個元素.代碼如下:
public class Demo02 {
public static void main(String[] args) {
Product p1 = new Product("A", 10, 5);
Product p2 = new Product("A", 10, 6);
Product p3 = new Product("B", 10, 6);
Product p4 = new Product("B", 9, 6);
Product p5 = new Product("A", 10, 6);
List<Product> productList = Lists.newArrayList(p1, p2, p3, p4, p5);
productList.sort((pro1, pro2) -> {
if (pro1.getType().compareTo(pro2.getType()) < 0) {
return -1;
}
if (pro1.getType().equals(pro2.getType())) {
if (pro1.getPrice() < pro2.getPrice()) {
return -1;
}
if (pro1.getPrice().intValue() == pro2.getPrice().intValue()) {
return pro2.getBenefit() - pro1.getBenefit();
}
return 1;
}
return 1;
});
System.out.println(productList.get(0));
}
}
這種方式本質(zhì)上和上面的方案一差不多,應(yīng)對變化的能力差.但也是這種情況下常見的解決方案.
方案三:責任鏈
上面的兩種方案,本質(zhì)上來說都是面向過程的開發(fā)方式.在這個場景下,實際上我們就是在進行一個操作:過濾.所以我們可以給過濾這個動作建模,然后針對各種不同的過濾條件進行實現(xiàn).通過組裝各種過濾實現(xiàn)達到擴展和調(diào)整的目標.實現(xiàn)如下:
給過濾動作建模:
public interface IFilter<T> {
List<T> filter(List<T> data);
}
實現(xiàn)按類型篩選的過濾器:
public class TypeFilter implements IFilter<Product> {
private Ordering<Product> ordering = new Ordering<Product>() {
@Override
public int compare(Product product, Product t1) {
return product.getType().compareTo(t1.getType());
}
};
@Override
public List<Product> filter(List<Product> data) {
if (CollectionUtils.isEmpty(data)) return data;
if (data.size() == 1) return data;
List<Product> result = Lists.newArrayList();
List<Product> sortResult = ordering.sortedCopy(data);
String type = sortResult.get(0).getType();
for (Product product : sortResult) {
if (type.equals(product.getType())) {
result.add(product);
} else {
break;
}
}
return result;
}
}
按價格篩選的過濾器
public class PriceFilter implements IFilter<Product> {
private Ordering<Product> ordering = new Ordering<Product>() {
@Override
public int compare(Product product, Product t1) {
return product.getPrice() - t1.getPrice();
}
};
@Override
public List<Product> filter(List<Product> data) {
if (CollectionUtils.isEmpty(data)) return data;
if (data.size() == 1) return data;
List<Product> result = Lists.newArrayList();
List<Product> sortResult = ordering.sortedCopy(data);
int price = sortResult.get(0).getPrice();
for (Product product : sortResult) {
if (price == product.getPrice()) {
result.add(product);
} else {
break;
}
}
return result;
}
}
按讓利篩選的過濾器
public class BenefitFilter implements IFilter<Product> {
private Ordering<Product> ordering = new Ordering<Product>() {
@Override
public int compare(Product product, Product t1) {
return t1.getBenefit() - product.getBenefit();
}
};
@Override
public List<Product> filter(List<Product> data) {
if (CollectionUtils.isEmpty(data)) return data;
if (data.size() == 1) return data;
List<Product> result = Lists.newArrayList();
List<Product> sortResult = ordering.sortedCopy(data);
int benefit = sortResult.get(0).getBenefit();
for (Product product : sortResult) {
if (benefit == product.getBenefit()) {
result.add(product);
} else {
break;
}
}
return result;
}
}
客戶端代碼
public class Demo03 {
public static void main(String[] args) {
Product p1 = new Product("A", 10, 5);
Product p2 = new Product("A", 10, 6);
Product p3 = new Product("B", 10, 6);
Product p4 = new Product("B", 9, 6);
Product p5 = new Product("A", 10, 6);
List<Product> productList = Lists.newArrayList(p1, p2, p3, p4, p5);
List<IFilter<Product>> filterChain = Lists.newArrayList(
new TypeFilter(), new PriceFilter(), new BenefitFilter()
);
for(IFilter<Product> filter : filterChain){
productList = filter.filter(productList);
}
System.out.println(productList.get(0));
}
}
使用這種方式似乎代碼量更多,但是擴展性和維護性上都有了質(zhì)的提升.
加規(guī)則
只需要寫一個IFilter的實現(xiàn)類,并在FilterChain上放到一個合適的位置即可.
不加規(guī)則,調(diào)整權(quán)重
只需要修改FilterChain上的位置即可.
還能做的更好嗎?
可以針對產(chǎn)品的需求將所有的Filter實現(xiàn)類都實例化,做成配置,接入到公司的配置管理系統(tǒng).產(chǎn)品在已有規(guī)則下的權(quán)重調(diào)整只需要在配置系統(tǒng)中修改即可,不需要重新修改代碼,編譯,部署.這樣下來,你還需要加班嗎?