智睿思维Logo智睿思维

MBSE建模学习之二:+-#~/^*都啥意思?详细说说属性

在MBSE的模型中,我们经常看到属性的定义中有“+-#~/^*”这些符号,它们可不是运算符,理解错了要闹笑话。请大家耐住性子看下去,只有理解了系统结构建模中的“属性”,才能记住这些符号的意义。

MBSE建模工作从哪里入手这个问题,往往都是刚开始对大家造成困惑的问题。如果从学习的角度,一般是从学习模型中各类元素的概念开始,先大概了解一遍MBSE模型中都有哪些元素、有什么用处、什么时候用。然后结合一个自己工作中的项目,把建模工作做一遍。如果按照正常的产品研发过程,一般按照“需求分析”—>“方案分析”—>“确定架构”这个过程,可能会依次用到需求图、用例图、行为图(活动图、序列图、状态机图)、包图或模块定义图、内部模块图、参数图。但是我们拿来当作“作业”的项目,也可能已经完成,也不妨直接从架构建模开始,先把系统结构模型化,然后逐步完善系统的行为模型、需求模型。这样从简到繁的过程,也许是更有效的学习方法。《SysML精粹》这本书的章节顺序正是这种方式编排的(推荐阅读)。

在上一篇文章中,介绍了表示系统结构(系统的整体架构,不是指机械结构,机械结构是专业模型,用CAD软件建模)模型的元素“模块”(Block)。我们在系统模型中,多次提到“元素”(Element)这个术语,这个术语也是UML标准中的一个概念。在UML\SysML模型概念中,“元素”是最基本的概念,模型中的其它概念都是从“元素”继承的,是所有模型概念的根(Root)。所以,我们把每个模型概念都统称为“元素”。那么继续说“模块”。“模块”代表我们设计的产品,以及任何层级的产品。当我们说明这个产品的时候,用它有什么“特征”(Feature,也是UML标准中的元素)来说明它。这个“特征”分两大类,一类表示它的结构的,称为“结构特征”(StructuralFeature);另一类称为“行为特征”(BehavioralFeature)。“行为特征”可以理解为一个产品有什么功能,它是如何工作的。我们在后续的文章中继续介绍“行为”这个概念,这里我们先说说“结构特征”。

“属性”(Property)的概念

“属性”(Property)是一种元素,它表示类的一个属性(attribute),或者一个关联(Association)的成员端(memberEnd);或者某些情况下同时都是。

先说说“Property”这个词的翻译,我们习惯把它翻译为“属性”,这个和“attribute”的翻译相同。“attribute”不是UML中的一种元素,它只是普通的一个词语;另外,“类”(Class)有“Attributes”这个属性,也就是它的所有“Property”。所以UML类图中,表示方框的“类”有分区的名称是“attributes”。为了区分“Property”和“attribute”,有些书中把“Property”翻译为“特性”。不过,在程序员中,已经很习惯把“Property”称为“属性”。所以我们还是按习惯把“Property”翻译为“属性”。两者区别不大。真要说相同和区别的话,如第一段中对“Property”的定义,就是当“Property”是一个类的“attribute”的时候,可以说两者相同,指的是同一个概念;当“Property”仅仅是一个关联关系两端的成员(memberEnd)时,这时候不能叫“attribute”。

产品的组成是有层级关系的。结合产品的模型,“属性”这个概念可以理解为下层产品的实例在上层产品实例中的名称及其它定义。“属性”可以指父级产品中具体的某一个下级产品,也可以指类型相同的一组产品。“属性”的类型除了具体的产品,也可以只是一个数据类型。在SysML标准中,表示产品的“模块”(Block)主要有四种类型的“属性”。“部件”(parts)、“引用”(references)、“值属性”(value properties)、“约束属性”(constraint Properties)。“部件”的类型一定是另外一种“模块”,而且聚合类型是“组合”;“引用”属性的类型也必须是“模块”,而且聚合类型是“共享”或无;“值属性”的类型是“值类型”(ValueType);“约束属性”的类型必须是“约束模块”(ConstraintBlock)。

“属性”(Property)的语法

“模块”的各种“属性”可以显示在包图或模块定义图中模块节点的“分区”(Compartment)中,以一个字符串的形式。这个字符串表示了定义“属性”的语法。“属性”完整的语法如下:

