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