作者:小傅哥
暖暖的春風(fēng)迎面吹扒披,桃花多多開(kāi)
小傅哥 | https://bugstack.cn
沉淀、分享圃泡、成長(zhǎng)碟案,專注于原創(chuàng)專題案例,以最易學(xué)習(xí)編程的方式分享知識(shí)颇蜡,讓自己和他人都能有所收獲价说。目前已完成的專題有;Netty4.x實(shí)戰(zhàn)專題案例风秤、用Java實(shí)現(xiàn)JVM鳖目、基于JavaAgent的全鏈路監(jiān)控、手寫RPC框架缤弦、架構(gòu)設(shè)計(jì)專題案例领迈、源碼分析等。
你用劍??碍沐、我用刀??狸捅,好的代碼都很燒,望你不吝出招累提!
一尘喝、前言
你見(jiàn)過(guò)這樣的代碼嘛?類似的呢刻恭?嗯瞧省,那么恭喜你被這個(gè)世界溫柔以待扯夭!
if else
鳍贾,并不是一個(gè)非常壞的關(guān)鍵字鞍匾,只不過(guò)有人把他用壞了。尤其在接到產(chǎn)品需求如下這樣骑科;
日期 | 需求 | 緊急程度 | 程序員(話外音) |
---|---|---|---|
星期一.早上 | 猿哥哥橡淑,老板說(shuō)要搞一下?tīng)I(yíng)銷拉拉量,給男生女生發(fā)不同的優(yōu)惠券咆爽,促活消費(fèi)梁棠。 | 很緊急,下班就要 | 行吧斗埂,也不難符糊,加下判斷就上線 |
星期二.下午 | 小哥哥,咱們上線后非常好呛凶。要讓咱們按照年輕男娄、中年、成年漾稀,不同年齡加下判斷模闲,準(zhǔn)確刺激消費(fèi)。 | 超緊急崭捍,明天就要 | 也不難尸折,加就加吧 |
星期三.晚上 | 喂,小哥哥殷蛇!睡了嗎实夹!老板說(shuō)咱們這次活動(dòng)很成功,可以不可以在細(xì)分下粒梦,把單身收擦、結(jié)婚、有娃的都加上不同判斷谍倦。這樣更能刺激用戶消費(fèi)塞赂。 | 賊緊急,最快上線昼蛀。 | 已經(jīng)意識(shí)到ifelse 越來(lái)越多了 |
星期四.凌晨 | 哇宴猾!小哥哥你們太棒了,上的真快叼旋。嘻嘻仇哆!有個(gè)小請(qǐng)求,需要調(diào)整下年齡段夫植,因?yàn)楝F(xiàn)在學(xué)生處對(duì)象的都比較早讹剔,有對(duì)象的更容易買某某某東西油讯。要改下值!辛苦辛苦延欠! | 老板陌兑,在等著呢! | 一大片的值要修改由捎,哎兔综!這么多ifelse 了 |
星期五.半夜 | 歪歪喂!巴巴狞玛,壞了软驰,怎么發(fā)的優(yōu)惠券不對(duì)了,有客訴了心肪,很多女生都來(lái)投訴锭亏。你快看看。老板硬鞍,他... | (一頭汗)慧瘤,哎,值粘錯(cuò)位置了膳凝! | 終究還是一個(gè)人扛下了所有 |
這樣的場(chǎng)景你是否有遇到過(guò)呢碑隆,那么是產(chǎn)品給你代溝里去了,還是你把項(xiàng)目帶溝里去了蹬音≈可能會(huì)覺(jué)得永部,這東西這么著急要独泞,我也沒(méi)辦法呀。其實(shí)不止你沒(méi)有辦法苔埋,是為了打下市場(chǎng)懦砂,讓每一個(gè)人都很匆忙。只有合理的評(píng)估组橄、鋪墊玉工、架設(shè)羽资,才會(huì)不斷滿足業(yè)務(wù)需求、產(chǎn)品形態(tài)的變化遵班。否則往后的路越來(lái)越難腹暖!
二汇在、場(chǎng)景
對(duì)于上面所提到的這種場(chǎng)景,在我們實(shí)際開(kāi)發(fā)中是經(jīng)常會(huì)遇到的微服。尤其是在一些趾疚;營(yíng)銷缨历、風(fēng)控以蕴、人群等,各種用戶信息決策樹(shù)關(guān)系時(shí)辛孵,都會(huì)出現(xiàn)這樣的業(yè)務(wù)邏輯丛肮。而且對(duì)于一些較大場(chǎng)景是肯定不會(huì)直接硬編碼if else
,因?yàn)樘y以維護(hù)魄缚。當(dāng)然除非你這東西就寫一次用一次宝与,下次不用了那無(wú)所謂。
接下來(lái)我們把上面的場(chǎng)景進(jìn)行轉(zhuǎn)換一種樹(shù)結(jié)構(gòu)圖冶匹,依次來(lái)體現(xiàn)出這個(gè)需求的全貌习劫,如下;
- 從上圖我們看到嚼隘,把產(chǎn)品一周提的需求匯總后就一張樹(shù)形的決策流诽里。每一種不同的因子都可以導(dǎo)致結(jié)果不同的走向。
- 而如果這個(gè)產(chǎn)品整體的內(nèi)容飞蛹,從一點(diǎn)點(diǎn)交給你谤狡,和一整套交給你,你所做出來(lái)的研發(fā)設(shè)計(jì)是不同的卧檐。當(dāng)然也有相同的墓懂,因?yàn)檫€有一部分很有遠(yuǎn)見(jiàn)的程序員,他們常年踩坑霉囚!而這份相同的高等的設(shè)計(jì)捕仔,就是踩坑踩出來(lái)的經(jīng)驗(yàn)。
- 那么盈罐,除了
if else
你還能在自己掌握的技術(shù)棧中想到什么解決方案嗎榜跌?接下來(lái),我們會(huì)寫出兩種實(shí)現(xiàn)方式暖呕,用作比對(duì)斜做。
三、if湾揽、else編碼
@Test
public void test_ifelse() {
Result result = null;
if ("男".equals(policy.getSex())) {
if (policy.getAge() < 18) {
if (policy.getUserSingle()) {
result = Result.buildResult("A", "紅色A");
} else {
result = Result.buildResult("B", "紅色B");
}
} else if (policy.getAge() >= 18 && policy.getAge() <= 30) {
if (policy.getUserMarry()) {
result = Result.buildResult("C", "紅色C");
} else {
result = Result.buildResult("D", "紅色D");
}
} else if (policy.getAge() > 30) {
if (policy.getUserParenting()) {
result = Result.buildResult("E", "紅色E");
} else {
result = Result.buildResult("F", "紅色F");
}
}
} else if ("女".equals(policy.getSex())) {
if (policy.getAge() < 18) {
if (policy.getUserSingle()) {
result = Result.buildResult("A", "黃色A");
} else {
result = Result.buildResult("B", "黃色B");
}
} else if (policy.getAge() >= 18 && policy.getAge() <= 30) {
if (policy.getUserMarry()) {
result = Result.buildResult("C", "黃色C");
} else {
result = Result.buildResult("D", "黃色D");
}
} else if (policy.getAge() > 30) {
if (policy.getUserParenting()) {
result = Result.buildResult("E", "黃色E");
} else {
result = Result.buildResult("F", "黃色F");
}
}
}
System.out.println("決策結(jié)果(IfElse):" + result);
}
- 這就不用說(shuō)了瓤逼,只要會(huì)
if else
寫出來(lái)還是沒(méi)問(wèn)題的笼吟,只不過(guò)寫錯(cuò)不錯(cuò)就不一定了,畢竟一層套一層霸旗。這還算少的贷帮!
四、規(guī)則引擎Drools
關(guān)于規(guī)則引擎簡(jiǎn)單說(shuō)呢就是诱告,將你業(yè)務(wù)邏輯中那些行為規(guī)則流程變化的部分撵枢,分離出來(lái)。交給單獨(dú)的規(guī)則引擎進(jìn)行處理精居。最終你只需要按照約定提供配置和入?yún)⒊荩涂梢赃_(dá)到規(guī)則的執(zhí)行結(jié)果。
Drools(JBoss Rules )具有一個(gè)易于訪問(wèn)企業(yè)策略靴姿、易于調(diào)整以及易于管理的開(kāi)源業(yè)務(wù)規(guī)則引擎沃但,符合業(yè)內(nèi)標(biāo)準(zhǔn),速度快佛吓、效率高宵晚。業(yè)務(wù)分析師或?qū)徍巳藛T可以利用它輕松查看業(yè)務(wù)規(guī)則,從而檢驗(yàn)是否已編碼的規(guī)則執(zhí)行了所需的業(yè)務(wù)規(guī)則维雇。
上去就是一巴掌淤刃,然后在問(wèn)為什么。好吱型,先來(lái)把上面的代碼用Drools
處理下逸贾,之后再解釋。
1. 環(huán)境配置
- jdk1.8.0
- idea + maven3.x
- drools 7.32.0.Final
- 案例源碼下載唁影,關(guān)注公眾號(hào):bugstack蟲(chóng)洞棧 回復(fù):
源碼獲取
- 可視化流程圖解決方案耕陷;flowdiagram.itstack.org
2. 工程結(jié)構(gòu)
itstack-demo-drools-02
└── src
├── main
│ ├── java
│ │ └── org.itstack.demo
│ │ ├── model
│ │ │ └── Policy.java
│ │ └── Result.java
│ ├── resources
│ │ ├── META-INF
│ │ │ └── kmodule.xml
│ │ └── rules
│ │ └── tree.drl
│ └── webapp
│ └── index.html
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
- 關(guān)于案例中出現(xiàn)的代碼,可以通過(guò)關(guān)注公眾號(hào)獲染萆颉:bugstack蟲(chóng)洞棧哟沫,回復(fù)關(guān)鍵字<獲取源碼>
- 以上是我們關(guān)于使用
Drools
規(guī)則引擎的的基本工程,規(guī)則引擎使用的方式并不復(fù)雜锌介,只要按照約定的方式進(jìn)行設(shè)置即可嗜诀。
3. 代碼講解
Policy.java & 定義決策屬性,同時(shí)這也是Fact對(duì)象
public class Policy {
private String sex; // 性別孔祸;男隆敢、女
private Integer age; // 年齡
private Boolean userSingle; // 單身;是/否
private Boolean userMarry; // 結(jié)婚崔慧;是/否
private Boolean userParenting; // 育兒拂蝎;是/否
...get/set
}
Result.java & 定義結(jié)果輸出
public class Result {
private String code;
private String info;
}
META-INF/kmodule.xml & 配置文件
<?xml version="1.0" encoding="utf-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="rules">
<ksession name="all-rules"/>
</kbase>
</kmodule>
- kmodule 可以包含多個(gè)
kbase
,分別對(duì)應(yīng)drl
的規(guī)則文件 -
kbase name="rules"
惶室,name名稱需要保證唯一 - kbase下面可以有一個(gè)或多個(gè)ksession温自,ksession的name屬性必須設(shè)置玄货,且必須唯一
- kbase的default屬性,表示當(dāng)前KieBase是不是默認(rèn)的悼泌,如果是默認(rèn)的則不用名稱就可以查找到該KieBase松捉,但每個(gè)module最多只能有一個(gè)默認(rèn)KieBase
rules/tree.drl & 規(guī)則文件
package rules;
import org.itstack.demo.model.Policy
import org.itstack.demo.Result;
global org.itstack.demo.Result res;
rule "紅A"
when
Policy(sex == "男", age < 18, userSingle)
then
res.setResult("A","紅色A");
end
rule "紅B"
when
Policy(sex == "男", age < 18, !userSingle)
then
res.setResult("B","紅色B");
end
rule "紅C"
when
Policy(sex == "男", age >= 18, age <= 30, userMarry)
then
res.setResult("C","紅色C");
end
rule "紅D"
when
Policy(sex == "男", age >= 18, age <= 30, !userMarry)
then
res.setResult("D","紅色D");
end
rule "紅E"
when
Policy(sex == "男", age > 30, userParenting)
then
res.setResult("E","紅色E");
end
rule "紅F"
when
Policy(sex == "男", age > 30, !userParenting)
then
res.setResult("F","紅色F");
end
rule "黃A"
when
Policy(sex == "女", age < 18, userSingle)
then
res.setResult("A","黃色A");
end
rule "黃B"
when
Policy(sex == "女", age < 18, !userSingle)
then
res.setResult("B","黃色B");
end
rule "黃C"
when
Policy(sex == "女", age >= 18, age <= 30, userMarry)
then
res.setResult("C","黃色C");
end
rule "黃D"
when
Policy(sex == "女", age >= 18, age <= 30, !userMarry)
then
res.setResult("D","黃色D");
end
rule "黃E"
when
Policy(sex == "女", age > 30, userParenting)
then
res.setResult("E","黃色E");
end
rule "黃F"
when
Policy(sex == "女", age > 30, !userParenting)
then
res.setResult("F","黃色F");
end
- rule 規(guī)則名稱、when then end 一套組合拳馆里,什么條件下輸出什么結(jié)果
-
sex == "女", age > 30, !userParenting
隘世,英文逗號(hào)隔開(kāi)的是and的條件,相當(dāng)你的且鸠踪。當(dāng)不完全是丙者,因?yàn)樵诤罄m(xù)處理中,逗號(hào)的處理邏輯在drools是有優(yōu)化的慢哈。 - then中處理結(jié)果蔓钟,將結(jié)果信息返回永票,這個(gè)結(jié)果使用是我們?cè)O(shè)置的一個(gè)
global
全局引入卵贱。最后結(jié)尾end關(guān)鍵字。 - 也許你會(huì)覺(jué)得這不是很像你的
if else
嗎侣集。但千萬(wàn)不要這么覺(jué)得键俱,因?yàn)檫@只是冰山一角。而且我們前面截圖一個(gè)樹(shù)形結(jié)構(gòu)世分,而這個(gè)屬性結(jié)構(gòu)是可以自動(dòng)化生成DRL
規(guī)則文件的编振。
4. 測(cè)試執(zhí)行
ApiTest.java & 單元測(cè)試中會(huì)設(shè)置Drools的啟動(dòng)過(guò)程
public class ApiTest {
private KieContainer kieContainer;
private Policy policy;
@Before
public void init() {
// 構(gòu)建KieServices
KieServices kieServices = KieServices.Factory.get();
kieContainer = kieServices.getKieClasspathContainer();
policy = new Policy();
policy.setSex("男");
policy.setAge(16);
policy.setUserSingle(false);
policy.setUserMarry(false);
policy.setUserParenting(false);
System.out.println("決策請(qǐng)求:" + JSON.toJSONString(policy));
}
@Test
public void test_drools() {
KieSession kieSession = kieContainer.newKieSession("all-rules");
kieSession.insert(policy);
Result result = new Result();
kieSession.setGlobal("res", result);
int count = kieSession.fireAllRules();
System.out.println("Fire rule(s):" + count);
System.out.println("決策結(jié)果(Drools):" + result);
kieSession.dispose();
}
}
init() 初始化
- 在初始化方法中,構(gòu)建
KieServices.Factory.get();
臭埋,這個(gè)過(guò)程是比較耗費(fèi)資源踪央,實(shí)際業(yè)務(wù)使用中也不會(huì)頻繁的構(gòu)建。 - 從
KieServices
中獲取KieContainer
瓢阴,用于給定KieModule的所有kiebase的容器畅蹂。 - 設(shè)置FACT對(duì)象,其實(shí)就是你的決策對(duì)象的一些條件值荣恐。
test_drools() 執(zhí)行規(guī)則
- 獲取kmodule.xml中配置中名稱為all-rules的session液斜,默認(rèn)為有狀態(tài)的。
- 設(shè)置決策對(duì)象
kieSession.insert(policy);
- 設(shè)置全局對(duì)象
kieSession.setGlobal("res", result);
叠穆,用于最終把結(jié)果輸出 - 開(kāi)始執(zhí)行規(guī)則
kieSession.fireAllRules()
- 最終輸出結(jié)果少漆,到最后釋放資源
kieSession.dispose()
測(cè)試結(jié)果
決策請(qǐng)求:{"age":16,"sex":"男","userMarry":false,"userParenting":false,"userSingle":false}
Fire rule(s):1
決策結(jié)果(Drools):B|紅色B
- 在測(cè)試過(guò)程中可以嘗試修改入?yún)⑿畔ⅲ源蓑?yàn)證不同的結(jié)果硼被。
五示损、Rete 算法了解
Drools 是用 Java 語(yǔ)言編寫的開(kāi)放源碼規(guī)則引擎,使用 Rete 算法對(duì)所編寫的規(guī)則求值嚷硫。Drools 允許使用聲明方式表達(dá)業(yè)務(wù)邏輯检访《嵋纾可以使用非 XML 的本地語(yǔ)言編寫規(guī)則,從而便于學(xué)習(xí)和理解烛谊。并且风响,還可以將 Java 代碼直接嵌入到規(guī)則文件中,這令 Drools 的學(xué)習(xí)更加吸引人丹禀。
好状勤!那么這樣你就知道,Drools的核心內(nèi)容是關(guān)于 Rete 算法的實(shí)現(xiàn)双泪。接下來(lái)我們?cè)賮?lái)了解下 Rete持搜。
為了解決生產(chǎn)式推理引擎效率底下的問(wèn)題,F(xiàn)orgy 在1979年提出 Rete 算法焙矛,作為生產(chǎn)式系統(tǒng)的高效模式匹配算法祝辣。Rete 算法的初衷是:利用規(guī)則之間各個(gè)域的公用部分減少規(guī)則存儲(chǔ),同時(shí)保存匹配過(guò)程的臨時(shí)結(jié)果以加快匹配速度烁登。為了達(dá)到這種效果抱完,算法將規(guī)則拆分,其中每個(gè)條件單元作為基本單位(節(jié)點(diǎn))連接成一個(gè)數(shù)據(jù)辨別網(wǎng)絡(luò)蟆盹,然后將事實(shí)經(jīng)過(guò)網(wǎng)絡(luò)篩選并傳播孩灯,最終所有條件都有事實(shí)匹配的規(guī)則被激活。
Rete 算法自從 1979 年提出以來(lái)逾滥,已經(jīng)經(jīng)歷過(guò)各種改進(jìn)與推廣峰档。除了對(duì)自身規(guī)則網(wǎng)絡(luò)結(jié)構(gòu)的優(yōu)化外,對(duì)一些功能擴(kuò)展如模糊推理寨昙、事件推理讥巡、并行化等也有很多研究。
1. 結(jié)構(gòu)優(yōu)化
-
混合邏輯符的處理
邏輯操作符(operators)是指注入and舔哪、or欢顷、not等,的邏輯運(yùn)算符處理尸红。
-
規(guī)則前件的重排序
規(guī)則前件順序是指規(guī)則體哦啊見(jiàn)中的各個(gè)約束的排列順序吱涉,它決定了條件鏈接操作的執(zhí)行順序,影響中間結(jié)果的大小外里,是決定規(guī)則匹配效率的關(guān)鍵因素怎爵。
-
索引方法
索引方法是指對(duì) Rete 網(wǎng)絡(luò)的節(jié)點(diǎn)建立當(dāng)前節(jié)點(diǎn)對(duì)后繼 的索引,在事實(shí)斷言時(shí)可以通過(guò)索引快速找到對(duì)應(yīng)的后繼節(jié) 點(diǎn)而無(wú)需逐個(gè)查找盅蝗。
2. 功能擴(kuò)展
處理其他邏輯
Rete 最初只是用于處理一階布爾邏輯鳖链,目前有很多 Rete 的擴(kuò)展被用來(lái)處理其他邏輯。帶時(shí)間信息的事件處理
Rete 通過(guò)事實(shí)來(lái)表達(dá)當(dāng)前狀態(tài),但是很多應(yīng)用包括一些事件流中的時(shí)間芙委,在事件并行執(zhí)行中起到關(guān)鍵作用逞敷。所以需要 Rete 算法對(duì)這些信息進(jìn)行處理。
3. 特殊數(shù)據(jù)的推理
-
瑕疵數(shù)據(jù)與不確定性推理
- 不正確性
- 不精準(zhǔn)性
- 不一致性
-
快速變化數(shù)據(jù)與機(jī)器學(xué)習(xí)
除了數(shù)據(jù)瑕疵灌侣,對(duì)于變化劇烈的數(shù)據(jù)也成為Rete算法需要解決的問(wèn)題推捐。
4. 并行化
Rete 算法從提出至今,性能提升問(wèn)題一直是研究重點(diǎn)侧啼。多核多處理器問(wèn)世后牛柒,將推理過(guò)程分配到不同機(jī)器上并行處理成為一種常見(jiàn)的效率提升方法
六、總結(jié)
- 優(yōu)秀的產(chǎn)品痊乾、優(yōu)秀的研發(fā)皮壁,從來(lái)不只是傳話筒也不是工具機(jī)器。而是有靈魂的工匠哪审,需要有謀有段蛾魄,決策、遠(yuǎn)見(jiàn)湿滓。
- Drools的使用還不止是這一點(diǎn)滴须,他還豐富的很,我們本章節(jié)主要是一個(gè)開(kāi)篇茉稠,后續(xù)會(huì)繼續(xù)完善描馅。關(guān)于工程代碼可以關(guān)注公眾號(hào)(bugstack蟲(chóng)洞棧)進(jìn)行獲取。
- 只有你的技術(shù)識(shí)棧足夠的全面而线,才能讓你在遇到一個(gè)問(wèn)題的時(shí)候,有N中的方案恋日。但學(xué)習(xí)一定是自己的事膀篮,無(wú)論是忙與閑,都要讓自己充充電岂膳。娛樂(lè)不是不可以誓竿,只不過(guò)要適當(dāng)?shù)目刂葡伦约骸?code>如果你控制不住自己,就會(huì)有別人控制你