我正在设计一个小的“规则引擎”解决方案。
模块的基本思想是检查接收到的用户交互是否满足某些规则,如果是,则给予奖励积分。简而言之,有一组List<IRule>
接口,当用户与系统交互时,假设买东西,我迭代到所有IRule
- s并检查bool Execute(ITransactionContext context)
是否返回true。
这是IRule接口
public interface IRule
{
bool Execute(IContext context);
}
问题是具体的Rule : IRule
类彼此不同,具有不同的参数和含义。例如。
AmountRule
有Min, Max
个属性DatePeriodRule
包含From
和To
参数 EqualsRule
包含CheckProperty
和ExpectedValue
个参数等等...
这些是非常基本和简单的规则,可能存在更复杂的规则。
public class AmountRule : IRule
{
private decimal _min;
private decimal _max;
public bool Execute(IContext context)
{
return context.Amount >= _min && context.Amount <= _max;
}
}
public class DatePeriodRule : IRule
{
private DateTime _from;
private DateTime _to;
public bool Execute(IContext context)
{
return context.ProcessDate >= _from && context.ProcessDate <= _to;
}
}
因此创建规则并非易事,我决定使用工厂模式(或任何与之相关的模式,不介意它是Abstract Factory
,{{1 }或Factory Method
)并有一个接口Builder
,负责创建任何类型的IRuleFactory
实现类。
IRule
由于public interface IRuleFactory
{
IRule Create(RuleType type);
}
的具体实现的参数多样性,我在这里遇到了实际问题。例如。如果我想创建IRule
,那么我的AmountRule
需要RuleFactory
参数,如果MinAmount, MaxAmount
它需要DatePeriodRule
参数等等...
我正在寻找一些解决这个问题的好方法。这里From, To
只是一个额外的头痛吗?
答案 0 :(得分:3)
您的Factory
可以创建实现接口但具有不同参数的构造函数的特定类型。这正是它意味着的抽象类型。因此,您的Create
方法可能如下所示:
IRule Create(RuleType type){
if(type == RuleType.ValidAmount){
return new ValidAmountRule(10, 20);
}
else{
return new OtherKindOfRule("some other param");
}
}
当然,这里假设您的工厂知道构造函数的参数应该是什么。如果这只是Create方法的客户端所知,那么我会说间接不是必需的,客户端应该自己构造规则。 RuleType
参数有点可疑,因为调用者显然必须知道它想要的IRule
。如果RuleType
与要构造的具体类型之间存在一对一的映射,那么这肯定是不必要的复杂性,除非调用者没有直接构造规则所需的信息。
一般而言,规则引擎的目的是通过删除一堆复杂的if / else语句并封装规则来降低复杂性,以便可以单独管理它们,并可能在代码部署之外进行配置。您的具体规则必须获取从对象的构造函数和对象的调用者的某种组合应用其逻辑所需的信息。必须将对象构造函数中无法知道的内容传递给您的Execute
方法。这意味着IRule接口中的参数应该是一个高级对象,其中包含许多规则甚至可能不需要的信息。
答案 1 :(得分:3)
我实际上实现了一个名为Ariadne的RulesEngine,所以我从这里的经验谈起。你的困境来自于你有不同的操作数类型,他们必须排队。毕竟,你不能问
是'foo'&gt; 10
该表达式无效,因为foo是一个字符串,10是一个数字。同样,你不能问是否
重量&gt; 15磅
因为计算机不知道“重量”是什么或者“15磅”是什么。
你必须通过各种模式和深思熟虑的设计来管理它。
Ariadne的设计架构(欢迎您尝试采取一些自由)是这样的:
您的第一个问题是将所有操作,操作员和操作员都保留在同一知识库中 - 这对于在您的应用中运行多个操作系统至关重要。我这样做的方式是使用 AbstractFactory模式。
在this example中,您可以看到:
KnowledgeBase kb = KnowledgeBase.getInstance();
我是如何控制这3件作品的。如果你试图管理一个operator / operand / operandowner,他们都会通过这个knowledgbase,因为你必须调用它的函数
您还需要能够funnel the creation of rules into your methods。
public Predicate getPredicate(OperandOwner lho, String op, OperandOwner rho) throws AriadneException {
return operationFact.getPredicate(lho, op, rho);
}
这是一个注册过程,允许通过使用the Flyweight pattern重用公共对象,此外,它是用于生成谓词的Factory method。
这一切都很好,但实际上,你希望能够任意组合规则。要执行此操作,您需要继续组合它们,但将它们作为单个规则公开。这种many as one模式是复合模式。考虑:
您有3个单独的谓词可以组合。在'和'或'或'的任意组合中,你仍然有一个谓词。您必须能够将许多组合谓词表示为一个。
有关其工作原理的示例look here
结论:强大的实施将是:
为此,你有:
答案 2 :(得分:2)
我会应用原型模式来注册规则。如果您预期的所有规则都是无状态的,那么您甚至可以不进行克隆。