分享一波:程序員賺外快-必看的巔峰干貨
什么是策略模式
定義一系列的算法,并將每一個算法單獨進行封裝,而且使它們可以相互替換,從而達到傳遞不同參數而執行不同算法的結果。
策略模式讓算法獨立于使用它的客戶而獨立變化
策略模式應用場景
策略模式的用意是針對一組算法或邏輯,將每一個算法或邏輯封裝到具有共同接口的獨立的類中,從而使得它們之間可以相互替換。策略模式使得算法或邏輯可以在不影響到客戶端的情況下發生變化。說到策略模式就不得不提及OCP(Open Closed Principle) 開閉原則,即對擴展開放,對修改關閉。策略模式的出現很好地詮釋了開閉原則,有效地減少了分支語句。
基本案例
案例模擬購物車場景,根據不同的會員級別執行不同的折扣。分為初級、中級、高級會員三種。
//策略模式 定義抽象方法 所有支持公共接口
abstract class Strategy {
// 算法方法
abstract void algorithmInterface();
}
class StrategyA extends Strategy {
@Override
void algorithmInterface() {System.out.println("初級會員9.5折");}
}
class StrategyB extends Strategy {
@Override
void algorithmInterface() {System.out.println("中級會員8折");}
}
class StrategyC extends Strategy {
@Override
void algorithmInterface() {System.out.println("高級會員半價");}
}
// 使用上下文維護算法策略
class Context {
Strategy strategy;public Context(Strategy strategy) {this.strategy = strategy;
}public void algorithmInterface() {// 這里類似于代理模式,可以在方法的前后執行不同的邏輯strategy.algorithmInterface();
}
}
class ClientTestStrategy {
public static void main(String[] args) {
Context context;
context = new Context(new StrategyA());
context.algorithmInterface();
context = new Context(new StrategyB());
context.algorithmInterface();
context = new Context(new StrategyC());
context.algorithmInterface();
}
}
提高篇
在實際開發中,常常會使用到case語句,根據不同的情況執行不同的邏輯。當情況較多時,大量的case、if語句會極大地降低代碼可讀性,后期維護也極為不便,因此在實際開發中應當避免大量case語句的使用,此時,就可以使用策略模式進行代碼重構。
在我的一個項目中,需要根據前端傳遞的不同值,而對sql進行不同的拼接,最先使用的是case語句,并對case中具體的邏輯抽取了方法,代碼如下。
/**
* 獲取查詢條件
*
* @param params 參數
* @param asTable 表別名
* @param joinTables 連表
* @return
* @throws ParseException
*/
private String getWhere(Map<String, Object> params, String asTable,
Map<String, Map<String, Object>> joinTables) throws ParseException {
List expresses = Lists.newArrayList();
// 參數的key全部取出來封裝成set集合
Set cloneSet = new HashSet<>(params.keySet());
for (String key : cloneSet) {
// 遍歷key
// 值
String value;
// 存值集合
List values;
// 判斷符號
switch (getOpt(key).toLowerCase()) {
case “in”:
// 如果是in
// 將其轉換成 list
values = paramsToList(params, key);
if (values.isEmpty()) {
// 處理完之后如果為空就不處理
break;
} else {
in(params, asTable, expresses, key, values);
}
break;
case “notin”:
// 是notin,邏輯一樣
// 將其轉換成 list
values = paramsToList(params, key);
if (values.isEmpty()) {
break;
} else {
notIn(params, asTable, expresses, key, values);
}
break;
case “gt”:
// 是大于號
gt(params, asTable, expresses, key);
break;
case “lt”:
// 是小于號
lt(params, asTable, expresses, key);
break;
case “gte”:
// 是大于等于
gte(params, asTable, expresses, key);
break;
case “lte”:
// 是小于等于
lte(params, asTable, expresses, key);
break;
case “eq”:
// 是等于
eq(params, asTable, expresses, key);
break;
case “like”:
// 是like
like(params, asTable, expresses, key);
break;
case “btw”:
// 是between
value = params.get(key).toString();
// 默認只允許用這三種連接符
if (!value.contains("~") ||
!value.contains("-") ||
!value.contains("/")) {
break;
}
between(params, asTable, expresses, key, value);
break;
case “null”:
isNull(params, asTable, expresses, key);
break;
case “not”:
// 不等于
notEquals(params, asTable, expresses, key);
break;
case “match”:
match(params, asTable, expresses, key);
break;
case “join”:
//join_表名對應的實體類_操作_字段
//join_name_in_ids
joinTable(params, joinTables, key);
default:
// 處理所有的key(如果key沒有前綴,就都默認為等于)
defaultCase(asTable, expresses, key);
break;
}
}
return StringUtils.join(expresses, " and ");
}
上面代碼極不方便閱讀,后面case可能繼續增加,從而使代碼的可維護性逐漸降低。
優化思路:對于不同的case情況,每個case即為一個枚舉,而case中具體的方法則為一個策略。通過枚舉的值來決定new哪個策略類,從而執行不同的代碼邏輯。
思路很明確,就是把每個case作為一個策略,根據不同的case而執行不同的代碼。
先編寫條件策略的抽象類,具體的邏輯為抽象方法,供策略類去繼承
public abstract class BaseOptStrategy {
/*** 處理option* @param params 參數* @param asTable 表別名* @param expresses 拆分后的數據* @param key #{param.key}*/
public abstract void sqlOptHandler(Map<String, Object> params, String asTable, List<String> expresses, String key);/*** 參數轉列名。取出符號后的參數,轉下劃線** @param temp* @return*/
public static String getExpressColumn(String temp) {return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, temp.split("_")[1]);
}
}
再編寫不同的策略,繼承BaseOptStrategy,這里只用lte和eq兩個case作為舉例。
EqStrategy
@Getter
public class EqStrategy extends BaseOptStrategy {
public EqStrategy() {
}@Override
public void sqlOptHandler(Map<String, Object> params, String asTable, List<String> expresses, String key) {String value = params.get(key).toString();if (!StringUtils.isBlank(value)) {expresses.add(asTable + "." + getExpressColumn(key) + " = #{params." + key + "}");}
}
}
LteStrategy
@Getter
public class LteStrategy extends BaseOptStrategy {
public LteStrategy() {}@Override
public void sqlOptHandler(Map<String, Object> params, String asTable, List<String> expresses, String key) {String value = params.get(key).toString();if (!StringUtils.isBlank(value)) {expresses.add(asTable + "." + getExpressColumn(key) + " <= #{params." + key + "}");}
}
}
再編寫執行策略的代理類
public class StrategyProxy {
private BaseOptStrategy strategy;public StrategyProxy(BaseOptStrategy strategy) {this.strategy = strategy;
}public void sqlOptHandler(Map<String, Object> params, String asTable, List<String> expresses, String key) {strategy.sqlOptHandler(params, asTable, expresses, key);
}
}
這樣,對于每個eq和lte都有了對應的策略類,調用sqlOptHandler之前,傳入不同的BaseOptStrategy就可以執行不同的邏輯。
但是,如何判斷具體應該傳入哪種策略?這里就不使用case語句,而是使用枚舉,根據枚舉值去new不同的對象
策略枚舉代碼
@Getter
public enum OptEnums {
/**
* 通過key去new對象
*/
IN(“in”, new InStrategy()),
NOTIN(“notin”, new NotInStrategy()),
GT(“gt”, new GtStrategy()),
LT(“lt”, new LtStrategy()),
GTE(“gte”, new GteStrategy()),
LTE(“lte”, new LteStrategy()),
EQ(“eq”, new EqStrategy()),
BTW(“btw”, new BtwStrategy()),
LIKE(“like”, new LikeStrategy()),
NULL(“null”, new NullStrategy()),
NOT(“not”, new NeqStrategy()),
MATCH(“match”, new MatchStrategy()),
JOIN(“join”, new JoinStrategy()),
;
private String opt;
private BaseOptStrategy strategy;OptEnums(String opt, BaseOptStrategy strategy) {this.opt = opt;this.strategy = strategy;
}
}
使用 OptEnums optEnums = Enum.valueOf(OptEnums.class, “枚舉名稱”); 就可以獲取指定的枚舉,優化后的getWhere代碼如下
private String getWhere(Map<String, Object> params, String asTable,
Map<String, Map<String, Object>> joinTables) throws ParseException {
List expresses = Lists.newArrayList();
// 參數的key全部取出來封裝成set集合
Set cloneSet = new HashSet<>(params.keySet());
for (String key : cloneSet) {
// 遍歷key
Object value = params.get(key);
if (value != null && !"".equals(value)) {
String optName = getOpt(key).toUpperCase();
try {
OptEnums optEnums = Enum.valueOf(OptEnums.class, optName);
StrategyProxy factory = new StrategyProxy(optEnums.getStrategy());
factory.sqlOptHandler(params, asTable, expresses, key);
} catch (Exception e) {
String[] optAndExpressColumn = key.split("_");
if (optAndExpressColumn.length == 1) {
expresses.add(asTable + “.” + CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, optAndExpressColumn[0])
+ “= #{params.” + key + “}”);
}
}
}
}
return StringUtils.join(expresses, " and ");
}
try中就是對不同的策略進行處理,代碼簡潔了很多。而default語句含義為,上面的case都不匹配的情況,而在枚舉中,則對應為沒有找到枚舉的情況。在這里可以判斷是否為空而決定執行case,也可以直接像上面代碼一樣try-catch。
事實上,上面代碼依然可以優化,把catch中的代碼作為默認執行策略,這里就不做過多的贅述。
策略模式是最符合OCP原則的設計模式,通過把不同的算法進行封裝,程序去控制執行哪個代碼,大大降低了代碼的耦合性。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新