类图可以用来展示类的结构和与其他类的关系,是一个重要的结构建模的工具。 01:
学过UML的人对类图想必都不陌生,作为结构建模的核心工具,类图充当着沟通现实世界与概念世界的桥梁。没有学过UML的同学也不必担心,在开始正文之前,我会先简要介绍一下UML和类图的基本概念。 什么是UML? 通俗地讲,UML就是统一建模语言(Unified Modeling Language)。如果说咱们平时用的汉字是帮助日常沟通的,那么UML就是帮助我们将现实抽象化,对其中的结构和行为进行建模。 举个简单的例子:你是一个人,现在,你对另一个人说话。在这个过程中,你使用了一种名为交流的功能。而交流,是需要你的喉咙、鼻腔、舌头、口腔和嘴唇共同完成的,我们可以把它们统称为发声器官,而喉咙这些小的部件就是发声器官的结构。 功能是由结构驱动的,而功能在UML中属于行为建模,由用例图承担。产品经理所熟知的用例模板其实是用例图的补充说明,当然这一块不在本篇文章的讨论范围之列。我们要讨论的,是承担结构建模的工具——类图。 上图是一张典型的类图,由三部分组成(由上自下):类名、属性、方法。 如果你是程序员出身,大概对这些很熟悉了——string是字符串,integer是整数,void为空,这些都代表属性的数据类型。至于"+"和"-",则代表着公有和私有的区别。 如果你不是程序员,也不用担心,暂且抛开数据类型和底下的方法,只保留类名和属性就好。 你可以忽略前面的加号,也可以把它当成一种惯例接受,这都不影响你对类图本身的使用。 其实类图本身很简单,它本身只是把类具象化的一种工具。关键是要明白它为什么存在?有什么意义?要怎么用? 要解决这些问题,首先就得明白什么是类。 通俗地讲,类就是现实世界的事物在概念世界的映射。 在理解这句话之前,我们需要明白一个隐秘的常识,即所有思维层面的活动都依赖于概念进行,我们不是在对实物思考着;而只是通过把它抽象化成概念,用概念在抽象世界中运行着。 当我们思考人的时候,我们是把人作为一种概念,存储在自己的脑海里。我说你是一个人,但人本身在现实世界是没有对应物的,你是人,他也是人,但你不是他,我无法用他来指涉你。 因为实体本身的制约,我们不得不把你和他抽象为一种共同的存在,用这种存在来指涉拥有共同特征的存在。这种存在就是类,而类本身就是一种概念。 拿人来说,人就是你、我、他在概念世界中的映射。换句话说,人就是类。 要知道,我们本来是没有办法思考实物的,因为实物就在那儿,我们不可能把它变成我们脑海中的物质材料,像厨子一样做出一道思维大餐。 可是通过类,我们突然可以分析真实世界了,只要我们将它们抽象化,映射在概念世界就好。 如果我们了解到这一点,就该知道,我们无论要解决何种问题,首先要做的事,就是把问题抽象化、概念化。 当然,当我们将实物抽象化以后,就该用一种可视化的方式将其呈现出来,一来为了与共事者交流,二来我们也可以更轻松地进行逻辑推导——类图就是一种很好的可视化方式。 如上面所讲,简化版类图包含两点:类名和属性。 之所以有类名,是为了方便人们称呼它,使这个类从虚无中显现出来。我们无法称呼一个没有名字的存在,更无法去思考它。 毕竟,最虚无缥缈的东西,老子都要强曰道之后,我们才能去探究它。 而光有了名字还不够,不同的两件事物,完全可以有相同的名字。譬如你可以叫张三,我也可以叫张三,但我们俩其实是完全独立的存在。 概念也是如此,单单把"人"提取出来,而不给它任何定义,那么我们怎么能知道"人"和"动物"这两个类的区别是什么?而属性,就是给类下定义的方式。 人这个类,有名字,有年龄,还有性别,职业,婚姻关系,住址…… 而动物,则有物种,有年龄,有性别,有栖息地…… 通过这些属性,人与动物得以区别。 可是,如果你仔细品味一下的话,会发现人和动物的属性有重合的地方。诸如年龄、性别什么的,人和动物都有啊。 为什么呢? 这就涉及到类与类之间的关系:继承。 类与类之间是有简单和复杂的区分的,而复杂的类往往继承自简单的类。譬如你是你父亲的儿子,你比他懂得更多,但你也仍然继承了他的一些特点。 而人和动物,都继承了来自简单类——生物的基本属性。 人与动物,在继承自生物这块的属性上,大同小异,而真正使他们做出区分的,是独属于其本身的那部分属性。譬如人的婚姻关系,动物的栖息地。婚姻关系是社会形态下出现的新型关系,动物是无法演化出来的。同样,动物的栖息地属于族群概念,与人的住所(涉及所有权问题)又有天壤之别。 试想一下,倘若栖息地转化成住所,那么动物的概念就不再适应,而必须演变成一种新的类:家养宠物。正是通过属性的不同,类本身的区别才能进行精准的回答。 有的读者看到这里可能会疑惑,我知道了类,对我有什么用呢? 这个问题的回答本来在开头就应该阐明,但考虑到有的同学并不了解类是什么,因此放到第一部分的结尾来解答。 理由有三: 类是一种抽象建模的思维方式,它充当现实世界和概念世界的接口;使用这种方式,能让我们在世界观的层面上革新我们的方法论。 类属性对于构建产品的信息结构有重要作用;一个产品呈现何种信息,取决于产品制作者对于类属性的识别和定义。 类的识别决定着后端的数据用几张表储存,类属性则决定着表上按照何种方式进行索引,或能否可以索引到,数据库的建设关系产品稳定性以及后续的数据分析;如果产品制作者在一开始对类的定义模糊,那么后端在不清楚业务逻辑的情况下所采取的存储方式往往会不如人意。 如果看到这里你觉得有必要进行类的学习,那么可以继续看下去,第二部分我会着重分析一下如何进行类的定义。 02:
这一部分主要谈类的定义。 其实在定义之前还有一个步骤,即类的识别。但在多数的场景下,识别是一件自然而然的事情。譬如你看到一个发光以照明的存在,会把它识别为灯;看到四轮驱动的存在,会把它识别成车。 这些都已经是我们的经验所内化的近乎反射的能力,唯一需要注意的,或许只是类的粗细层级。譬如你识别出照明物是灯,但灯也可能是白炽灯、LED灯等等。后者继承自前者,但又具有自身的特性,这时候就需要你结合自身的业务场景进行合理区分。 如果灯是商品,那么识别到白炽灯这一层级是有必要的。如果灯只是作为场景中的辅助道具,譬如"你回到家,打开灯";在这个场景中,灯只是为了照亮屋子的辅助道具,就没有必要区分是白炽灯还是LED灯。 如果你把后者识别到白炽灯的级别,那么后端就不得不存储多余的一列以区分它与其他灯的区别,从而造成了信息冗余和资源浪费。 因此在识别类的过程中,要记住两点: 确定类之间的继承关系; 围绕业务场景确定使用哪个类,两者为前后顺序。 识别完类之后,就涉及类的定义了。 类的定义通过定义类属性表现出来,运用最典型的例子就是游戏中的人物属性。 (图一) (图二) 图一是射击类游戏的人物属性面板,该人物属性包括角色类型、生命值、防御力和战斗力; 图二是RPG类游戏的人物属性面板,该人物属性包括角色名称、等级、生命、内力、基础属性、详细属性和战斗力。 同样是对于人物的类定义,可是两者的属性定义却截然不同:在射击类游戏中作为原子单位的防御力;在RPG中就要分为物防和法防,并且后者还多出了内力、物攻、法攻等其他属性。 如果你已经弄懂了类的继承,可能会说,它们各自在简单类——人物的基础上发展出了新的类,图一是枪械师类,图二是弓箭手类。 当然有道理,可是值得思考的是:为什么图一没有发展出枪械亲和力、开枪速度这样的属性呢?为什么仅仅有一个战斗力属性呢? 一个重要的原因在于:属性本身也是有粗细的。 粗属性可以分解为细属性,细属性也可以分解为粗属性。属性的粗细与否,跟产品的核心路径有关。譬如上面的射击类游戏,玩法本身不依靠属性加成、削弱,所以识别到战斗力属性即可;而RPG游戏,则更依靠属性间的配比,所以识别更细致一点。 但这并不意味着在进行属性识别时,一开始就要从粗的来。MECE原则告诉我们,要从不同角度穷尽可能性。如果一上面就识别粗的层面,很有可能漏掉很多细节。 因此,最好的方式,是一开始将所有细的属性全部识别出来,再结合产品逻辑进行整合。这样做虽然更累,但也会使你对类本身进行更深入的思考。 上面我们谈到MECE法则,该法则的第一要点就是从不同角度。意思是,我们在进行头脑风暴时,依靠穷举法,最后总结归纳;但一味地穷举并不能很好包含所有可能性。 有时候往往只是一个方向上的多重反复。因此在进行穷举之前,必须要先想好一共有哪些角度可以进行思考。 类也是一样,我们必须思考定义类的属性,本身都有哪些类别,其依靠何种方式而对定义类有所贡献。 总体而言,类和属性都可以有两种分法: 类:实体类、抽象类; 属性:指涉自我、与其他元素交互。 所谓实体类和抽象类,差别在于它们在现实世界有无对照物。 譬如水,水本身在概念世界中是一个抽象化的概念,但它在现实中有具体的实指——一种可以喝的、看得见、摸得着的存在。因此,水是实体类。 而如知识,本身是看不见摸不着的,它没有实物的依存;只能通过概念世界产生,并且只能在概念世界运行。因此知识是抽象类。 区分实体类和抽象类的重要意义在于:确定属性穷举的基本维度。 实体类的基本维度是时间和空间。 水,从时间角度思考,可以确定其流速;从空间角度思考,可以确定其体积。 抽象类的基本维度是聚合。 知识本身是虚无缥缈的,但它有类型,可以分为语文知识、数学知识、物理知识;抽象类不是识别出来的,而是聚合出来的。 它是你遇到实类之后,根据业务需要,抽象出来的统筹概念。就像知识一样,如果你的产品中有一个模块,需要放语文、数学等各科内容,那么这时候你就需要抽象化出知识的概念作为内容的统称。 不妨这样理解,抽象类是一群实体类的最大公约数,它建立在先识别并定义好实体类之上。 接下来看属性。 所谓指涉自我,其实就是事物本身蕴含的,不需要借助其他元素呈现出来的状态。 桌子的属性,可以是面积、新旧程度、占地空间、材质,这些都是它本身自带的。 但是诸如品牌,是桌子与生产商交互的结果;诸如承重力,是它与放置其上的事物交互的结果。没有其他元素的参与,桌子本身无法实现属性的生成。因此,像品牌、承重力这些,则属于与其他元素交互。 实际上,现实中的绝大部分事物,指涉自身的属性很少,绝大部分的属性都是在和其他元素交互中产生的。 拿人来说,身高、体重、姓名、年龄是指涉自身的属性,但社会关系、工作状态这些,却都要借助其他元素生成。 交互属性是属性中最难把握的一块,因为它没有固定的痕迹,所生成的属性取决于交互的对象。就像氧气一样,遇到氢气,可以变成水;在吸热反应下,可以变成臭氧。 因此,涉及交互属性时,着重点是要根据业务需求,找到所交互的对象。 还是拿商品举例子,如果桌子是一件商品,那么它的交互对象只需要限定在买卖双方:从卖家的角度考虑,需要为桌子指定价格,指明库存量;从买家的角度讲,需要知道桌子的品牌、大小、承重力。 当然,买家角度的属性还包括了指涉自身的属性,但从更广延的角度考虑,我们是可以用关系链的视角去看待以上的问题。 另外,也许你已看出了,指涉自身的属性一般都属于实体类的视角:时间和空间。而交互属性则有点抽象类的意味。 实际上,这两类四种视角,本身既有重叠,又有不同。关键是要结合业务,确定类别,再依据自身与交互确定方向。 类图本身当然还有讨论的空间,以上也只是一家之言。限于笔力和经验,本篇文章到此就告一段落了。 希望有经验的伙伴能分享自己的经验,对文章中的谬误之处也请不吝批评。 感谢阅读。