<property> ::= [<visibility>] [‘/’] <name> [‘:’ <prop-type>] [‘[‘ <multiplicity-range> ‘]’] [‘=’ <default>] [‘{‘<prop-modifier > [‘,’ <prop-modifier >]* ’}’]

用中文把上面的语法翻译一下如下:

<属性>定义为:[<可见性>] [‘/’] <名称> [‘: ’<属性类型>] [‘[‘ <多重性范围> ‘] ’] [‘=’ <默认值>] [‘{‘<属性修饰语>[‘, ’<属性修饰语>]*’}’]

上面语法中各部分说明如下:

<visibility>:可见性,表示属性可以被访问的范围。有四种:

+:表示属性是公共的(public),可以被任何模块的行为访问。在SysML标准中所有属性都是公共的,所以在SysML图中一般是不显示可见性的。但是“模块”既可以表示一个具体的硬件产品,也可以表示一个软件的模块。当对软件进行建模的时候,也还是可以使用“可见性”这个概念。

-:属性是私有的(private),仅仅被所属模块的行为访问;

#:属性是受保护的(protected),仅仅对模块及其子类中可见;

~:相同包内可见(package),也就是同一个包中的模块都可以访问这个属性。

‘/’:导出属性的符号。导出的属性,意思是这个属性是其它属性通过计算得到的。例如正方形的“面积”属性是“长”、“宽”属性相乘“导出”的属性。导出属性的值一定是“只读”的,在模块的实例中不能被其它行为方法修改。

<name>:属性的名称。名称也可以为空。为空的时候,可以用它的类型表示这个属性。

<prop-type>:属性的类型,另外一个“模块”、“值类型”或其它能作为类型(Type)的元素。类型也可以为空,表示可以是任何类型。

<multiplicity-range>:多重性。多重性是指一个属性在模型中的数量范围。它的表示方法是“[lowerValue..upperValue]”,翻译一下,既“[下限值..上限值]”。例如“[0..1]”表示最少0个、最多1个;“[4..*]”最少4个、最多不限;“[2]”上限、下限都是2,既必须2个;“[*]”不限个数,既0到多个。

=< default>:属性的默认值;没有赋过值的话,属性就取这个值。可以没有默认值。

<prop-modifier >:属性的修饰语,可以理解为“属性”的“属性”。可能有多个修饰语,每个可能的是如下的值:

<prop-modifier> ::= ‘readOnly’ | ‘union’ | ‘subsets’ <property-name> |’redefines’ <property-name>| ‘ordered’ | ‘unordered’ | ‘unique’ | ‘nonunique’ | ‘seq’ | ‘sequence’ |’id’|<prop-constraint>

‘readOnly’:表示属性是只读的。例如导出的属性就是只读的。

‘union’:表示属性是另外的子集属性的并集。

‘subsets’ <property-name>:表示这个属性是另外一个集合属性的子集。例如汽车“前轮”属性是“轮子”属性的子集,定义表示为“前轮:车轮{ subsets 轮子}”,表示2个前轮是汽车所有轮子属性集合中的2个。

’redefines’ <property-name>:重定义了某个属性。在上一篇文章中,我们介绍过“模块”是一种可以“继承”的“类”。作为“继承类”的模块自动拥有了“父类”的所有属性,这些父类的属性称为“继承”的属性。在继承的属性前面用符号“^”标识。继承的属性是父类的属性,不能直接修改它;如果要修改它的定义,必须新定义一个属性“重定义”它。这里“redefines 父类属性”就是这种意思。

‘ordered’:表示属性是有顺序的集合。

‘unordered’:表示属性是没有顺序的集合。

‘unique’:表示是集合的属性中,集合中每个成员都是唯一的。

‘nonunique’: 表示是集合的属性中,集合中每个成员不一定是唯一的。

‘seq’或‘sequence’:表示属性是一个有序的“袋”(bag)。换句话说,就是作为属性的集合,集合中成员是有序的、但不唯一。集合的类型,“无序、唯一”称为“组”(Set);“有序、唯一”称为“有序组”(OrderedSet);“无序、不唯一”称为“袋”(Bag);“有序、不唯一”就称为“序列”(Sequence)。

