捅破窗户纸:如何从过程到对象—For金色的海洋以及所有为面向对象而困惑的Tx
鄙视OO的也进来鄙视我吧。望OO达人多多指正。
前头有一篇关于对象持久化的。不过很多Tx,连OO都不理解,那么持久化也是空谈。
我们首先抛弃千篇一律的什么对象来源于生活,是真是对象的程序表现的屁话,空洞,对于一个一接触程序就开始过程,将严谨的过程渗透到骨髓里的工程人员来说,跟他扯这个简直是对牛谈琴。所以我们从过程来讲对象,然后来看如何将过程化的思维方式转化到对象式的方法论。以及过程抽象的弊端以及为什么要在复杂系统里OO。
我们不能否认,我们在描述一件事情的时候,可以按照过程的方式进行抽象。打个比方。
=======================
将一个表单存入数据库
=======================
按照过程的方式描述。
1.一条条读取表单上字段的值
2.根据值构建SQL语句
3.创建链接
4.创建命令
5.执行命令
6.关闭链接
基本上也就6步就完成了,没有多余的步骤。同意?
那么接下来我们把这个命题扩展到两个表单乃至N个表单,那么这个过程就会N倍的扩张。于是过程达人们就开始动脑筋了。于是开始了第一个迭代的抽象(根据怪怪的理论,我们认同过程的抽象)。我们把构建SQL语句的部分过程抽象出来成了单独的过程-[构建Sql语句]。再一看,我们其实对执行SQL都可以抽象,于是将3,4,5,6都抽象出来,于是SqlHelper诞生了。
这就是过程抽象的方式,不可否认其实我们很多人都经历过这个阶段。而且对此很为困惑。如果所做的很简单,过程很短,那么看起来过程的抽象方式确实更加容易理解。但是我们来看看过程的噩梦在那里。
我们继续扩充命题。
现在我们提交一个表单需要提交到两个数据库。于是我们的N个表单又产生更多的子过程。随着单个过程的长度增加,我们抽象出来的子过程还可能抽象出子过程,结果抽象出来的子过程呈级数增加。直到你的脑袋爆掉。
还有一个问题就是,我们抽象出来的子过程放哪里?C#是一个面向对象的语言,所有的过程(或者函数,方法)都是从属于类,于是就出现了一个巨大的类包含无数静态方法的怪胎。
那么,我们来看面向对象。面向对象和过程有什么联系呢。其实程序最终的运行还是按照过程的,类的作用就像一个框框,指定了那个过程应该属于那个类,必须在存在对象的时候才能调用还是在类中调用。之前在过程的时候抽象出的一大堆子过程杂乱无章。但是在用面向对象的方法所抽象出来的对象确实井然有序的。
对象在执行的时候是按照一定的时序和过程在执行(所以在UML里有时序图)。但是我们不能够因为计算机是按照过程在执行就否定了OO,因为OO是设计时的概念(所以只有OOA、OOD、OOP而没有OOR【面向对象执行-自我发明的词,不存在此物】),而OO的过程是在设计的时候自然而然的产生的,既然OO的语言都存在,那么在这么多年的实践中,证明OO的方法是行之有效的。
下面我们来看如何OO。
用一个很简单的例子,聊天室,不用数据库的,数据记录在内存。
虽然聊天室比较简单,但是我们还是通过用例分析来构建业务逻辑的领域模型。首先我们来找用例。下面是我们对聊天室业务的描述:
建立一个聊天系统,用户在登陆后方可发言,用户可以看到所有人的公共发言和其他用户发给自己的私聊信息,用户可以发布公共信息和单独发送私聊信息给某个用户。用户可以自己创建聊天的房间,并作为聊天的主持人拥有对房间的控制权,可以踢人出房间。用户可以加入任何一个房间开始聊天。
我们通过上面的描述可以得到两个粗略的实体:用户,房间。然后画个用例图来看看:
然后我们分别建立这几个用例的用例卡片:
创建房间的用例
用例名称 |
创建房间 |
用例描述 |
用户创建一个自己控制的聊天区域 |
事件流 |
(用户已登陆)用户输入要创建的房间名称后系统创建一个新房间,创建房间的用户自动进入房间 |
备选流1 |
(用户已登陆,已经创建了一个房间)提示用户一次只能创建一个房间 |
备选流2 |
(用户未登录)按钮无发点击 |
进入房间的用例
用例名称 |
进入房间 |
用例描述 |
用户选择一个聊天区域进入 |
事件流 |
用户进入一个聊天区域后获取当前公共聊天区域的信息, |
发布公共信息的用例
用例名称 |
发布公共信息 |
用例描述 |
用户向其他所有用户发布信息,所有的用户都能够收到 |
事件流 |
(用户已登陆)用户输入要发布的信息后将信息发送给所有用户 |
备选流1 |
(用户未登录)不能发布 |
发布私聊信息的用例
用例名称 |
发布私聊信息 |
用例描述 |
用户给指定用户发送消息 |
事件流 |
(用户已登陆)用户选择要发送消息的用户和要发送的信息后将信息发送给指定用户 |
备选流1 |
(用户已登陆,要发送的用户不存在)提示用户要发送的用户不存在 |
备选流2 |
(用户未登录)不能发布 |
踢人的用例
用例名称 |
踢人 |
用例描述 |
创建房间的用户将选定用户踢出房间 |
事件流 |
(用户已登陆,是房间创建者)用户选择踢的用户,改变该用户的状态为被踢 |
备选流1 |
(用户已登陆,但不是房间创建者)看不到此功能 |
备选流2 |
(用户未登录)看不到此功能 |
我们根据上面的用例描述基本覆盖了大部分业务的流程。我们在其中抽象出下面的对象:
l 聊天系统
l 房间
l 用户
l 消息
l 消息区域
现在我们来分析这几个对象之间的关系,首先是聊天系统,因为是多人的系统,所以比如说创建房间的行为,虽然是用户发出的,但是实际上的创建者和拥有者却是系统,我们需要一个容器来容纳所有的对象。所以其实换一个思路来说,系统对象就相当于一个大楼,而房间就应该在大楼里。因为系统里有多个房间,所以系统里应该有一个房间对象的列表。
因为房间是有名字的,所以为了方便检索,我们需要将房间的名字作为索引。因为我们要从系统找到房间,就需要提供一个查找房间的行为,而且系统也要提供房间的列表。最后我们得到了一个类图:
然后我们来看房间,房间包含在系统中,被用户通知系统来创建,在创建的时候要给房间一个名字,以方便用户来找到这个房间。房间里有什么呢?房间里有人,也就是在交谈中的人,所以我们要在房间里准备椅子,也就是聊天用户对象的列表。因为是不见面的聊天,所以每个人都将要说的话写下来(一个消息),然后如果是给所有人看的,就贴到公共的留言板上(消息区域),如果是发给某个人的就把消息交给某个用户,所以房间的程序(房间对象)需要两个处理消息的行为,一个处理公共消息,一个处理私人消息。最后将用户清除出房间的行为也需要房间来执行(因为用户包含在房间里,所以根据封装的原则,这个行为只能由房间来执行,当然可能不一定是房间发起的)。最后我们得到了类图:
接下来我们继续分析用户对象。因为很多动作都是用户主动发出的。首先用户对象必须实现所有用例上的行为:
l 创建房间
l 进入房间
l 发公共信息
l 发私聊信息
l 踢人
然后再来分析用户的属性,首先用户在一个房间内,那么用户需要有一个房间的属性,在进入一个房间就具备这一个房间的属性。然后我们还需要一个属性用来表示用户的状态。还有就是用户收到的消息需要一个地方来存,所以需要一个用户消息的存储区域,所以需要一个消息池。最后得到类图:
消息池对象是属于用户和房间的,算是一个公共的结构,作用就是用来存放消息,并且从特性上来说是一个栈结构的容器,也就是FIFO队列。而由于这个类的对象的生命周期都是由房间或者用户来维护管理,所以基本上属于内部对象,这里也就不用画关系了。消息池需要几个行为:添加消息、清除消息两个方法。这两个方法主要出于封装上的需要,主要是用来提高整个对象的内聚减小耦合。类图如下:
最后是消息对象,消息对象基本上算是DTO对象,用来传递信息的,所以只需要值属性就可以了。
最后我们初步的通过分析整个业务逻辑的问题域得到了体现领域模型的类结构。我们在之后会继续修改完善这个模型,因为如果我们在某个过程的实现当中需要操作另外一个对象的内部成员的时候,那么我们就需要在目标对象上增加属性,或者是增加方法,这里对于值对象我们推荐用属性,而对于一些需要操作的复杂对象,比如列表,我建议使用方法。
最后我们实现这几个类就OK拉。