代码之家  ›  专栏  ›  技术社区  ›  Anyname Donotcare

如何定义和执行约束层次实体的复杂规则

  •  3
  • Anyname Donotcare  · 技术社区  · 6 年前

    如果我有 Policy 而这 政策 应该包括 Sections (固定号码)。

    我的分区是4个预定义的分区:

    • 工作时间规定。
    • 借口。
    • 轮班。
    • 时间表。

    Section 具有与其他部分中的属性不同的固定属性。 如果我可以用一个类比来说明这一点:

    • 政策---人体。
    • 截面--->(手臂、腿、头)
    • 每个部分彼此不同,就像(头部包含眼睛, 耳朵,…等,而不是手臂有两只手)

    例如:

    • 工作时间规定 截面有 name,list of work-times .
    • 借口 截面有 numberofhours, reason, excuse_type .

    注: 基于领域专家的解释:他想要一个 保存 对每个部分的操作(作为草稿),以便他可以更新该部分 除非保险单尚未提交,以及 提交 整体行动 策略,以便在提交策略后,没有人可以更新或删除此 政策或其部分。(任何必需的更新=定义新策略)


    现在我想设计 政策 , 截面 its content . 但我被卡住了。

    首先我想我可以设计 政策 作为实体(聚合根)并创建 four 班级,每人一个 截面 把他们都继承过来 Section base class(Id,name) 政策 包含的列表 截面 .


    其次,我指导我的思想,将本节内容概括如下:

    我将创造:

    • 界面 ISection : SectionType , SectionRule
    • 每个部分都将实现这个接口

    然后我将创建引用表部分规则:

    前任:

    rule-key         |default-value|operators|section-type|value-type|rule-type
    
    NumOfhoursInMonth| 5:00        |  =      |  2         | String      |Assignment 
    AvailableExcuses |2:00,2:30    |  IN     |  2         | List<String>|Relational 
    

    笔记:

    • 截面类型 1 Excuses
    • 运算符是枚举
    • 节类型为枚举

    当用户启动 政策 我将循环遍历引用表,以表格形式列出规则,以便他可以更改 default values 把它们保存在 基于此类型的节:

      Id   |Name       |Rule                   |section-type
      1    |Excuses    |NumOfhoursInMonth <= 6 |   2
    

    我现在面临两个问题。

    1. 如果其中一些规则依赖于每个规则,那么如何关联不同的规则 其他?前任 NumOfExcuses'hoursInMonth Should be less than or equal 6:00 根据第一条规则,但是如何防止用户 在设置第二个规则时违反了此规则,如果他设置了 AvailableExcuses IN(5:00,7:00) !现在我应该阻止用户 添加一个大于 6 因为第一条规则限制了 第二个?第二个规则与第一个规则不一致,因为列表包含 (07:00) 第一条规则规定 totalExcuseshoursInMonth <= 06:00 hours
    2. 如何使规则更具表现力以允许条件规则和其他 规则?

    我走对了吗?在我的情况下,我可以得到一些建议吗?

    2 回复  |  直到 6 年前
        1
  •  3
  •   plalx    6 年前

    我不完全确定哪种设计最合适,在你满意之前,你一定要经历多次模型迭代,但是我认为问题的核心,我假设是组成规则和发现冲突的规则,可以用 Specification Pattern 规范模式基本上是将规则作为模型的一级公民,而不是只通过条件语言构造来表达。

    实现模式有很多种方法,但下面是一个示例:

    Specification Pattern

    在我设计的系统中 ,我已经设法重用设置相同的规范集来强制执行命令和查询的授权规则,并强制执行和描述业务规则。

    例如,您可以添加 describe(): string 您的规范中负责描述其约束的方法,或者 toSql(string mainPolicyTableAlias) 方法,可以将其转换为SQL。

    例如(伪代码)

    someSpec = new SomeRule(...).and(new SomeOtherRule(...));
    unsatisfiedSpec = someSpec.remainderUnsatisfiedBy(someCandidate);
    errorMessage = unsatisfiedSpec.describe();
    

    但是,直接在规范上实现这些操作可能会因各种应用程序/基础结构问题而污染它们。为了避免此类污染,您可以使用 Visitor Pattern ,这将允许您在正确的层中对各种操作进行建模。不过,这种方法的缺点是,每次添加新类型的具体规范时,都必须更改所有访问者。

    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>
        2
  •  1
  •   Rafael    6 年前

    似乎您需要一个对象模型,大概是用于自定义CMS,它将用于呈现表单:

    • 政策就是形式
      • 提交时,表单被锁定
    • 部分是字段集
      • 可单独保存
    • 节属性是字段
      • 可以用初始值填充字段
      • 字段受其他地方/动态定义的验证规则约束

    er diagram

    enter image description here

    需要注意的一些事项:

    • 应在sectionattributes上捕获默认值
    • 节属性具有ValidationRules

    从你的问题看来,至少有两个角色:

    • 那些可以锁定策略的人,管理员
    • 无法锁定策略的用户

    设计注意事项

    • 节可以递归吗?
    • 与系统交互的参与者、管理员、用户等是谁?
    • 每个实体的公共操作是什么?
    • 锁定策略后,是否可以更新sectionattributevalidationrules?当新的/更新的规则使现有的节属性无效时会发生什么?
    • 可以跨策略重用节吗?
    • 策略访问是否受控制?

    我的忠告

    • 坚持好的软件原则
      • 开闭原则
      • 固体、干燥、德米特定律及其他
    • 别担心犯错误
    • 重构为模式
    • 利用测试驱动的设计(红色、绿色、重构)

    这是一个很好的开始,真正的尝试提前100%是浪费时间;希望这能帮助你摆脱困境。