‘id’:属性是作为模块的ID属性,就是唯一标识模块实例的属性。例如“产品编号”。

<prop-constraint>:属性的约束,一般用“{约束表达式}”来表示约束。

通过上面对“属性”语法的解释,再总结一下“+-#~/^*”这一串符号的意思:

“+-#~”:表示属性的可见性,分表表示“公共的、私有的、受保护的、包内可见”;

“/”:表示属性是导出的;

“^”:表示属性是继承的;

“*”:表示属性的多重性,显示在属性定义“名称:类型”后面的“[]”中。

除了以上UML定义的属性的基本的语法之外,SysML为属性扩展了方向特征(DirectedFeature)。方向特征有三种取值:“prov | reqd | provreqd”,翻译为“提供|需求|提供且需求”。可以为除了流属性以为的其它属性添加这个特征(流属性也有方向特征,“进|出|进出”)。“提供”(prov)表示当前这个模块提供(写入)属性值、被别的模块使用(读取);“需求”(reqd)表示别的模块提供(写入)属性值、当前这个模块需要读取;“提供且需求”(provreqd)表示上面两种要求都有。

在MBSES软件中,属性的这么复杂的语法让每个人记住、直接输入是太复杂了一些。所以一般只是输入基本的部分:“属性名称:类型[多重性]”。其它语法部分通过属性框输入,简化了语法。属性的修饰语部分也可以通过属性框设置是否显示(对应模块元素属性框中“节点属性”—“特征属性”)。

另外,可以为属性添加构造型,增加额外的特征。例如,对于数据有分布特征的属性添加“分布属性”构造型,为属性增加分布参数。

下图显示了属性定义的示例。在示例中,“显示器”模块有三个通过关联关系生成的部件属性(screen、power-supplying和driver)。“24寸显示器”继承了“显示器”模块,所以自动有这三个部件属性。“显示器”的“产品编号”属性的类型是一个字符串,它表示产品的唯一编号,是产品的ID。“显示面积”属性是一个导出的属性,因为当显示器的“屏幕尺寸”和“显示比例”确定之后,显示面积就可以计算出来,所以不能再赋值。“24寸显示器”是从“显示器”继承的产品,它继承了显示器的所有属性(继承属性前面都有“^”符号,继承的属性在子类中不能编辑)。但是为了进一步定义“显示尺寸”这个属性,重新定义一个相同名称的属性,并重定义父类的“显示尺寸”属性。重定义父类的“屏幕尺寸”属性后,为这个属性添加了一个“正态”分布属性构造型,并设置构造型的“均值”和“标准差”属性值。

模块定义图中的属性

在模块定义图中(或包图中),如上篇文章所讲,“模块”节点有20多种分区,其中大部分是显示各种属性的。

除了在分区中显示属性之外,在关联关系的两端也会显示属性节点。一般情况下在一个图中不需要同时显示。

模型之间的关联关系有三种,“引用关联”、“共享关联”和“组合关联”。这三种关联生成的属性类型不同。“引用关联”两端的属性都是“引用”属性;“共享关联”两端的属性也都是“引用”属性,不过在关系末端(不是菱形箭头的端)生成的引用属性的聚合关系是“共享”;“组合关联”在关系末端(不是实心菱形箭头的端)生成的属性是“组件”(part),聚合关系是“组合”。如果关联是双向的,则两端都生成属性;如果是单向的,仅仅在箭头端生成属性。这个属性的类型,就是靠近关联端的模块。在这个关联端属性节点中,只是输入属性的名称,不需要显示类型。“共享关联”和“组合关联”在靠近菱形箭头的这一端的属性(双向关联时才会产生),默认的多重性是“[0..1]”,可以不显示;另外一端的默认多重性是“[1]”,也可以不显示。多重性是其它情况的话,都要输入、显示。例如在“显示器”模块中的三个属性(screen、power-supplying和driver)就是由“组合关联”关系生成的部件属性。

