论坛首页 Java企业应用论坛

论面向组合子程序设计方法 之 微步毂纹生

浏览 87440 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-01-07  
ajoo 写道
哎。我说跟Rete之类的有关了么?

就age0这个需求,用jaskell还是用functor都是扯淡。你要不针对rule语义作组合,死抱着apache functor就没意义。或者说apache functor对这个问题没有任何价值。jaskell对这个问题有一定价值,但是也就起一个语法封装的作用,真正的核心还得是用java对Rule接口作组合。

算了,不说了。你还是自己动手做做吧。空对空没意思。

粗略作了一下,感觉跟着实际需求走会好一些。不过整体的设计还是跟着自己的理解走,别人也不一定看得懂。
基本思路是用一个折扣表对应到一个顾客的购物清单。每一个商品对应会生成一个折扣纪录。每一个折扣策略会对一个新建的折扣表进行运算,累加的话会合成一个新的折扣表,累加的策略按照我的理解是同一种货物取最大折扣。
取最大折扣的操作同样也类似上面的累加,不过是返回最大折扣金额的那个折扣表而已。
要做到脚本解析的话,因为Functor天生是用OO来做的,这个是弱项,不过,做一个特定的脚本解析器并不是想象中那么难。
0 请登录后投票
   发表时间:2006-01-08  
哪是排它率的test case?怎么测试代码都是单条规则的?

就是这样:

rule1 = ...;
rule2 = ...;
rule3 = ...;
exclusive(rule1, rule2, rule3);;

从rule1到rule3寻找第一个适用规则。
0 请登录后投票
   发表时间:2006-01-08  
ajoo 写道
哪是排它率的test case?怎么测试代码都是单条规则的?

就是这样:

rule1 = ...;
rule2 = ...;
rule3 = ...;
exclusive(rule1, rule2, rule3);;

从rule1到rule3寻找第一个适用规则。

增加两个类即可,一个是Exclusive的函数,一个是终止条件谓词。
package jfun.rule.generator;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.functor.UnaryFunction;
import org.apache.commons.functor.UnaryPredicate;
/** 
 * representing the Excluse ations,execute every action According to the order which joins until the condition 
 * return true based on the result of previouse executed action.
 * @author   firebody
 * @since    2006-1-8-12:49:33
 */
public class Exclusive implements UnaryFunction {
	private UnaryPredicate condition;
	public Exclusive(UnaryPredicate predicate); {
		super();;
		this.condition = predicate;
	}
	private List actions = new ArrayList();;
	/**
	 * 
	 * @param action
	 * @return
	 */
	public Exclusive addExclusiveAction(UnaryFunction action);{
		this.actions.add(action);;
		return this;
	}

	public Object evaluate(Object obj); {
		for(int i=0;i<actions.size();;i++);{
			UnaryFunction action = (UnaryFunction);actions.get(i);;
			Object result = action.evaluate(obj);;
			if(condition.test(result););
				return result;
		}
		return null;
	}

}


终止条件谓词:
package jfun.rule.predicate;

import java.util.Iterator;
import java.util.Map;

import jfun.domain.Discount;
import jfun.rule.Util;

import org.apache.commons.functor.UnaryPredicate;
/** 
 * @author   firebody
 * @since    2006-1-8-13:02:58
 */
public class IsDiscounted implements UnaryPredicate {

	public IsDiscounted(); {
		super();;
		
	}
	private boolean hasDiscounts(Map discountTable);{
		for(Iterator iter = discountTable.values();.iterator();;iter.hasNext();;);{
			Discount discount = (Discount); iter.next();;
			if(discount.getDiscountPrice();>0);
				return true;
		}
		return false;
	}
	public boolean test(Object arg0); {
		Util.assertIsTypedArg(arg0,Map.class);;
		return hasDiscounts((Map);arg0);;
	}
	public static final IsDiscounted instance = new IsDiscounted();;

}


