写在前面
前段时间因为工作原因接触了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 供大家参考学习,转载请注明出处