作者:小傅哥
暖暖的春風迎面吹,桃花多多開
小傅哥 | https://bugstack.cn
沉淀、分享、成長,專注于原創(chuàng)專題案例,以最易學習編程的方式分享知識,讓自己和他人都能有所收獲。目前已完成的專題有;Netty4.x實戰(zhàn)專題案例、用Java實現(xiàn)JVM、基于JavaAgent的全鏈路監(jiān)控、手寫RPC框架、架構設計專題案例、源碼分析等。
你用劍??、我用刀??,好的代碼都很燒,望你不吝出招!
一、前言
你見過這樣的代碼嘛?類似的呢?嗯,那么恭喜你被這個世界溫柔以待!
if else,并不是一個非常壞的關鍵字,只不過有人把他用壞了。尤其在接到產(chǎn)品需求如下這樣;
| 日期 | 需求 | 緊急程度 | 程序員(話外音) |
|---|---|---|---|
| 星期一.早上 | 猿哥哥,老板說要搞一下營銷拉拉量,給男生女生發(fā)不同的優(yōu)惠券,促活消費。 | 很緊急,下班就要 | 行吧,也不難,加下判斷就上線 |
| 星期二.下午 | 小哥哥,咱們上線后非常好。要讓咱們按照年輕、中年、成年,不同年齡加下判斷,準確刺激消費。 | 超緊急,明天就要 | 也不難,加就加吧 |
| 星期三.晚上 | 喂,小哥哥!睡了嗎!老板說咱們這次活動很成功,可以不可以在細分下,把單身、結婚、有娃的都加上不同判斷。這樣更能刺激用戶消費。 | 賊緊急,最快上線。 | 已經(jīng)意識到ifelse越來越多了 |
| 星期四.凌晨 | 哇!小哥哥你們太棒了,上的真快。嘻嘻!有個小請求,需要調(diào)整下年齡段,因為現(xiàn)在學生處對象的都比較早,有對象的更容易買某某某東西。要改下值!辛苦辛苦! | 老板,在等著呢! | 一大片的值要修改,哎!這么多ifelse了 |
| 星期五.半夜 | 歪歪喂!巴巴,壞了,怎么發(fā)的優(yōu)惠券不對了,有客訴了,很多女生都來投訴。你快看看。老板,他... | (一頭汗),哎,值粘錯位置了! | 終究還是一個人扛下了所有 |
這樣的場景你是否有遇到過呢,那么是產(chǎn)品給你代溝里去了,還是你把項目帶溝里去了??赡軙X得,這東西這么著急要,我也沒辦法呀。其實不止你沒有辦法,是為了打下市場,讓每一個人都很匆忙。只有合理的評估、鋪墊、架設,才會不斷滿足業(yè)務需求、產(chǎn)品形態(tài)的變化。否則往后的路越來越難!
二、場景
對于上面所提到的這種場景,在我們實際開發(fā)中是經(jīng)常會遇到的。尤其是在一些;營銷、風控、人群等,各種用戶信息決策樹關系時,都會出現(xiàn)這樣的業(yè)務邏輯。而且對于一些較大場景是肯定不會直接硬編碼if else,因為太難以維護。當然除非你這東西就寫一次用一次,下次不用了那無所謂。
接下來我們把上面的場景進行轉換一種樹結構圖,依次來體現(xiàn)出這個需求的全貌,如下;
- 從上圖我們看到,把產(chǎn)品一周提的需求匯總后就一張樹形的決策流。每一種不同的因子都可以導致結果不同的走向。
- 而如果這個產(chǎn)品整體的內(nèi)容,從一點點交給你,和一整套交給你,你所做出來的研發(fā)設計是不同的。當然也有相同的,因為還有一部分很有遠見的程序員,他們常年踩坑!而這份相同的高等的設計,就是踩坑踩出來的經(jīng)驗。
- 那么,除了
if else你還能在自己掌握的技術棧中想到什么解決方案嗎?接下來,我們會寫出兩種實現(xiàn)方式,用作比對。
三、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("決策結果(IfElse):" + result);
}
- 這就不用說了,只要會
if else寫出來還是沒問題的,只不過寫錯不錯就不一定了,畢竟一層套一層。這還算少的!
四、規(guī)則引擎Drools
關于規(guī)則引擎簡單說呢就是,將你業(yè)務邏輯中那些行為規(guī)則流程變化的部分,分離出來。交給單獨的規(guī)則引擎進行處理。最終你只需要按照約定提供配置和入?yún)?,就可以達到規(guī)則的執(zhí)行結果。
Drools(JBoss Rules )具有一個易于訪問企業(yè)策略、易于調(diào)整以及易于管理的開源業(yè)務規(guī)則引擎,符合業(yè)內(nèi)標準,速度快、效率高。業(yè)務分析師或審核人員可以利用它輕松查看業(yè)務規(guī)則,從而檢驗是否已編碼的規(guī)則執(zhí)行了所需的業(yè)務規(guī)則。
上去就是一巴掌,然后在問為什么。好,先來把上面的代碼用Drools處理下,之后再解釋。
1. 環(huán)境配置
- jdk1.8.0
- idea + maven3.x
- drools 7.32.0.Final
- 案例源碼下載,關注公眾號:bugstack蟲洞棧 回復:
源碼獲取 - 可視化流程圖解決方案;flowdiagram.itstack.org
2. 工程結構
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
- 關于案例中出現(xiàn)的代碼,可以通過關注公眾號獲取:bugstack蟲洞棧,回復關鍵字<獲取源碼>
- 以上是我們關于使用
Drools規(guī)則引擎的的基本工程,規(guī)則引擎使用的方式并不復雜,只要按照約定的方式進行設置即可。
3. 代碼講解
Policy.java & 定義決策屬性,同時這也是Fact對象
public class Policy {
private String sex; // 性別;男、女
private Integer age; // 年齡
private Boolean userSingle; // 單身;是/否
private Boolean userMarry; // 結婚;是/否
private Boolean userParenting; // 育兒;是/否
...get/set
}
Result.java & 定義結果輸出
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 可以包含多個
kbase,分別對應drl的規(guī)則文件 -
kbase name="rules",name名稱需要保證唯一 - kbase下面可以有一個或多個ksession,ksession的name屬性必須設置,且必須唯一
- kbase的default屬性,表示當前KieBase是不是默認的,如果是默認的則不用名稱就可以查找到該KieBase,但每個module最多只能有一個默認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 一套組合拳,什么條件下輸出什么結果
-
sex == "女", age > 30, !userParenting,英文逗號隔開的是and的條件,相當你的且。當不完全是,因為在后續(xù)處理中,逗號的處理邏輯在drools是有優(yōu)化的。 - then中處理結果,將結果信息返回,這個結果使用是我們設置的一個
global全局引入。最后結尾end關鍵字。 - 也許你會覺得這不是很像你的
if else嗎。但千萬不要這么覺得,因為這只是冰山一角。而且我們前面截圖一個樹形結構,而這個屬性結構是可以自動化生成DRL規(guī)則文件的。
4. 測試執(zhí)行
ApiTest.java & 單元測試中會設置Drools的啟動過程
public class ApiTest {
private KieContainer kieContainer;
private Policy policy;
@Before
public void init() {
// 構建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("決策請求:" + 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("決策結果(Drools):" + result);
kieSession.dispose();
}
}
init() 初始化
- 在初始化方法中,構建
KieServices.Factory.get();,這個過程是比較耗費資源,實際業(yè)務使用中也不會頻繁的構建。 - 從
KieServices中獲取KieContainer,用于給定KieModule的所有kiebase的容器。 - 設置FACT對象,其實就是你的決策對象的一些條件值。
test_drools() 執(zhí)行規(guī)則
- 獲取kmodule.xml中配置中名稱為all-rules的session,默認為有狀態(tài)的。
- 設置決策對象
kieSession.insert(policy); - 設置全局對象
kieSession.setGlobal("res", result);,用于最終把結果輸出 - 開始執(zhí)行規(guī)則
kieSession.fireAllRules() - 最終輸出結果,到最后釋放資源
kieSession.dispose()
測試結果
決策請求:{"age":16,"sex":"男","userMarry":false,"userParenting":false,"userSingle":false}
Fire rule(s):1
決策結果(Drools):B|紅色B
- 在測試過程中可以嘗試修改入?yún)⑿畔?,以此驗證不同的結果。
五、Rete 算法了解
Drools 是用 Java 語言編寫的開放源碼規(guī)則引擎,使用 Rete 算法對所編寫的規(guī)則求值。Drools 允許使用聲明方式表達業(yè)務邏輯??梢允褂梅?XML 的本地語言編寫規(guī)則,從而便于學習和理解。并且,還可以將 Java 代碼直接嵌入到規(guī)則文件中,這令 Drools 的學習更加吸引人。
好!那么這樣你就知道,Drools的核心內(nèi)容是關于 Rete 算法的實現(xiàn)。接下來我們再來了解下 Rete。
為了解決生產(chǎn)式推理引擎效率底下的問題,F(xiàn)orgy 在1979年提出 Rete 算法,作為生產(chǎn)式系統(tǒng)的高效模式匹配算法。Rete 算法的初衷是:利用規(guī)則之間各個域的公用部分減少規(guī)則存儲,同時保存匹配過程的臨時結果以加快匹配速度。為了達到這種效果,算法將規(guī)則拆分,其中每個條件單元作為基本單位(節(jié)點)連接成一個數(shù)據(jù)辨別網(wǎng)絡,然后將事實經(jīng)過網(wǎng)絡篩選并傳播,最終所有條件都有事實匹配的規(guī)則被激活。
Rete 算法自從 1979 年提出以來,已經(jīng)經(jīng)歷過各種改進與推廣。除了對自身規(guī)則網(wǎng)絡結構的優(yōu)化外,對一些功能擴展如模糊推理、事件推理、并行化等也有很多研究。
1. 結構優(yōu)化
-
混合邏輯符的處理
邏輯操作符(operators)是指注入and、or、not等,的邏輯運算符處理。
-
規(guī)則前件的重排序
規(guī)則前件順序是指規(guī)則體哦啊見中的各個約束的排列順序,它決定了條件鏈接操作的執(zhí)行順序,影響中間結果的大小,是決定規(guī)則匹配效率的關鍵因素。
-
索引方法
索引方法是指對 Rete 網(wǎng)絡的節(jié)點建立當前節(jié)點對后繼 的索引,在事實斷言時可以通過索引快速找到對應的后繼節(jié) 點而無需逐個查找。
2. 功能擴展
處理其他邏輯
Rete 最初只是用于處理一階布爾邏輯,目前有很多 Rete 的擴展被用來處理其他邏輯。帶時間信息的事件處理
Rete 通過事實來表達當前狀態(tài),但是很多應用包括一些事件流中的時間,在事件并行執(zhí)行中起到關鍵作用。所以需要 Rete 算法對這些信息進行處理。
3. 特殊數(shù)據(jù)的推理
-
瑕疵數(shù)據(jù)與不確定性推理
- 不正確性
- 不精準性
- 不一致性
-
快速變化數(shù)據(jù)與機器學習
除了數(shù)據(jù)瑕疵,對于變化劇烈的數(shù)據(jù)也成為Rete算法需要解決的問題。
4. 并行化
Rete 算法從提出至今,性能提升問題一直是研究重點。多核多處理器問世后,將推理過程分配到不同機器上并行處理成為一種常見的效率提升方法
六、總結
- 優(yōu)秀的產(chǎn)品、優(yōu)秀的研發(fā),從來不只是傳話筒也不是工具機器。而是有靈魂的工匠,需要有謀有段,決策、遠見。
- Drools的使用還不止是這一點,他還豐富的很,我們本章節(jié)主要是一個開篇,后續(xù)會繼續(xù)完善。關于工程代碼可以關注公眾號(bugstack蟲洞棧)進行獲取。
- 只有你的技術識棧足夠的全面,才能讓你在遇到一個問題的時候,有N中的方案。但學習一定是自己的事,無論是忙與閑,都要讓自己充充電。娛樂不是不可以,只不過要適當?shù)目刂葡伦约骸?code>如果你控制不住自己,就會有別人控制你