组合规则可以这样写:
	//ajoo: exclusive discounts
	public static final UnaryFunction exclusiveDiscounts();{
		UnaryFunction A = discountType_A();;
		UnaryFunction B= discountType_B();;
		UnaryFunction C = discountType_C();;
		//(A+B);
		UnaryFunction composit1 = new CompositUnaryFunction(A);.add(AddDiscounts.INSTANCE,B);;
		//exclusive((A+B);,C); ,if (A+B); matched,then return it's result,else run the c action
		Exclusive exclusive = new Exclusive(IsDiscounted.instance);;
		exclusive.addExclusiveAction(composit1);.addExclusiveAction(C);;
		return exclusive;
		
	}

原来计算折扣价格的那个计算有些bug,已经修复。 测试也加上了对这些组合的测试,主要请看DiscountTypesTest.java .
0 请登录后投票
   发表时间:2006-01-08  
这个东西除了直接计算discount,能不能做以下事情?
规则1,计算结果为客户账户拥有者的性别
规则2,计算结果为购买日期
规则3,如果性别为女性并且购买日期在3/8,折扣10%。否则规则不适用。此规则最好能重用规则1和2。
规则4,得到客户年龄
规则5,判断客户是否为成年,判断标准为女性16,男性18,结果为bool。
规则6,如果规则5返回true,应用规则3。否则规则不适用。
0 请登录后投票
   发表时间:2006-01-08  
ajoo 写道
这个东西除了直接计算discount,能不能做以下事情?
规则1,计算结果为客户账户拥有者的性别
规则2,计算结果为购买日期
规则3,如果性别为女性并且购买日期在3/8,折扣10%。否则规则不适用。此规则最好能重用规则1和2。
规则4,得到客户年龄
规则5,判断客户是否为成年,判断标准为女性16,男性18,结果为bool。
规则6,如果规则5返回true,应用规则3。否则规则不适用。

主要请看
主要代码:
jfun.rule.ajoo这个包下的。
package jfun.rule.ajoo;

import jfun.rule.discountType.DiscountExp;
import jfun.rule.function.SetDiscount;
import jfun.rule.predicate.IsAllCommodity;

import org.apache.commons.functor.BinaryPredicate;
import org.apache.commons.functor.UnaryFunction;
import org.apache.commons.functor.UnaryPredicate;
import org.apache.commons.functor.adapter.RightBoundPredicate;
import org.apache.commons.functor.core.IsEqual;
import org.apache.commons.functor.core.composite.Composite;
import org.apache.commons.functor.core.composite.ConditionalUnaryFunction;
import org.apache.commons.functor.core.composite.UnaryAnd;

/** 
 * @author   firebody
 * @since    2006-1-8-17:13:38
 */
public class Rules {
	public Rules(); {
		super();;
		
	}
	public static final UnaryFunction rule3();{
		
		 return new ConditionalUnaryFunction(rule3_condition();,
				 DiscountExp.setDiscountCalculator(new SetDiscount(0.1););.addCondToDiscountedObjects(IsAllCommodity.instance);,DiscountExp.NODISCOUNT);;
	}
	public static final UnaryPredicate rule3_condition();{
		UnaryAnd conditions = new UnaryAnd();;
		//sex is femail
		UnaryPredicate cond1=constructUnaryPredicate(GetCustomerSex.instance,IsEqual.instance();,"femail");;
		//purchase date is 3/8
		UnaryFunction getDate = Composite.function(GetDate.instance,GetPurchaseDate.instance);;
		UnaryFunction getMonth = Composite.function(GetMonth.instance,GetPurchaseDate.instance);;
		return conditions.and(cond1);.
							and(constructUnaryPredicate(getMonth,IsEqual.instance();,new Integer(3);););.
								and(constructUnaryPredicate(getDate,IsEqual.instance();,new Integer(8);););;
	}
	/**
	 * shortcuit method to construct predicate.test(leftFunction.evaluate(obj);,rightConstant);
	 * @param leftFunction
	 * @param predivate
	 * @param constant
	 * @return
	 */
	static UnaryPredicate constructUnaryPredicate(UnaryFunction leftFunction,BinaryPredicate predicate,Object rightConstant);{
		return Composite.predicate(RightBoundPredicate.bind(predicate,rightConstant);,leftFunction);;
	}

}
0 请登录后投票
   发表时间:2006-01-09  
