寫在前面
前段時間因爲工作原因接觸了Groovy,Groovy對定義DSL有很好的支持,在這裏給大家分享一些我學到的知識,希望對大家有幫助,例子有點長…
目標:積分獎勵系統
原有的消費系統:BroadbandPlus
Subsciption level
cost per month
Access points
Basic
$9.99
120
Plus
$19.99
250
Premium
$39.99
550
這些積分的作用就是可以在BroadbandPlus上消費,我們的BroadbandPlus提供了三種產品:game/movie/music.當用戶的積分耗盡後怎麼辦呢?==>充值就會變強.下面就來看看我們的價目表吧
Media
Points
Type of access
Out of plan price
Movies
New Release
40
Daily
$3.99
Other
30
Daily
$2.99
Games
New Release
30
3 days access
$2.99
Other
20
3 days access
$1.99
Songs
10
Download
$0.99
怎麼樣?
作爲一個額外付費用戶($39.99)
你每個月的消費計劃可能是這樣的
看4場新電影:160積分
玩30天遊戲:300積分
下載9首歌:90積分
而作爲一個屌絲用戶($9.99)
有一個月你突發奇想,也想體驗一把富人的生活.那麼..
看4場新電影:120積分 + $3.99
玩30天遊戲:$2.99*10 = $29.9
下載9首歌:$0.99*9 ≈ $8.9
算下來就是:$9.99+$3.99+$29.9+$8.99 ≈ $53
class BroadbandPlus {
boolean canConsume(subscriber,media){
1.用戶是否已經購買了該產品?
2.用戶已經購買了該產品,但是否已經過期?
3.如果用戶沒有購買產品,或者產品過期
檢測用戶是否有足夠的分數,如果有則扣除響應的分數並授權
return 1&&2&&3
}
void consume(subscriber,media){
subscriber正在用media
}
void purchase(subscriber,media){
付錢吧,少年!
}
void upgrade(subscriber,fromPlan,toPlan){
分數不夠了,您要升級到VVIP?
大爺.您請!!
}
//什麼?你問爲什麼沒有降級?對不起.當前版本不支持!以後也不會支持!
}
積分獎勵系統
開始
注意:此時你的視角是使用者視角
onConsume = {//用戶消費觸發
rewward("Reward Description"){//獎勵
condition{
//想獲取獎勵要達到的條件,當然是各種花$了
}
grant{
//各種好處.無非也就是多下載個電影啥的
}
}
}
注意:此時你的視角是開發者視角
binding.condition = { closure->
closure.delegate = delegate
//考慮多種條件
binding.result = (closure() && binding.result)
}
binding.grant = {closure->
closure.delegate = delegate
if(binding.result)
closure()
}
注意:此時你的視角是使用者視角
reward ( "anyOf and allOf blocks" ) {
allOf {
//所有條件都滿足時
condition { }
... more conditions
}
condition {
//單獨的條件
}
anyOf {
//滿足某一個條件時
condition {}
... more conditions
}
grant {
//我們的獎勵: )
}
}
而爲了實現它我們不得不將我們的binding做出如下改變,使用一個binding.useAnd來標識我們使用的是and邏輯還是or邏輯
注意:此時你的視角是開發者視角
binding.reward = {
spec,closure->{
closure.delegate = delegate
//在reward的最開始,我們假設結果爲true
binding.result = true
//默認的操作符爲and
binding.and = true
closure();
}
}
binding.condition = {closure->
closure.delegate = delegate
if(binding.useAnd)
binding.result = (closure() && binding.result)
else
binding.result = (clousre() || binding.result)
}
binding.allOf = {closure->
closure.delegate = delegate
//在此之前我們先將之前的result和useAnd保存起來
//這主要是考慮到嵌套的情況,要先用臨時變量保存之前的結果
def storeResult = binding.result
def storeAnd = binding.and
//將binding.result和binding.and設置爲true
binding.result = ture
binding.and = true
closure()
if(storeAnd){
binding.result = (storeResult && binding.result)
}else{
binding.result = (storeResult || binding.result)
}
binding.and = storeAnd
}
binding.anyOf = { closure ->
closure.delegate = delegate
def storeResult = binding.result
def storeAnd = binding.and
//將binding.result和binding.and設置爲false
binding.result = false
binding.and = false
closure()
if (storeAnd) {
binding.result = (storeResult && binding.result)
} else {
binding.result = (storeResult || binding.result)
}
binding.and = storeAnd
}
如果一個市場人員這樣寫:那麼最終結果就是符合條件
reward ( "nested anyOf and allOf conditions" ) {
anyOf {
allOf {
condition { true }
condition { false }
}
condition { false }
anyOf {
condition { false }
condition { true }
}
}
}
看完這一段你一定是崩潰的:
fuck!什麼玩意?我寫個false/true?
不要着急.往下看.
輕量速記的方法
binding.extend = { days ->
//當然.你先不需要知道BroadbandPlus類是什麼.請往下看
def bbPlus = new BroadbandPlus()
bbPlus.extend(binding.account, binding.media, days)
}
2.binding.points:給用戶賬戶增加點數
binding.points = { points ->
binding.account.points += points
}
media又是什麼鬼??
media顯然就是我們的產品,包括GAME,VIDEO,SONG,是我們主營業務.請往下看
集成
接下來就是最後一步了.我們通過一個service將我們寫的DSL規則和市場人員寫的獎勵規則集成起來,應用到我們的系統中去
class BroadbandPlus {
//後面會說.這個類是我們DSL的核心類
def rewards = new RewardService()
def canConsume = { account, media ->
def now = new Date()
if (account.mediaList[media]?.after(now))
return true
account.points > media.points
}
def consume = { account, media ->
// 第一次消費才獎勵
if (account.mediaList[media.title] == null) {
def now = new Date()
account.points -= media.points account.mediaList[media] = now + media.daysAccess // 應用 DSL 獎勵規則 rewards.applyRewardsOnConsume(account, media)
}
}
def extend = {account, media, days ->
if (account.mediaList[media] != null) {
account.mediaList[media] += days
}
}
}
class Account {
String subscriber
String plan
int points
double spend
Map mediaList = [:]
void addMedia (media, expiry) {
mediaList[media] = expiry
}
void extendMedia(media, length) {
mediaList[media] += length
}
Date getMediaExpiry(media) {
if(mediaList[media] != null) {
return mediaList[media]
}
}
@Override
String toString() {
String str = "subscriber:"+subscriber+"\n" +
"plan:"+plan+"\n" +
"points:"+points+"\n" +
"spend:"+spend+"\n"
mediaList.keySet().each {
str += it.title+","+mediaList.get(it)+"\n"
}
return str
}
}
class Media {
String title
String publisher
String type //類型是 VIDEO\GAME\SONG
boolean newRelease
int points
double price
int daysAccess
}
class RewardService {
static Binding baseBinding = new Binding();
static {
loadDSL(baseBinding)
loadRewardRules(baseBinding)
}
//構造 reward、condition、allOf、anyOf、grant 等核心閉包到 binding 中
//而這些 binding 構建的變量、上下文信息都可以傳入給 DSL,讓編寫 DSL
//的人員可以利用!
static void loadDSL(Binding binding) {
binding.reward = { spec, closure ->
closure.delegate = delegate
binding.result = true
binding.and = true
closure()
}
binding.condition = { closure ->
closure.delegate = delegate
if (binding.and)
binding.result = (closure() && binding.result)
else
binding.result = (closure() || binding.result)
}
binding.allOf = { closure ->
//closure.delegate = delegate
def storeResult = binding.result
def storeAnd = binding.and
binding.result = true // Starting premise is true binding.and = true
closure()
if (storeAnd) {
binding.result = (storeResult && binding.result)
} else {
binding.result = (storeResult || binding.result)
}
binding.and = storeAnd
}
binding.anyOf = { closure ->
closure.delegate = delegate
def storeResult = binding.result
def storeAnd = binding.and
binding.result = false // Starting premise is false binding.and = false
closure()
if (storeAnd) {
binding.result = (storeResult && binding.result)
} else {
binding.result = (storeResult || binding.result)
}
binding.and = storeAnd
}
binding.grant = { closure ->
closure.delegate = delegate
if (binding.result)
closure()
}
binding.extend = { days ->
def bbPlus = new BroadbandPlus()
bbPlus.extend(binding.account, binding.media, days)
}
binding.points = { points ->
binding.account.points += points
}
}
//構建一些媒體信息和條件短語
void prepareMedia(binding, media) {
binding.media = media
binding.isNewRelease = media.newRelease
binding.isVideo = (media.type == "VIDEO")
binding.isGame = (media.type == "GAME")
binding.isSong = (media.type == "SONG")
}
//初始化加載獎賞腳本,在這個腳本中,可以定義 onConsume 等 DSL
static void loadRewardRules(Binding binding) {
Binding selfBinding = new Binding()
GroovyShell shell = new GroovyShell(selfBinding)
//市場人員寫的 DSL 腳本就放在這個文件下,裏面定義 onConsume //這些個 rewards 獎勵
shell.evaluate(new File("./rewards.groovy")) //將外部 DSL 定義的消費、購買獎勵賦值
binding.onConsume = selfBinding.onConsume
}
//真正的執行方法
void apply(account, media) {
Binding binding = baseBinding;
binding.account = account
prepareMedia(binding,media)
GroovyShell shell = new GroovyShell(binding)
shell.evaluate("onConsume.delegate=this;onConsume()")
}
}
package com.tianhaollin.groovy
onConsume = {
reward ( "觀看迪斯尼的電影, 你可以獲得 25%的積分." ) {
allOf {
condition {
media.publisher == "Disney"
}
condition {
isVideo
}
}
grant {
points media.points / 4
}
}
reward ( "查看新發布的媒體,可以延長一天" ) {
condition {
isNewRelease
}
grant {
extend 1
}
}
}
account = new Account(subscriber: "Mr.tian",plan:"BASIC", points:120, spend:0.0)
terminator = new Media(title:"Terminator", type:"VIDEO",
newRelease:true, price:2.99, points:30,
daysAccess:1, publisher:"Fox")
up = new Media(title:"UP", type:"VIDEO", newRelease:true,
price:3.99, points:40, daysAccess:1,
publisher:"Disney")
account.addMedia(terminator,terminator.daysAccess)
account.addMedia(up,up.daysAccess)
def rewardService = new RewardService()
rewardService.apply(account,terminator)
rewardService.apply(account,up)
println account
總結
本文主要是通過Binding對象來實現一種簡單的DSL,在我們寫好DSL之後,每次系統啓動時候只需要從特定的地方加載reward.groovy就可以確定獎勵規則,並且在用戶消費時調用鉤子方法就可以執行特定的action(onConsume)了
我們還可以使用MetaClass的動態方法生成、groovy方法指針、方法鏈、命名參數等高級特性來定義自己更加優雅的DSL,甚至可以讓DSL像寫英語一樣簡單,比如:sendEmail from:“xiaoming”,to:“xiaohong”,context:"i love you"執行一個發送郵件的操作
本文項目地址:https://github.com/tianhaolin1991/groovyDsl 供大家參考學習,轉載請註明出處