在下面这个图中,显示一个分区中的“值”属性(“price”值属性的值类型有一个单位“元”)、两个关联端的“部件”属性。“hardDisk”属性是一个“部件”属性(由组合关联产生),多重性是“[1..4]”(关联端上的多重性不显示[],如果不是默认值1,需要通过属性框输入上、下限才会显示)。“monitor”属性是一个“引用”属性,多重性是“[1..2]”。它表示计算机可以有1~2个显示器(显示器也可以同时连接其它计算机,所以可以建模为共享聚合关系。)

内部模块图中的属性

仅仅在模块节点的分区中显示属性,只是显示属性本身的定义。模块的结构定义,更重要的是各个属性之间的关系。内部模块图的作用和意义就是显示一个模块的属性之间的连接关系。

在内部模块图中主要显示三种类型的属性:部件属性、引用属性和值属性。部件属性外框是实线,引用属性是虚线,值属性也是实线的方框。

方框中显示属性的定义。属性的“可见性”和各种修饰语也可以通过属性节点的属性设置是否显示。属性的“多重性”一般是显示在节点右上角。

在内部模块图中的属性节点,除了可以显示对应类型的模块的各种分区之外,还可能会显示一个“初始值”(initialValues)分区。初始值表示这个类型的模块在实例化的时候,作为类型的模块的各个属性优先取的默认值,也就是说它会取代属性定义的“默认值”。添加初始值可以通过属性节点的右键菜单“添加”—“属性值”。

在模块的内部模块图中中添加属性,和在模块定义图中在模块的分区中添加属性效果是一样的。在打开一个已有属性的模块的内部模块图时(在模块定义图中,选择模块的右键菜单“打开内部模块图”),如果这个模块以前没有内部模块图,会自动新建一个内部模块图,而且会显示已有的部件属性和引用属性。如果在已有的内部模块图显示已有的属性,可以把属性元素从模型浏览器中拖拽到内部模块图中;或者先建立一个属性节点,然后通过属性节点右键菜单“选择已有属性”来建立节点和已有属性的关联。

属性节点可以显示内部模块图代表模块的直接属性,也可以显示间接的下层属性。显示间接的下层属性的时候,属性名称用“.”分隔的多层属性名称,表示间接属性的关系。也可以直接通过嵌套的属性节点显示多层的属性。下图显示“host:主机”属性中“mainboard:主板”属性的两种方式:

内部模块图主要还是为了表示属性之间的连接关系。属性之间的连接关系,用一个“连接器”(Connector)来表示。“连接器”可以仅仅表示两个部件之间有一个关联关系,也可以表示物理的“连接器”部件。连接器上可能还传递“项目流”。“项目流”是特定类型的信息流,它是一种关系元素。项目流可以单独在模块定义图中定义,也可以在连接器上直接定义(通过连接器节点的右键菜单“选择项目流”,在选择窗口中直接增加项目流)。连接器和连接器上的项目流两端的类型和方向都必须是相容的。

如下图所示,主板通过一个“连接器”连接到显示器。在连接器靠近显示器这一端也有个“连接器端”的元素,它的“多重性”是1,表示连接器连接的显示器是1个(虽然整个计算机可能有两个显示器,但是一条连线只能连接其中的一个显示器)。在连接器中传递的“项目流”的类型是“显示信号”,它的方向从主板到显示器。“显示信号”是一个元素类型为“信号”(Signal)的信号,在后面的图中有定义。

上面是表示部件属性“mainboard:主板”和部件属性“monitor:显示器”之间通过一个连接器连接关系的模型。我们可以继续细化这个模型,其中有几个点需要细化。两个部件连接的标准是啥?通过什么接口连接?作为连接器的连线是否也需要在模型中体现?假设我们的需求是通过一条符合HDMI2.0标准的连接线连接主板和显示器。为了表达上述建模要求,在主板和显示器上分别增加一个“端口”(Port),端口的类型是一对共轭的接口模块“HDMI2.0接口标准”。我把还增加“HDIM2.0标准线”关联模块,并作为“计算机”模块中的一个连接器属性。用到的这些类型定义在下面的包图中。

添加接口和连接器类型的内部模块图如下所示。在“mainboard:主板”属性中增加一个端口,类型为接口模块“HDMI2.0”;属性“monitor:显示器”中增加一个端口,类型为共轭接口模块“~HDMI2.0”。连接器的类型可以选择为关联模块“HDMI2.0线”,两者的方向以及两端的类型必须是相同或兼容的。