如果客户性别还有“不知道”一项,而对规则1,如果客户性别不知道,则不适用。
同样的,客户年龄也有“不知道”这个选项。如果不知道,任何关于年龄的规则也不适用。

规则三里,调用了规则1,如果规则1不适用,则规则3也不适用。(实际上,推而广之,任何作为某个其它规则X的条件的规则如果不适用,则该规则X也不适用)

能不能做这么个组合子,ifthen(rule1, rule2),它产生一个新的rule,以rule1为condition,rule2为结果。rule1不适用,或者rule1返回false,都认为整个规则不适用。
0 请登录后投票
   发表时间:2006-01-09  
ajoo 写道
如果客户性别还有“不知道”一项,而对规则1,如果客户性别不知道,则不适用。
同样的,客户年龄也有“不知道”这个选项。如果不知道,任何关于年龄的规则也不适用。

规则三里,调用了规则1,如果规则1不适用,则规则3也不适用。(实际上,推而广之,任何作为某个其它规则X的条件的规则如果不适用,则该规则X也不适用)

能不能做这么个组合子,ifthen(rule1, rule2),它产生一个新的rule,以rule1为condition,rule2为结果。rule1不适用,或者rule1返回false,都认为整个规则不适用。

可以把每一个Rule既作为能够获得结果的Function,由可以作为条件判断的Predicate,可以类似这样:
public class Rule1 implements UnaryFunction,UnaryPredivate{
              public Object evaluate(Object arg);{
                    //.. your logic 
                    return result;
              }
              public Boolean test(Object arg);{
                      Object result = evaluate(arg);;
                      //your logic here
                         return true or false;
               }

}



或者用一个适配器,将UnaryFunction适配成一个UnaryPredivate,Composit已经作了这个实现。
0 请登录后投票
   发表时间:2006-01-09  
好。现在你的每个Rule都要实现两个接口:
Predicate和UnaryFunction。Predicate用来表示这个rule是否apply,UnaryFunction用来直接取结果。

可是,test()和evaluate()都要执行业务逻辑。如果在某处我既需要evaluate(),还要知道rule是否适用怎么办?

另外,这种实现两个接口的要求只在原子规则上才能够被满足。当用functor库里面提供的功能进行组合的时候,你还能始终保持着两个接口?



比如CompositeUnaryFunction之类的东西一旦使用,你的Predicate就被藏了起来,不能用了。


ifthen(rule1, rule2)
这里面,rule1和rule2可能是任何Rule,这些Rule甚至可能无法用evaluate()的返回值来判断是否可用。(比如,一个Rule如果适用,将读取数据库,返回客户的配偶信息,数据库可能返回null如果没有配偶的话。此时,所有返回值都被业务逻辑占据,你根本无法用一个predicate通过返回值判断规则是否适用)

它可能复杂到:ifthen(ifthen(rule1, rule2), ifthen(rule3, rule4));

这时候,参与计算的所有rule都要同时提供是否适用的接口和具体计算业务逻辑的接口。
0 请登录后投票
   发表时间:2006-01-10  
ajoo 写道

