模块二:核心语言概念
命名空间、包、导入/可见性、特化、多重性和完整的特征模型——你在每个 SysML v2 模型中都会用到的六种核心语言机制。每一种都是 KerML L1 概念在 SysML L2 层的实现。
KerML → SysML:第一层如何变成第二层
模块一介绍了 KerML 作为语义基础(L1)以及建立在其上的 SysML v2 语言层(L2)。这在实践中意味着什么?每一个 SysML v2 关键字都是某个 KerML 概念的受约束、命名化的应用。下表展示了本模块涉及的每个 SysML 特性背后对应的 KerML 构件。
| SysML v2 概念(L2) | 底层 KerML 构件(L1) | SysML 额外添加了什么 |
|---|---|---|
package | Namespace(非 Type 的特化) | 约定它只持有定义;没有实例语义 |
part def / part | Class(一种 Classifier) | 关键字 part 意味着复合所有权和默认多重性 [1..1] |
attribute def / attribute | DataType(一种 Classifier) | 关键字 attribute 意味着值类型语义 |
action def / action | Behavior(一种 Classifier) | 关键字 action 意味着随时间发生的语义 |
port def / port | PortDefinition(特化自 StructureDefinition)/ 由 PortDefinition 类型化的有向复合 Feature | 关键字 port 意味着边界交互点;共轭(~)反转所有方向 |
specializes | Subclassification(一种 Relationship) | 相同语义;SysML 将其限制在定义级别使用 |
subsets | Subsetting(一种 Relationship) | 相同语义;SysML 将其限制在用法级别的特征 |
redefines | Redefinition(一种 Relationship) | 相同语义;SysML 将其限制在用法级别的特征 |
import | Import(一种 Namespace 成员关系) | SysML 增加了 public/private 可见性和递归(::**)变体 |
多重性 [m..n] | 类型化元素上的 Multiplicity Feature | SysML 对 part/attribute/port 强制默认 [1..1];KerML 默认是 [0..*] |
你不需要写 KerML——你写的是 SysML v2。但了解每个关键字的 KerML 来源,能解释为什么规则是这样的。当某件事让你困惑时,答案总是在 KerML 这一层找到。
有了这张对照图,本模块接下来深入介绍每个 SysML v2 概念,并注明哪些行为继承自 KerML、哪些是 SysML 自己添加的约束。
命名空间 — 每个模型元素的骨架
KerML 来源:Namespace(根层,§8.3.2.4.5)
命名空间(Namespace)是一种可以包含其他元素的 Element,这些被包含的元素叫做它的成员。这是 KerML 中最基本的结构概念。文本表示法中每一对花括号 { } 都划定了一个命名空间边界。SysML v2 原封不动地继承了这个概念——这就是为什么 part def、action def,甚至各个特征体都充当命名空间。
在 SysML v2 中,每一个 Type 也是一个 Namespace。每一个 part def、action def、port def,甚至每一个特征体,都充当一个命名空间。在 SysML v1 中,只有 Package 和 Model 是命名空间——在 v2 中,这个概念无处不在。
名称解析与限定名
元素可以携带两个标识符:一个声明名(普通标识符或单引号括起来的非限制名,如 'Ordinary Cargo Bay')和一个用尖括号括起来的短名(如 <CB>)。限定名使用 :: 作为分隔符:
1 Vehicles::Powertrain::Engine
当一个简单(非限定)名在当前作用域链中没有歧义时,可以直接使用。当存在冲突或元素不在作用域内时,需要使用限定名。
KerML 裸命名空间的文本写法
1 namespace Outer { 2 namespace Inner { 3 // 这里的元素:Outer::Inner::elementName 4 } 5 }
认为只有 Package 是命名空间——因为每个 Type 都是 Namespace,所以在定义内部使用导入是合法且有用的。这种技术可以避免把只与一个定义相关的名称污染外层包:
1 part def Reindeer { 2 private import Datatypes::Color; // 仅限 Reindeer 的命名空间 3 attribute noseColor : Color; 4 }
包 — 没有语义的分组
KerML 来源:不特化 Type 的 Namespace
包(Package)是一种命名空间,但与 part def 或 action def 不同,它在 KerML 中不特化 Type。这一事实解释了一切:Type 对建模领域的实例进行分类;Package 根本没有实例语义——它只用来组织模型的元素。
1 package VehicleModel { 2 import ScalarValues::*; 3 import ISQ::*; 4 5 part def Vehicle { 6 attribute mass : ISQ::MassValue; 7 part engine : Engine; 8 } 9 10 part def Engine; 11 12 package Powertrain { 13 part def Transmission; 14 part def Drivetrain; 15 } 16 }
库包
可复用的模型库使用 library package 形式。标准库使用 standard library package:
1 library package CommonParts { 2 part def Fastener; 3 part def Seal; 4 }
一个包必须位于单个文件中——没有跨多个源文件重新打开或扩展包的机制(与 C# 的 partial class 不同)。请提前规划包的粒度。
导入与可见性
KerML 来源:Import(一种 Membership 关系);每个 Membership 上的 VisibilityKind
在 KerML 中,Import 是一种 Membership 关系,它将元素从一个命名空间引入到另一个命名空间中。SysML v2 原封不动地继承这一机制,并添加了两个约束:一个控制重新导出的可见性注解(public 或 private),以及一个递归形式(::**)。命名空间中的每个元素也有自己的 VisibilityKind——两者是正交的。
SysML v2 的导入系统因此控制三个正交维度:导入哪些元素、如何导入,以及是否重新导出。
可见性种类
每个成员关系都携带一个 VisibilityKind:
| 关键字 | 作用域 |
|---|---|
public(默认) | 在命名空间外部可见 |
private | 仅在所属命名空间内部可见 |
默认可见性是 public——除非你明确声明,否则你声明的一切都是可见的。最佳实践:对实现细节声明 private。
两种导入机制
1 // MembershipImport — 单个命名元素 2 import Sensors::Accelerometer; 3 4 // NamespaceImport — 一次导入所有公开成员 5 import Sensors::*; 6 7 // 私有导入 — 名称不会重新导出 8 private import InternalUtils::*; 9 10 // 递归导入 — 包含所有嵌套命名空间 11 import VehicleModel::**; 12 13 // 别名 — 创建一个替代名称 14 alias Accel for Sensors::Accelerometer;
公有 vs 私有导入 — 控制重新导出
导入关系本身的可见性控制着当此命名空间被其他命名空间导入时,被导入的名称是否会重新导出:
1 package HighLevel { 2 public import Sensors::*; // 名称会被重新导出 3 private import InternalUtils::*; // 名称留在这里 4 }
由于成员和导入可见性都默认为 public,通配符导入链可能会将名称传播到远超预期的范围。始终使用 private import,除非你明确想要重新导出。
特化链
KerML 来源:Subclassification、Subsetting、Redefinition(Relationship 的子类型)
SysML v2 将 UML 的泛化、子集和重定义统一在一个概念下:特化(Specialisation)。这三种关系都存在于 KerML 层——SysML 只是通过关键字 specializes、subsets 和 redefines 将它们暴露出来,并限制了它们的使用范围:specializes 适用于定义之间,而 subsets 和 redefines 适用于用法之间。由于 KerML 将分类器和特征都视为 Type,特化可以在整个模型中统一应用。
四种特化关系
| 关系 | 适用对象 | 关键字 | 简写 | 语义效果 |
|---|---|---|---|---|
| 子分类 | 定义 ↔ 定义 | specializes | :> | 子类型继承所有特征 |
| 子集 | 用法 ↔ 用法 | subsets | :> | 值构成子集 |
| 重定义 | 用法 ↔ 用法 | redefines | :>> | 替换继承的特征 |
| 特征类型化 | 用法 → 定义 | typed by | : | 指定类型 |
:> 在定义之间表示子分类,在用法之间表示子集。定义/用法的区别决定了含义。
子集示例
1 part def ReindeerTeam { 2 abstract part reindeer : Reindeer[9]; // 共9只驯鹿 3 part rudolph : Reindeer subsets reindeer; // rudolph 是其中之一 4 part dasher : Reindeer subsets reindeer; // dasher 是另一只 5 }
重定义示例
1 part def Vehicle { 2 part eng : Engine; 3 attribute vendor : ScalarValues::String; 4 } 5 6 part def DieselVehicle specializes Vehicle { 7 part eng : DieselEngine redefines eng; // 缩窄类型 8 attribute vendor : ScalarValues::String = "BoschMotorsport" 9 redefines vendor; // 绑定值 10 }
共轭 — 镜像端口
共轭创建一个类型的镜像,其中所有特征方向都被反转:in ↔ out,inout 保持不变。~ 前缀表示共轭形式——主要用于端口兼容性:
1 port def FuelPort { 2 out item fuelSupply : Fuel; 3 in item fuelReturn : Fuel; 4 } 5 6 part def FuelTank { port tankPort : FuelPort; } // 发送燃油 7 part def Engine { port engFuelPort : ~FuelPort; } // 接收燃油
混淆 subsets 和 redefines。将 subsets(子集)用在你想要 redefines(重定义)的地方,会创建一个额外的特征而不是替换继承的特征——模型最终同时拥有原始特征和新特征。redefines 是替换;subsets 是并列添加。
多重性
KerML 来源:Multiplicity(一种 Feature——类型化元素将 Multiplicity Feature 作为拥有的子元素携带)
在 KerML 中,多重性本身就是一个 Feature——它不是 UML 风格的注解,而是由类型化元素拥有的一等模型元素。这允许模型级的参数化多重性,并且多重性参与与其他所有 Feature 相同的机制(继承、重定义、约束)。SysML v2 直接继承这一点,并在 KerML 的开放式 [0..*] 上额外施加了特定领域的默认値。
语法和常见形式
1 part def Vehicle { 2 part engine : Engine; // 默认 [1..1] 3 part wheels : Wheel[4]; // 恰好4个 4 part passengers : Person[0..5]; // 0到5个 5 attribute tags : String[*]; // 零个或多个(= [0..*]) 6 part sensors : Sensor[1..*]; // 一个或多个 7 ref part trailer: Trailer[0..1]; // 可选引用 8 }
默认多重性 — 关键细节
| 上下文 | 默认多重性 |
|---|---|
KerML feature(未指定) | [0..*] — 无约束 |
SysML part 用法 | [1..1] — 恰好一个 |
SysML attribute 用法 | [1..1] — 恰好一个 |
SysML port 用法 | [1..1] — 恰好一个 |
有序、唯一与集合类型
| 关键字 | 集合类型 |
|---|---|
| (默认) | 集合 — 无序,唯一 |
ordered | 有序集合 — 有序,唯一 |
nonunique | 包 — 无序,允许重复 |
ordered nonunique | 序列 — 有序,允许重复 |
特征上的多重性约束的是每个被特征化实例的值数量(例如,每辆 Vehicle 有4个 Wheel)。分类器上的多重性约束的是整个宇宙中该类型的总体数量。这是非常不同的概念——实践中特征级含义占绝大多数。
在 KerML 中,集合类型(ordered、nonunique)被编码为 Multiplicity Feature 本身的属性。SysML 将这些标志作为关键字修饰符继承下来。默认(无关键字)是集合:无序且唯一——与数学集合论一致。
特征模型
KerML 来源:Feature(一种 Type——核心层)
Feature 是一个 Type,它对事物之间的关系进行分类。由于 Feature 是 Type,它们可以拥有子特征、被特化、携带多重性,并参与每种 Type 关系。这种统一——UML 中没有的——是 KerML 中最重要的架构决策。在 SysML v2 中,每个用法关键字(part、attribute、action、port)在底层都产生一个 KerML Feature;关键字只是在原始 KerML Feature 之上添加了特定领域的约束。
特征方向:in、out、inout
方向声明了数据或控制信号相对于拥有它的元素流向哪一侧。它只对参与交互边界的特征有意义——端口内的流动项、动作的参数,或函数的输入和输出。
in— 值从外部被提供给此元素。可以理解为输入插口。out— 值由此元素产生并向外发送。可以理解为输出插口。inout— 值双向流动;该特征既能接收也能产生值(例如读写数据总线)。
想象墙上的电源插座。从建筑的角度看,墙壁供应电力——这是 out。从电器的角度看,它接收电力——这是 in。同一条物理连接,从一侧看是 out,从另一侧看是 in。这正是共轭(~)所捕捉的概念:它创建一个镜像端口类型,其中每个方向都被反转。
1 port def PowerPort { 2 out item voltage : ElectricalPower; // 此端口向外供应电力 3 in item load : LoadSignal; // 此端口接收负载信号 4 } 5 6 // ~PowerPort 是共轭——反转所有方向: 7 // in item voltage : ElectricalPower (现在接收电力) 8 // out item load : LoadSignal (现在发送负载信号) 9 10 action def MotorControl { 11 in rpm : Real; // 输入:控制器期望的转速 12 in voltage : Real; // 输入:供电电压 13 out torque : Real; // 输出:产生的扭矩 14 }
有方向的特征自动是引用性的——不需要添加 ref。端口本身没有方向:不要写 in port p。方向属于端口定义内部的特征,而不是端口本身。
复合 vs 引用:part vs ref part
这个区别控制所有权和生命周期。它回答的问题是:这个元素的存在是因为它的父级存在,还是它独立于父级而存在?
part— 复合:父级独占子级。子级随父级创建,随父级销毁。汽车物理上包含它的发动机——你不能把发动机拆下来还说它仍然有意义地是"这辆车的发动机"。ref part— 引用:子级独立存在;父级只是持有对它的引用。同一个Person可以同时是多辆车的司机。销毁汽车不会销毁司机。attribute— 始终是引用性的,但专门用于值类型数据(数字、字符串、量)。值没有身份——"这个特定的 3.7 伏"没有意义。
复合就像焊接在汽车里的发动机:它与汽车共生共死。引用就像坐在车里的司机:他们下车后仍然独立存在。
1 part def Hospital { 2 part ward : Ward[1..*]; // 复合:病房只存在于这家医院内 3 ref part doctor: Doctor[0..*]; // 引用:医生在这里工作,但独立存在 4 attribute name : String; // 值:只是数据,没有独立存在 5 } 6 7 // 后果:删除 Hospital 也会删除它的 Ward。 8 // Doctor 不受影响——它们独立存在。
四种值赋值方式
SysML v2 对如何赋值做了精确区分,将永久约束与初始起始值区分开来,将可继承的默认值与固定值区分开来。每种形式回答的问题:
| 语法 | 名称 | 回答的问题 | 能否被覆盖? |
|---|---|---|---|
= expr | 绑定值 | 这个值始终是什么? | 不能——永久不变量,不可改变 |
:= expr | 初始值 | 这个值从什么开始? | 能——实例生命周期内可以改变 |
default = expr | 默认绑定 | 除非特化覆盖,值默认是什么? | 能——重定义的子类型可以覆盖 |
default := expr | 默认初始 | 除非特化覆盖,值默认从什么开始? | 能——重定义的子类型可以覆盖 |
想想汽车仪表盘。绑定值就像车轮数量永远是 4——永不改变的结构事实。初始值就像出厂时油量从满格开始——使用过程中会变化。默认绑定就像所有车型共用的标准胎压——除非运动车型覆盖它。默认初始就像座椅位置的出厂设置——每个司机都可以更改。
1 part def Engine { 2 attribute cylinders : Integer = 4; // 绑定:始终为4,不可改变 3 attribute rpm : Real := 0; // 初始:从0开始,运行时会变化 4 attribute idleRPM : Real default = 800; // 默认绑定:800,子类型可覆盖 5 attribute warmupTime : Real default := 30; // 默认初始:从30秒开始,可覆盖 6 } 7 8 part def RacingEngine specializes Engine { 9 attribute idleRPM : Real = 1200 redefines idleRPM; // 覆盖默认绑定 10 }
= 是永久不变量,不是起始点。写 attribute rpm : Real = 0 意味着转速在实例整个生命周期内都不能偏离零。当你的意思是"从这个值开始,但运行过程中会变化"时,请使用 :=。
特征链 — 点表示法路径
特征链让你使用点表示法穿越嵌套特征的路径。它构建一个派生特征,其值是沿整条链导航的结果——链中的每一步产生一组值,这组值成为下一步的输入域。
你在三种主要情况下使用特征链:
- 定义派生特征 — 使用点表示法(如
department.employee)声明一个特征的值通过遍历其他特征的路径来得到。注意:SysML v2 中没有chains关键字;特征链纯粹通过点表示法来表达。 - 编写流语句 — 精确指定流连接到哪个嵌套的流动项端点,因为仅写端口名称不够具体。
- 在约束和需求中访问嵌套属性,即需要深入到子部件内部的情况。
"我的堂兄弟"不是一个直接关系——你必须沿路径走:我的父母,然后他们的兄弟姐妹,然后他们的孩子。特征链正是把这种多跳路径命名为一个单一可达的特征。
1 // ── 使用点表示法定义派生特征 ──────────────────────────────── 2 part def Company { 3 part department : Department[1..*]; 4 // allEmployees 导航路径:Company -> Department -> Employee 5 feature allEmployees : Employee[*] = department.employee; 6 } 7 part def Department { 8 part employee : Employee[1..*]; 9 } 10 11 // ── 流语句中的点表示法 ────────────────────────────────────── 12 // 流不能只说"从 tank 到 engine"——必须命名 13 // 嵌套在每个端口内的具体流动项端点。 14 part def DriveSystem { 15 part tank : FuelTank { port fuelOut : FuelPort; } 16 part engine : Engine { port fuelIn : ~FuelPort; } 17 18 flow of Fuel from tank.fuelOut.fuelSupply // tank -> 端口 fuelOut -> 流动项 fuelSupply 19 to engine.fuelIn.fuelSupply; // engine -> 端口 fuelIn -> 流动项 fuelSupply 20 } 21 22 // ── 约束中访问嵌套属性 ────────────────────────────────────── 23 requirement def PowerBudget { 24 subject sys : ElectricalSystem; 25 require constraint { 26 sys.battery.voltage >= 12[V] // sys -> battery(部件)-> voltage(属性) 27 } 28 }
SysML v2 中的点表示法有精确的 KerML 语义:a.b.c 的每一段都缩窄了前一段产生的值的集合。链式特征是模型中一个真实的 Feature,其多重性由链派生而来——它不只是语法糖。
完整示例 — 多包系统
以下示例改编自官方 Sensmetry SysML v2 速查表,在一个完整的模型中演示了本模块的大多数概念:
1 package VehicleSystem { 2 private import ScalarValues::*; 3 private import ISQ::*; 4 private import SI::*; 5 6 package Definitions { 7 part def Vehicle { 8 attribute mass : ISQ::MassValue; 9 part engine : Engine; 10 part fuelTank: FuelTank; 11 port fuelPort: FuelPort; 12 } 13 14 part def Engine { 15 attribute power : ISQ::PowerValue; 16 port engineFuelPort : ~FuelPort; 17 } 18 19 port def FuelPort { 20 out item fuelSupply : Fuel; 21 in item fuelReturn : Fuel; 22 } 23 24 part def SportsCar specializes Vehicle { 25 part engine : TurboEngine redefines engine; 26 } 27 28 part def TurboEngine specializes Engine { 29 attribute boostPressure : ISQ::PressureValue; 30 } 31 } 32 33 package Instances { 34 private import Definitions::*; 35 36 part myCar : SportsCar { 37 attribute mass : ISQ::MassValue = 1400[kg] redefines mass; 38 } 39 } 40 41 package Requirements { 42 private import Definitions::*; 43 private import Instances::*; 44 45 requirement def MassLimit { 46 subject vehicle : Vehicle; 47 attribute massLimit : ISQ::MassValue; 48 require constraint { vehicle.mass <= massLimit } 49 } 50 51 requirement vehicleMassReq : MassLimit { 52 subject vehicle = myCar; 53 attribute massLimit : ISQ::MassValue = 1800[kg] redefines massLimit; 54 } 55 56 assert satisfy vehicleMassReq by myCar; 57 } 58 }
此模型演示了:包和嵌套、私有通配符导入、子分类(specializes)、重定义(redefines)、带有方向特征的端口定义、共轭(~)、特征链、约束、需求,以及定义/用法模式。
本模块总结
| SysML v2 概念 | KerML 来源 | 关键规则 |
|---|---|---|
package | Namespace(不特化 Type) | 无实例语义;仅用于组织模型 |
import | Import(一种 Membership 关系) | 默认是公开的;除非有意重新导出,否则使用 private import |
可见性(public/private) | 每个 Membership 上的 VisibilityKind | 独立作用于成员和导入 |
specializes | Subclassification 关系 | 仅用于定义之间;子类型继承所有特征 |
subsets | Subsetting 关系 | 用于用法之间;在继承特征旁并列添加命名子集 |
redefines | Redefinition 关系 | 用于用法之间;完全替换继承的特征 |
共轭(~T) | KerML Conjugation 关系 | 反转类型内 Feature 上的所有方向 |
多重性 [m..n] | Multiplicity Feature(拥有的子元素) | KerML 默认:[0..*];SysML part/attribute/port:[1..1] |
ordered / nonunique | Multiplicity Feature 上的属性 | 编码集合/包/序列/有序集合类型 |
特征方向(in/out/inout) | Feature 上的方向属性 | 仅在交互边界(端口、动作)处有意义 |
| 特征链(点表示法) | FeatureChaining 关系 | 多跳点表示法路径(如 a.b.c)构建具有从链派生多重性的派生 Feature |