接口进阶:继承、关系与 Action

第 4 课 · 共 6 课 约 8 分钟

继承叠出更宽的形状;接口关系规定实现方必须有哪条线;接口上的 Action 是怎么设计的、AIDC 今天怎么做。

本课目标

读完这一课,你将能够

  • 用继承把接口叠起来,说清实现子接口意味着什么
  • 给接口声明关系,让实现方映射到自己的链接端
  • 说清接口上的 Action 是怎么设计的,以及 AIDC 今天怎么做

继承:一层加一点

上一节的 Schedulable 是一个平的接口。接口还可以继承别的接口,拿到它全部的属性和关系,再加上自己的。示例工厂里,「有人负责」是比「可排期」更宽的形状:先有 Owned(一个 ownerId),Schedulable 继承它。假设三种订单都有 ownerId,由 assign-owner 维护。

[
  { "kind": "sharedPropertyType", "apiName": "ownerId", "title": "Owner", "schema": { "type": "string" } },
  {
    "kind": "interfaceType",
    "apiName": "Owned",
    "title": "Owned",
    "schema": { "properties": { "ownerId": { "required": true } } }
  },
  {
    "kind": "interfaceType",
    "apiName": "Schedulable",
    "title": "Schedulable",
    "schema": {
      "extendsInterfaces": ["Owned"],
      "properties": { "dueAt": { "required": true }, "status": { "required": true } }
    }
  }
]
  • 实现 Schedulable 的类型,同时也实现了 Owned。按 Owned 查,三种订单一样会出来。
  • 继承来的必填属性也要映射;名字相同的可以省略,这里的 ownerId 就是。
  • 一个接口最多继承 10 个接口,一个对象类型最多直接实现 20 个接口,继承不能成环。
  • 改父接口会波及所有子接口的实现方,所以父接口要小而稳。去掉一层继承,或给父接口加必填属性,都会被标为破坏性变更。

不要做深继承链。建议组合,而不是层层继承:工单将来还要「可检验」,就让它同时实现 Schedulable 和 Inspectable,不要发明组合类型。

随口一问

SchedulableWorkOrder、InspectableWorkOrder……每多一种能力,就多一个组合类型。

好的交代

workOrder 同时实现 Schedulable 和 Inspectable;新能力只需要再加一个接口。

接口关系:规定必须有哪条线

接口也能声明关系,这叫关系约束(link type constraint)。约束写清四件事:目标(一个对象类型,或另一个接口)、基数(ONE 或 MANY)、是否必填,以及在代码里叫什么。实现方用自己已有的链接端去满足它。

例子:销售订单的对方是客户,采购订单的对方是供应商。两个目标类型不同,所以目标要写成接口。先建 Party(有 name),让 customer 和 supplier 各自实现它,再给 Schedulable 加一个指向 Party 的 counterparty 关系。

[
  {
    "kind": "interfaceType",
    "apiName": "Party",
    "title": "Party",
    "schema": { "localProperties": { "name": { "type": "string", "required": true } } }
  },
  {
    "kind": "interfaceType",
    "apiName": "Schedulable",
    "title": "Schedulable",
    "schema": {
      "extendsInterfaces": ["Owned"],
      "properties": { "dueAt": { "required": true }, "status": { "required": true } },
      "links": {
        "counterparty": {
          "title": "Counterparty",
          "linkedEntity": { "interfaceType": "Party" },
          "cardinality": "ONE",
          "required": false
        }
      }
    }
  }
]

实现方在 implements 里用 links,把接口关系映射到自己的链接端。可选的关系可以不映射:工单没有对方,就不写。

{
  "salesOrder":    [{ "interface": "Schedulable", "properties": { "dueAt": "promisedDate" }, "links": { "counterparty": "customer" } }],
  "purchaseOrder": [{ "interface": "Schedulable", "properties": { "dueAt": "expectedAt" }, "links": { "counterparty": "supplier" } }],
  "workOrder":     [{ "interface": "Schedulable", "properties": { "dueAt": "dueDate" } }]
}

把 required 改成 true,工单就必须补上,否则定义被拒:workOrder: must implement required interface link Schedulable.counterparty。所以新加的接口关系先写成可选,等实现方都到位再收紧。

接口上的 Action

接口上的 Action 有两层设计。接口 Action 规则:创建、修改、删除、建关系,作用在「实现某个接口的对象」上,所以只能改接口共有的属性,比如给所有订单推迟到期日。接口的 Action 类型约束(这一层还在早期阶段):接口只规定实现方要有一个什么样的 Action,包括名字和参数的形状,逻辑仍由各类型自己的 Action 去写。

接口 Action 规则要小心几点:提交条件对所有实现类型一视同仁,所以权限要按最宽的情况去想;修改规则不能改主键;创建规则要求接口和规则里有一个能当主键的属性。另外,它目前不写 Action 日志,也不能配合函数使用。

它做什么AIDC 今天
对接口写创建、修改、删除规则对任一实现类型的对象操作,只能改接口共有的属性目前还没有:规则的 objectType 必须是具体的对象类型
接口的 Action 类型约束规定实现方要有什么样的 Action:名字和参数的形状目前还没有
引用任一实现类型的对象当参数接口引用参数:可以选到实现了接口的任意对象有:interfaceObject 参数,按对象 RID 传入;改数据的规则仍要写具体类型

所以今天的做法是:为每种类型各写一个 Action,参数名、提交条件和日志保持同一个形状。比如「推迟到期日」:postpone-sales-order、postpone-work-order、postpone-purchase-order,都带 newDueDate 和 reason,都开 actionLog。这是先搭好接口、暂时按类型重复:不比没有接口更费事,将来合并也有路。

下一节回到判断力:这些构件都会用了,什么该建成对象、属性、关系,还是接口?

要点

  • 子接口继承父接口的属性和关系;实现子接口,就同时实现了它继承的接口。
  • 组合优于深继承:一个类型实现几个小接口,不发明组合类型。
  • 接口关系规定实现方必须有哪条链接,目标可以是对象类型,也可以是另一个接口;新约束先写成可选。
  • 接口上的 Action 分规则和类型约束两层;AIDC 目前还没有,今天为每种类型各写一个同形状的 Action。

练一练

给接口加继承、关系和动作

接着上一节的 ontology/ 目录做。

把 Owned 加进定义,让 Schedulable 继承它。三种订单的 implements 各要多映射哪个属性?哪些可以省略?

小测

选一个答案,马上看解析。

Q1workOrder 实现了 Schedulable,而 Schedulable 继承 Owned。按 Owned 查询,会包含工单吗?

Q2Schedulable.counterparty 是可选的接口关系,工单没有对方。会怎样?

Q3今天能不能对 Schedulable 直接写一个 modifyObject 规则,一次改所有订单的到期日?

延伸阅读