可是,test()和evaluate()都要执行业务逻辑。如果在某处我既需要evaluate(),还要知道rule是否适用怎么办?
对于这点我不是很理解,我所理解的是规则总是由Condition和Action来构成的,Action仅仅是指单纯的动作,比如返回一个折扣点数,或者设置某个对象的状态等等,而Condition就是判断执行规则的条件,我所认同的是他们两者是分开的,虽然前面有点仓促的回答你所提的问题,但我并不完全认同这样的思路。
另外,用对象来封装组合各种组合子,确实需要一些反复构思的技巧,我想他要比一个已经定义好的规范或者解析引擎来说要远远不够“好用“,但是他多了一个好处,就是我可以在具体的设计中随心所欲的设计我的组合子,结果可以是一个返回的对象,甚至一个对象状态的修改。 这些都非常灵活。
但是因为基础设施的不够完善,带来的效率很值得考虑,除非已经有一个前期的准备和充分的设计。
在具体的项目中,规则引擎的考虑不大会是他,但是小模块范围内的规则组合,我会首选它。
引用

ifthen(rule1, rule2)
这里面,rule1和rule2可能是任何Rule,这些Rule甚至可能无法用evaluate()的返回值来判断是否可用。(比如,一个Rule如果适用,将读取数据库,返回客户的配偶信息,数据库可能返回null如果没有配偶的话。此时,所有返回值都被业务逻辑占据,你根本无法用一个 predicate通过返回值判断规则是否适用)


java里面的if else总归是boolean的,我不大理解你的意思。 可以说仔细一些吗? predicate不是根据返回值来动作,而是返回一个boolean .
0 请登录后投票
   发表时间:2006-01-11  
firebody 写道
ajoo 写道

可是,test()和evaluate()都要执行业务逻辑。如果在某处我既需要evaluate(),还要知道rule是否适用怎么办?

对于这点我不是很理解,我所理解的是规则总是由Condition和Action来构成的,Action仅仅是指单纯的动作,比如返回一个折扣点数,或者设置某个对象的状态等等,而Condition就是判断执行规则的条件,我所认同的是他们两者是分开的,虽然前面有点仓促的回答你所提的问题,但我并不完全认同这样的思路。

对,rule的condition确实是返回bool,但是condition还是可能由其它的规则组成的。
比如一个condition的定义是客户的性别为女性,那么从数据库中读取性别这个动作可能就是一个规则。
如果不认同这一点,可以换个例子,假设我有一个规则的condition是某个计算折扣的规则返回的折扣小于10个百分点:
如果rule1返回的折扣小于十个百分点,则应用rule2。
而如果rule1不适用,或者rule2不适用,自然整个rule也不适用。



这种时候,无可避免的,规则也可以被用来组合condition。其实,仔细想想,规则和condition唯一的区别就是condition返回bool,而规则返回任意对象。从这里可以看出,实际上规则是condition的超集。

把两者统一,避免了不必要的复杂性,方便了代码重用。所有对规则的组合,可以自动地应用于condition。



firebody 写道

引用

ifthen(rule1, rule2)
这里面,rule1和rule2可能是任何Rule,这些Rule甚至可能无法用evaluate()的返回值来判断是否可用。(比如,一个Rule如果适用,将读取数据库,返回客户的配偶信息,数据库可能返回null如果没有配偶的话。此时,所有返回值都被业务逻辑占据,你根本无法用一个 predicate通过返回值判断规则是否适用)


java里面的if else总归是boolean的,我不大理解你的意思。 可以说仔细一些吗? predicate不是根据返回值来动作,而是返回一个boolean .

但是你给的例子,那个判断规则是否适用的predicate,就是通过判断rule的返回值是否是一个空map来判断的。你可以实现其它的predicate来采用其它的策略,但是采用了functor,你的选择只能局限在判断返回值上,没有其它方法。这其实是一个hack,而不是一个合理的可扩展的设计。(除非你抛出异常?)

更何况,用一个单独的predicate来判断规则是否可用,等于把规则的内在属性强行割裂成两部分。给定一个Functor,没有匹配的Predicate来判断适用性,根本就不是一个逻辑上完整的规则。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics