我不完全确定哪种设计最合适,在你满意之前,你一定要经历多次模型迭代,但是我认为问题的核心,我假设是组成规则和发现冲突的规则,可以用
Specification Pattern
规范模式基本上是将规则作为模型的一级公民,而不是只通过条件语言构造来表达。
实现模式有很多种方法,但下面是一个示例:
在我设计的系统中
一
,我已经设法重用设置相同的规范集来强制执行命令和查询的授权规则,并强制执行和描述业务规则。
例如,您可以添加
describe(): string
您的规范中负责描述其约束的方法,或者
toSql(string mainPolicyTableAlias)
方法,可以将其转换为SQL。
例如(伪代码)
someSpec = new SomeRule(...).and(new SomeOtherRule(...));
unsatisfiedSpec = someSpec.remainderUnsatisfiedBy(someCandidate);
errorMessage = unsatisfiedSpec.describe();
但是,直接在规范上实现这些操作可能会因各种应用程序/基础结构问题而污染它们。为了避免此类污染,您可以使用
Visitor Pattern
,这将允许您在正确的层中对各种操作进行建模。不过,这种方法的缺点是,每次添加新类型的具体规范时,都必须更改所有访问者。
#1为了做到这一点,我必须实现上述文章中描述的其他规范操作,例如
remainderUnsatisfiedBy
等。
我用C语言编程已经有一段时间了,但我认为
expression trees
在C中,可以非常方便地实现规范并将其转换为多个表示。
验证策略每个部分中不同规则之间的相关性
我不完全确定你在这里想什么,但是通过添加一个操作,比如
conflictsWith(Spec other): bool
在您的规范中,您可以实现一个冲突检测算法,该算法将告诉您一个或多个规则是否冲突。
例如,在下面的示例中,这两个规则都会发生冲突,因为这两个规则都不可能是真的(伪代码):
rule1 = new AttributeEquals('someAttribute', 'some value');
rule2 = new AttributeEquals('someAttribute', 'some other value');
rule1.conflictsWith(rule2); //true
总之,您的整个模型肯定要比这复杂得多,您必须找到正确的方法来描述规则并将它们与正确的组件关联起来。您甚至可能希望将一些规则与适用性规范相链接,以便它们仅在满足某些特定条件时适用,并且您可能有许多不同的规范候选类型,例如
Policy
,
Section
或
SectionAttribute
考虑到一些规则可能需要适用于整个
政策
而其他类型的规则必须根据特定节的属性进行解释。
希望我的回答能激发一些想法,让你走上正轨。我还建议您查看现有的验证框架和规则引擎以了解更多的想法。还请注意,如果您想要完整的规则和
政策
为了始终保持一致,那么您很可能会设计
政策
是由所有章节和规则组成的大型集合。如果由于性能原因或并发冲突(例如,许多用户编辑同一策略的不同部分),这在某种程度上不可能也不可取,那么您可能会被迫分解大型聚合并使用最终一致性。
当然,当现有状态被新规则失效时,您还必须考虑需要做什么。也许您希望同时强制更改规则和状态,或者您可以实现状态验证指示器,将当前状态的某些部分标记为无效等。
1-您能解释更多关于describe()、tosql(string mainpolicyTableAlias)的内容吗?我不理解这些函数背后的意图。
好,
describe
会给出规则的描述。如果你需要I18N支持
或者对消息进行更多的控制,您可能希望使用访问者,也可能需要一个功能,您可以使用模板化消息等覆盖自动描述。
toSql
方法是相同的,但生成可以在
WHERE
例如,条件。
new Required().describe() //required
new NumericRange(']0-9]').if(NotNullOrEmpty()).describe() //when provided, must be in ]0-9] range
这是一个相当大的缺点!我能问一下如何克服这个问题吗?
直接在对象上支持行为使添加新对象变得容易,但在使用访问者模式时添加新行为变得困难,这使得添加新行为变得容易,但添加新类型变得困难。那是众所周知的
Expression Problem
.
如果您可以找到一个对于所有特定类型都不太可能改变的通用抽象表示,那么问题就可以得到缓解。例如,如果要绘制多种类型的
Polygon
,如
Triangle
,
Square
等等,你最终可以把它们表示为一系列有序的点。一个规范可以被分解为
Expression
(
explored here
但这并不能神奇地解决所有的翻译问题。
下面是一个用javascript&html实现的示例。请注意,一些规范的实现非常幼稚,不能很好地处理未定义/空白/空值,但您应该了解这一点。
class AttrRule {
isSatisfiedBy(value) { return true; }
and(otherRule) { return new AndAttrRule(this, otherRule); }
or(otherRule) { return new OrAttrRule(this, otherRule); }
not() { return new NotAttrRule(this); }
describe() { return ''; }
}
class BinaryCompositeAttrRule extends AttrRule {
constructor(leftRule, rightRule) {
super();
this.leftRule = leftRule;
this.rightRule = rightRule;
}
isSatisfiedBy(value) {
const leftSatisfied = this.leftRule.isSatisfiedBy(value);
const rightSatisfied = this.rightRule.isSatisfiedBy(value);
return this._combineSatisfactions(leftSatisfied, rightSatisfied);
}
describe() {
const leftDesc = this.leftRule.describe();
const rightDesc = this.rightRule.describe();
return `(${leftDesc}) ${this._descCombinationOperator()} (${rightDesc})`;
}
}
class AndAttrRule extends BinaryCompositeAttrRule {
_combineSatisfactions(leftSatisfied, rightSatisfied) { return !!(leftSatisfied && rightSatisfied); }
_descCombinationOperator() { return 'and'; }
}
class OrAttrRule extends BinaryCompositeAttrRule {
_combineSatisfactions(leftSatisfied, rightSatisfied) { return !!(leftSatisfied || rightSatisfied); }
_descCombinationOperator() { return 'or'; }
}
class NotAttrRule extends AttrRule {
constructor(innerRule) {
super();
this.innerRule = innerRule;
}
isSatisfiedBy(value) {
return !this.innerRule;
}
describe() { return 'not (${this.innerRule.describe()})'}
}
class ValueInAttrRule extends AttrRule {
constructor(values) {
super();
this.values = values;
}
isSatisfiedBy(value) {
return ~this.values.indexOf(value);
}
describe() { return `must be in ${JSON.stringify(this.values)}`; }
}
class CompareAttrRule extends AttrRule {
constructor(operator, value) {
super();
this.value = value;
this.operator = operator;
}
isSatisfiedBy(value) {
//Unsafe implementation
return eval(`value ${this.operator} this.value`);
}
describe() { return `must be ${this.operator} ${this.value}`; }
}
const rules = {
numOfHoursInMonth: new CompareAttrRule('<=', 6),
excuseType: new ValueInAttrRule(['some_excuse_type', 'some_other_excuse_type']),
otherForFun: new CompareAttrRule('>=', 0).and(new CompareAttrRule('<=', 5))
};
displayRules();
initFormValidation();
function displayRules() {
const frag = document.createDocumentFragment();
Object.keys(rules).forEach(k => {
const ruleEl = frag.appendChild(document.createElement('li'));
ruleEl.innerHTML = `${k}: ${rules[k].describe()}`;
});
document.getElementById('rules').appendChild(frag);
}
function initFormValidation() {
const form = document.querySelector('form');
form.addEventListener('submit', e => {
e.preventDefault();
});
form.addEventListener('input', e => {
validateInput(e.target);
});
Array.from(form.querySelectorAll('input')).forEach(validateInput);
}
function validateInput(input) {
const rule = rules[input.name];
const satisfied = rule.isSatisfiedBy(input.value);
const errorMsg = satisfied? '' : rule.describe();
input.setCustomValidity(errorMsg);
}
form > label {
display: block;
margin-bottom: 5px;
}
input:invalid {
color: red;
}
<h3>Rules:</h3>
<ul id="rules"></ul>
<form>
<label>numOfHoursInMonth: <input name="numOfHoursInMonth" type="number" value="0"></label>
<label>excuseType: <input name="excuseType" type="text" value="some_excuse_type"></label>
<label>otherForFun: <input name="otherForFun" type="number" value="-1"></label>
</form>