内容简介:使用EA画类图
本文介绍使用EnterpriseArchtect进行建模是,类图的使用方法。篇幅较长,请慢慢阅读。示例中使用的EnterpriseArchtect的版本为13.0,其他版本的区别也应该不大,请自行调整。
准备工作
创建EA工程
启动EA后选择【New Project】菜单项,在出现的【New Project】对话框中输入文件名后按下【保存】按钮。
为简单起见,在出现的【Model Wizard】中不做任何选择,直接选取消。我们还是可以得到下面的工程文件。
注意右上角的【Modle】节点,就是工程的根节点。
创建View
在Model节点的上下文菜单中选择【Add】-【Add View】菜单。就可以调出CreateNewView对话框,具体如下图。
输入Name并选择Icon Style。这里不必太纠结,事后都可以改的,比如IconStyle就可以通过DemoView节点的上下文菜单中的【Set View Icon】项目修改。
创建类图
接下来我们从DemoView节点的上下文菜单中选择【Add Diagram】,调出【New Diagram】如下的对话框。
输入/选择必要的信息。
1.在【Diagram】栏中输入Class Diagram1
2.在【Select Form】列表中选择【UML Structral】
3.在【Diagram Type】列表中选择【Class】
点击【OK】按钮,就可以得到一个名为Class Diagram1的类图。画面大致如下。
添加类/删除类
添加类
添加新类的操作如下图所示从软件的 工具 栏中向类图中拖动Class图标。
释放鼠标后会自动出现类属性对话框。
在【General】分类页面中输入类名Person并确定Language中选中的是C++以后,按下【确定】按钮。画面会变成以下的样子。
这里选择C++有两个作用,一是决定生成代码的语言,二是有些选项(比如私有继承)会随着本设定而改变。其实也不一定要问为什么,做对的事情就好了。
除了左侧出现黄色的Person类方框以外,右上部分的Model树上会出现一个Person节点。严格来讲,这个Person节点才是我们在模型中增加的那个类。左边类图中的Person只是一个链接。
删除链接
为了说明这点,我们可以删除类图中的Person类,这时画面会变成下面的样子。
类图中的Person类虽然不见了,Model树上的Person还好好的在那里。
粘贴链接
我们再将Person节点拖到ClassView中,这时会出先【Paste Person】对话框。
目前的【Drop as】项目的选项是Link,会在画面上增加一个Person类的链接。
现在回复到了删除前的状态,没有损失任何东西。追加说明一点,在ClassView中有了Person类,如果继续上面的操作,EA会拒绝。
粘贴实例
这还没有完,我们继续向ClassView拖动Person节点,但这次我们选In
stance(Object)增加一个Person类的实例,画面变成下面这样。
请同时关注ClassView的变化和Model树的变化。我们可以继续增加实例,增加一个实例,Model数的节点也会增加。它们目前名字相同,但是是不同的实例,这个场景下,就是不同的人。
我们还可以通过属性对话框来修改实例名,由于篇幅和流量的原因,这里省略。
粘贴子类
到这里还没有完,我们继续向ClassView拖动Person节点,但这次我们选Child(Generalization)增加一个Person类的派生类,画面就会变成下面这样。
你大概注意到了,软件为我们可以自动添加了泛化连接线。子类也可以重复添加,每次都是增加另外一个子类,虽然目前的名称相同,但是都是不同的子类,这一点可以从Model树上看到结果。
删除类/实例
选择Model树上的对应节点,打开上下文菜单,选择【Delete ‘???’】即可,这回可是真删,要慎重!
添加类属性
属性是OO中的一个词汇,在C++语法中,应该叫数据成员。这里我们尽量使用OO中的属性一词。
打开属性窗口有两种方法:
1.双击类图中的对应类框图,选择【General】以后,点击【Attributes】按钮。
2.从模型树中选择对应的类节点,打开上下文菜单并选择【Attributes】菜单项。
无论哪种方法都可以打开下面的属性设定对话框。严格讲这并不是属性自己的属性对话框,而是属性和操作共同的对话框。本文只关注属性部分。
添加新属性的操作主要是在红绿两个矩形框中进行的。我们在这里只说明有(zhi)关(dao)的项目。
基本项目
基本项目通过红框中的列表控件来设置。
Name:变量名,可以自由输入文本。
Type:数据类型,可以自由输入文本,也可以点从下拉菜单中选择。在下拉菜单的最下面,还有一个【Select Type...】选项,提供了从工程中选择类型的功能。但是选择的结果也只是作为文本保存,起到的作用仅限于输入辅助。如果你期待变量类型和被参照的类型名联动,那你想多了。
Scope:从下拉列表中选择,一共有四个选项:Public/Protected/Private/Pack
age。Package也是可选的,表示也没有问题。但是在生成代码时当作Public处理。
Initial Value:为属性设置初期值。可以就地输入或打开对话框输入。区别不详。
扩展项目
属性列表中有属性被选中时,可以设定该属性的扩展属性。
Static:表明该属性是静态属性,或称静态数据成员。
Property:定义Property操作方法(Getter/Setter)。具体画面如下:
Const:定义常量数据成员。
下面来看一个实例:
在本例中创建了三个属性(扩展项目部分省略表示):
1.私有的int型变量m_age,并为其设置了Proerpty属性。
2.保护的string变量m_name。
3.静态,公开的int型变量MARRY_AGE
作为上述操作的结果,类图变成下面这个样子。
请注意观察在类图中属性的表达方式。另外也可以看到SetAge和GetAge两个方法。
关于初期值的补充说明:
在基本项目中有一个初期值InitialValue需要补充说明一下。
在C++11之前这个项目只能应用于静态常量数据成员。
在C++11以后,可以为每个变量设定缺省值。当构造函数的初始值列表中没有为数据成员制定初始值的时候,编译器会利用这个缺省值来初始化数据成员。
添加操作和方法
也许有人会问,操作和方法不是一回事么?还真不是一回事。
操作
操作指明了目标对象状态的转换或返回给操作调用者值的查询。它有名称和参数列表,包括返回参数。操作指定了行为的结果,而不是行为本身,行为可以是一个方法,一次状态机转换或其他。
方法
方法是一个过程,它实现了一个操作,它有一个算法或过程描述,调用如果解析为一个方法,将导致该过程被执行。
以上是ULM2.0对操作的方法的说明。你看懂了么,反正我是琢磨了好一会。那我就举个例子吧。
说有一个驾驶者基类,它有有两个派生类,分别是车主和小偷。驾驶者类声明了一个启动汽车的操作,车主类使用(实现)的方法是拧车钥匙,小偷类使用(实现)的方法是直接接发动机电源线(电影里常有的)。
怎么样,好点没,下面继续今天的话题。
属性窗口的打开方法:
1.双击类图中的对应类框图,选择【General】以后,点击【Operation】按钮。
2.从模型树中选择对应的类节点,打开上下文菜单并选择【Operations...】菜单项。
无论哪种方法都可以打开下面的操作设定对话框。严格讲这并不是操作自己的对话框,而是属性和操作共同的对话框。本文只关注操作部分。
添加新操作的操作主要是在红绿蓝三个矩形框中进行的。和上篇文章一样我们在这里只说明有(zhi)关(dao)的项目。
基本项目
基本项目通过红框中的列表控件来设置。
Name:变量名,可以自由输入文本。
Parameters:在参数设定部分详细说明,此处省略。
Retun Type:数据类型,可以自由输入文本,也可以点从下拉菜单中选择。在下拉菜单的最下面,还有一个【Select Type...】选项,提供了从工程中选择类型的功能。但是选择的结果也只是作为文本保存,起到的作用仅限于输入辅助。这一点和属性类型一样。
Scope:设定操作的可视性。从下拉列表中选择,一共有四个选项:Public/Protected/Private/Package。Package也是可选的,表示也没有问题。但是在生成代码时当作Public处理。
Stereotype:可以设定一些分类信息,比如property set,property get,或者constructor等。
扩展项目
操作列表中有操作被选中时,可以设定该操纵的扩展属性。
Concurrency:用于自定操作的并发属性。可能的选项有:
sequential:同时只能有一个调用发生。如果并发调用发生,则结果不保证。
guarded:允许并发调用发生,但同时只允许一个调用执行。
concurrent:允许并发调用发生,并保证可以正确地并发执行。
Virtual:用于指定抽象操作(虚函数)。
Static:表明该操作是类操作(静态函数),而非实例操作。
参数设定项目
参数定义主要是通过蓝框中的【Parameters】表单来进行的。可以定义多个参数,并设定他们的属性。方法和类设定属性的方法基本一致,此处省略。
我们试着为Person类追加了两个方法,一个是静态方法GetMarryAge,一个是虚函数Show。Person类变成了如下的样子。
可以看到静态方法GetMarryAge的下面有一条横线,而Show操作被表示成斜体。这就是UML中静态方法和抽象操作的表达方式。
如果这还不够,还可以再往前走一步,生成代码。在Person类上点击鼠标右键调出上下文菜单选中【Code Engineering】-【Generate
Code】可以调出如下的生成代码对话框。
在选择路径之后,按下【Generate】按钮,我们既可以得到以下代码。
首先是头文件
然后是cpp
不做重复的事情,这才是正确的方法应该有的样子。
模板和泛型编程也是C++中很重要的一部分,相信很大一部分 程序员 都用过某种容器类。但一般来说也就是用用而已,并不会自己构建类模板或者在建模中使用类模板。
类模板
接下来介绍EA中类模板的创建类模板和使用类模板的方法。
创建类模板
假设我们要创建一个映射类(假设而已,可别真去创建)MyMap,它有两个参数,一个是Key,一个是元素T。
首先创建一个普通的类,设定类名为MyMap。
一定有人在输入类名的时候直接输入MyMap<class Key, class T>,这时候生成的类图就像下面这样。
看起来也是那么回事,但是并不能生成正确的代码。所以还是回到原先的轨道上来吧。只要输入MyMap就好。
接下来选择【Templates】分类,并在【Template Parameter(s)】列表空间中增加Key和T两个参数,Type都指定为class。
按下【确定】按钮返回后,类图会变成下面这样。
生成的代码如下:
使用类模板
作为例子接下来利用MayMap实例化一个类名为PersonMap的类,负责管理从整数到Person*的映射。
首先创建一个普通的类,名为PersonMap。
从工具栏的【Class Relationsships】组里选择下面图标。
然后从PersonMap类向MyMap类拖动鼠标,以建立两个类的连接关系。
鼠标双击《bind》连接线打开属性对话框并选择【Binding】分类,然后按下【Add】按钮在【Parameter Substitution(s)】列表中添加参数。
如上图所示,Formal列可以选择Key和T参数。它们都是在MyMap类模板中定义的。继续操作,指定Key和T参数的内容。
类图会变成下面这样。请关注红圈中的变化。
对应代码如下。
生成的有效代码很少,但这确实是正确的代码。在UML中这种方式叫显示绑定。
关联入门
在定义了类属性,类操作等限制在单个类内部的内容之后,接着说明类之间的关系,今天是关联的基础篇。
什么是关联
关联是两个或多个特定类之间的关系,它描述了这些类的实例之间的连接。在问题陈述中,关联经常以动词(或动宾)形式出现。
比如学生和老师之间的关联,如果以学生为起点,老师为终点,那么这种关联就可以称为获取知识(AquireKnowledge)。如果以老师为起点,学生为终点,那么这种关联就教授知识(TeachKnowledge)。有教就有学,一体两面。
关联本质上都是双向的。但是在读的时候要按从起点到终点的方向来读。
下面是AquireKnowledge在UML中的表现方式。
关联就是连接Student类和Teacher类之间的那条线,上面带有关联名AquireKnowledge。下面介绍关联的表示/设定方法。
在增加关联关系之前,首先打开类图并增加两个类:Student和Teacher。
接下来点击工具栏中的Associate图标(如下图),然后在Student类上按下鼠标并拖动鼠标到到Teacher类后释放。这里的方向是有意义的,拖动开始的类就是关联的起点。
在生成的直线上双击鼠标以打开如下的AssociateProperty对话框。
在名称栏中输入AquireKnowledge,同时确认右边的属性列表,可以看到Source项目的内容为Student,Target项目的内容为Teacher。
接下来选择Role(s)分类,在SOURE和TARGET两边的列表中都可以看到Multiplicity项目,这个项目叫多重度,后面会讲到,先都输入选择【*】。
这样就可以得到本文一开始的那张图了。
多重性
多重性指定了一个类与其关联类的单个实例可能相关的实例数目。也不知道为什么这种定义总是那么难以理解。还是结合上面的例子来说明吧。先假设这里的一个类是Student类,那么它的多重性就指定了一个Teacher类的实例可能与多少个Student类的实例相关。
多重性的标准格式为:minimum..maxmun,minimum和maxmun都是整数,maxmum也可以是“*”,表示无限多。例如:[1..*]就表示1个到无限多个。
区间还可以一个单独的整数来表示。
先看下面的例子。
有两个多重度的设定值。首先Student类侧的1,表示的是一个Book类的实例只能和1个Student类的实例相关。Book类则为[0..*]表示一个Student类的实例可以和0到无限多个Book类的实例相关。
关联端名
我们也可以给关联的两端指定名称,例如在上面的Has关联中,可以指定Student端的名称为owner,指定Book端的名称为belongings。
关联端名的设定也是通过下面的AssociationProperty对话框来进行的。
设定关联端名以后,类图就变成下面这样。
关联端名一般以名词出现,大多数场合关联端的命名会比关联的命名更容易一些。一旦指定了关联端名,就可以省略关联名。
有序性
昨天的文章算是关联的基本内容,不大好理解,但是非常重要。对于面向对象的建模,识别类当然是第一步,接下来就是要识别类之间的关系,也就是关联。可能会觉得有点虚,但是这是设计向上游发展的表现,请务必认真体会并加以练习。
当某个关联端的多重度被指定为一以上时,并没有强调这些对象是不是有序的,也没有明确对象的值是不是可以重复。这样的建模结果不够精确。其实很多场合是需要确定这些信息的。在UML中,把这种信息成为有序性,有序性关键词可以放在属性串的后面。
下面就以一个事件处理系统为例来说明。
首先是按照发生先后处理事件的情况,这时候事件是按照发生的时间次序排列(Ordered=True)的,又因为同样的事件可能多次发生,所以队列中的值是可以重复(Allow Duplicates=True)的。这种情况UML称之为{sequence},类图是下面这样的。
另一种情况是按照事件优先级进行处理。这时候需要两方面的信息。一个是EventHandler,管理所有发生的事件,这些事件是无序(Ordered=False)的,允许重复(Allow Duplicates=True)的;另一个是优先级信息队列EventPriorityQueue,这个队列管理的是事件的优先级,是有序(Ordered=True)的,不允许重复(Allow Duplicates=False)的。EventHandler向EventPriorityQueue询问优先级后按照结果处理事件。EventHandler的情况UML称之为{bag},EventPriorityQueue的情况UML称之为{ordered}。以下是类图。
接下来说明这两种信息的设定方法。进入关联端的设定对话框后,通过下图红框中的项目,就可以分别设定是否有序和是否允许重复的选项了。有一点需要注意的是,只有在指定了多重度以后,设定结果才会在类图中表示出来。
一共有两个设定项目,四种组合,归纳起来就是下面这张图。
你一定注意到左下角的空白,UML并没有像其他三种情况一样给个说法,而是作为初始(缺省)状态,在这个状态下,元素是无序的(Ordered=False),同时每个元素又是唯一(Allow Duplicates=False)的。这实际上就是集合。
关联类
正如可以使用属性描述类的对象一样,也可以用属性来描述关联。UML用关联类来表示这样的信息。关联类(association class)是一种关联,也是一个类。
-----------UML面向对象建模与设计
也不知道你是懂了呢还是懂了呢?还先从一个简单的例子开始说起吧。
有一个温度控制系统,通过传感器测量温度。传感器的输出是1v到5v,对应的温度为0到100摄氏度。
控制器每0.1秒获取一次温度值,然后根据实际温度和期望的温度的偏差来决定输出值,计算的周期为1秒,输出值的范围为0%到100%。这个输出值发送给一台加热器来控制温度。
加热器的控制端在输入1v时的输出功率为0KW,输入5v是输出功率为10KW。
这个系统应该如何建模呢?先来第一步,识别类和关联。
接下来将全部信息都反映到模型上。
不要被满篇的属性吓倒,耐心地,慢慢地读下去,你会理解的。
传感器Sensor
传感器Sensor的功能其实就是将现实世界中的0度(m_tempLow)到100度(m_tempHigh)的温度经过线性变换转换成1v(m_outputLow)到5v(m_outputHigh)的电压信号。
控制器Controller
控制器每0.1秒(m_sampleCycle)获取一次Sensor的电压输出,将这个电压值从范围[m_sensorOutoutLow,m_sensorOutputHigh]线性变换到[m_tempLow,m_tempHight]之间,然后由控制器根据实际温度的和期望温度之间的偏差来决定输出值,计算的周期为1秒(m_controlCycle),输出值的范围为0%(m_controllerOutoutLow)到100%(m_controllerOutoutHigh)。这个输出值再经过线性变换变成一个[m_heaterInputLow,m_heaterInputHigh]之间的值,发送给加热器来控制温度。
加热器Heater
加热器的控制端接受到1v(m_intputLow)到5v(m_intputHigh)之间的电压值以后经过线性变换转换成0KW(m_outoutLow)到10KW(m_outputHigh)之间的功率。
谢谢你坚持读到这里。回头来审视一下模型,有没有发现什么问题?
是的,Controller类太大,功能也太多了。
这个问题的解决方案就是本文的话题:关联类。当关联足够复杂,复杂到必须需要利用属性来描述细节,利用操作来定义动作的时候就该关联类出场了。
在本例中取得温度值(GetTemperature),输出(SetOutput)两个关联,要进行线性变换,各自需要四个属性(实际上还应该有操作),都可以定义为关联类。就像下面这样。
前面废了那么多口舌,到主角的时候反而简单了。
如果要总结的话,关联类的内容就是补充描述关联的那些信息。
限定关联
从一个例子开始今天的说明。
假设有一个系统,收到外界的事件通知以后,根据设备Id,将事件转发给适当的设备。按照之前的说明我们可以建模如下。
系统按照以下方式运行:
EventCreater生成Event并设置DeviceId
EventCreater将生成的Event发送给EventDistributor
EventDistributor根据DeviceId检索对应的Device
将Event发送给Device
对于每一个EventDistributor,可能有多个EventCreator向它发送Event。也可能有多个设备接收由它转来的Event。
为了提高检索速度,我们将SendEvent关联的Device端的有序性设定为{ordered},即:结构有序,而且在这个列表中每个Device只能出现一次。
我们知道,有序性为{ordered}的数据结构,可以是数组,也可以是链表。查询是一般采用的线性查询。这种设计可以实现功能,而且被大量使用着。
怎么样,够了么?
应该有很多人想到了,还不够快,可以哈希表,B树嘛!对了就是这个。我们今天的话题:限定关联。利用限定关联以后,类图会变成下面这个样子。
注意观察EventDistributor右边的小框。这种表达方式就是限定关联。图中的EventDistributor和Device之间的SendEvent关联可以理解为:在EventDistributor中通过deviceId可以决定唯一的一个Device。
进一步讲,引入deviceId限定符以后,除了通过deviceId取得唯一的Device这件是意外,它还附带了另外的含义:应该让这种操作更有效率,差不多就等于要求采用更有效率的数据结构。
限定关联还是通过关联端属性设定对话框进行的。
图中有两处变化,一是红框中Qualifiers项目设成了deviceId,而是绿框中多重度从“*”改到了1。
泛化(generalization)
类和类之间,除了存在关联/聚合/组合这种协作关系以外,还有泛化关系,也就是C++中的继承关系。
定义
泛化是指一个较特殊的类到一个较普通的类之间的关系。较特殊的类也叫子类(subclass);较普通的类也叫超类(superclass)。子类继承了超类的所有特性(属性和操作),任何使用超类的地方,都可以用子类代替。
表示法
泛化表示为从子类到超类的实线,超类端带有空心三角形。
在本例中,File类的功能已经很完整,可以独立使用,但是我们需要支持文本文件和Utf文件的行读写功能,于是增加了两个子类TextFile和Utf8File,它们一方面完整继承了File的所有特性,一方面又为用户提供了利用者需要的读写文本文件和Utf8文件的便利功能。
这种泛化关系虽然可以满足利用者的需求,但是没有人会在使用File的地方替换使用TextFile或者Utf8File,而是把它们作为另外的类来使用。还有一点:很难找到漂亮的方法避免用户使用File类的Write/Read方法带来的混乱。可以说这种泛化是没有经过认真设计的泛化,或者说是被动的泛化。
抽象类和具象类
还有另外一种情况,在设计时就考虑好超类,子类的分工,共同的部分由超类实现,特殊的部分由子类实现。
在上图中,图形尺寸,位置的处理由Shpe类负责;表示的部分则在Shape定义Show操作,具体的Show方法由各个子类实现。因为Shape类没有实现所有的功能,所以不应该被实例化。关于这一点,UML提供了方法,就是将Shape定义为抽象类。在EA中表示为斜体的类名。设定方法是在类属性的【detail】页中,选中Abstract选项。具体如下图:
在这种场景下,我们称Shape为抽象类(abstract class),各子类为具象类(concrete class)。
实现(realization)
前面讲到了抽象类和具象类。其中抽象类是不能被实例化的类。这即可能是因为类的实现还不完整(如缺少某些操作的方法),也可能是因为功能不完整而不想被实例化。与之相对的就是具象类。
接口
但是一般来说,抽象类还是有一些功能(属性,方法)的。我们继续简化(抽象化),直到只剩下公开的抽象操作,而没有了属性和方法,这种状态UML有一个专门的名字:接口(interface)。
接口用来定义一组公共的特性和服务,是服务提供者和利用者之间的协议,定义接口的目的就是为了替换由不同的服务提供者提供的实现;抽象类抽取了具象类的共通特性,并通过具象类实现完整的功能。目的在于抽取共通而不是定义行为。二者的使用场景有很大的不同。
实现(realization)
具象类到抽象类的关系叫泛化,接口的实现到接口的关系就叫实现(realization)。
表示法
在类图中,接口和类的表示基本一致,只是在类名上多了一个《interface》关键字。实现则有两种表现形式:一是指向接口类的顶端带有三角形的虚线;另一种方式是带有《interface》关键字的依赖箭头。
尽量用左边这个吧。
更多更新文章欢迎关注微信公众号:【面向对象思考】
以上所述就是小编给大家介绍的《使用EA画类图》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Mechanics of Web Handling
David R. Roisum
This unique book covers many aspects of web handling for manufacturing, converting, and printing. The book is applicable to any web including paper, film, foil, nonwovens, and textiles. The Mech......一起来看看 《The Mechanics of Web Handling》 这本书的介绍吧!