分享:面相对象的建模(封装业务层)

我一直想写这篇如何构建一完整系统的文章(从需求采集-分析-设计-实施-重构-集成--测试),目的是从思想上改变编程方式(而不是从某个技术上改变,如果是这样,最终会变成形而向上的东西了),因为所有的行为都由思想驱使,技术相对是硬性或形式的东西(容易掌握但也容易变化),思想却是软性而实质东西(不容易掌握但也不容易变化)。

一直没有动手的原因:一来是没有时间(借口吧),更重要的是我不知道如何写,因为很多思想性的东西我不知道 如何用文字或语言来描述(程序员的表达能力公认是不怎么样的),但在工作中经常看到一些程序员写代码,他们写代码真的很任性。我并不是对他们有什么鄙视或什么不好的意思,我相信他们已经尽自己的所能做到最好的了,我只是希望在这个信息从未如此发达的时代里,他们应该能做的更好(早些年,网络发展初期,在电脑上有个api帮助文档就很满足了)。

在构建我们的业务层的前,须要搞清楚几件事:

1.搞清楚业务(这个对于初级程序可能有些困难),它是整个系统的灵魂或基础,对业务中的每个过程(场景)需要有一个初步的认识,我习惯对一些比较复杂或想不透的业务过程用图形的方式描述出来 (写文档是一方面,另一面是能过写文档把脑海中的思路理清),如果这块没有搞清楚,我想相信你会在后面死的很难看(我试过,也看过别人试过),这里我也就不多说了,这里给个建议:不要紧着写代码,三思而后行

2.搞清楚边界(广义上的接口,它是建立在业务之上的),这个和建表的道理很相近:保持业务功能的单一性(这也是设计模式中的原则之一)

我们这个时代,工作分的越来越细,因为分工协作对处理大型而又复杂的工作很有效率,同样我们的业务层也应该是这样,把每一个实体类当成人一样对待,对每个实体分派不同的功能,然后运用语言自身的特性(interface、abstract、 class、public、delegate)实现之间的交互、组合。依此类推,组件之间,层之间,系统之间。(可以把业务层当成一个组件来做也可以)

这里所说的边界不一定是指代码中的interface或com或其它形式化的定义,它是种思想或概念,当我们熟知它后,就知道在什么时候用interface、abstract、 class、service.....形式化的语法, 而它在我们生活中无处不在(门,窗,电器中的控制器或控制面板,车站,电话.等等)

3.搞清楚什么是视图逻辑和业务逻辑,不要混为一谈,这样就知道那些代码应该放在那里了

4.搞清楚对象与类的关系(这个本是基础知识,本不想写这点的,还是提示一下吧)

下面构建业务模型:

依据业务,整理出业务实体关系图(设计与初步实施的阶段):帮助我们从整体上观察业务的合理性,减少由于业务需求变化对工作效率的冲击。

这个是我其中一个觉得做的比较理想中的一个项目的业务关系图(这个图已被改过很多次了,在开发过程中会一直的修改,因为客户方的需求很不稳定),箭头代表对另一端的业务实体的引用(或看作地址指针或数据表中的外键),对于每个实体中具体有什么字段或功能,我们可以在脑海里有个大概就行。这里的关注点在:业务的每个过程是否都可以映射(满足)到业务关系图中的某个或部分结点。

这个工作好象在设计数据库,你可以这么想,但去实施它时,我会先去写Class,而不是去建库建表。为什么啦,在前期需求不稳定的情况下(可以是客户方的问题,也有我们理解上的问题),而另一方面业务层是依赖于数据层的,如果数据层发生了改变,业务层也要改变动(如果视图是同步实施与集成的话,还要改视图层的代码,一般情况下这个阶段不会对视图的逻辑代码的实施,一般是样式效果的设计),如果我们先从业务层着手的话,上述的问题对我们的代码就影响很小了。在我看到的初级程序员都习惯从数据层下手,最后可能会变成业务层形同虚设或两极分化(在视图层写业务的逻辑代码或在数据层写逻辑代码,早期的桌面时代比较偏重于后者--存储过程+视图)或变成数据访问组件的代替品。当然,如果觉得整个业务层与业务过程都很相近了,十有八九都不会变了,那就直接建表也没有问题。

从业务层下手带来的问题:

如果我们的业务层初步完成时,需要与视图进行集成,出一个演示版本给客户看,以确定最后产品应该是怎么一个样子时,由于这个时候还没有数据库,怎么办;

我的办法是实体类中方法中返回虚拟数据, 更新-》insert (..... ){  new Class(....)  ; return OK ;  } , 查询-》GetObjects(...){  list.Add( new(...) , new(...) , ...  },

可以做一个容器对象当作临时的数据库来用。等到给客户演示完后,核心需求比较稳定的时候再建库(不是一定的,还是依据情况来定),而且这些代码可以变成业务层的测试代码


设计完业务关系图后(主干部分),下来就是设计每个实体类的属性和方法(支叶部分)。

这一块主要分成两块,

@业务部分:把业务拆分成多个子过程,对每个过程的思考分析,推断出过程的需要存储的属性

@代码实施部分:下面内容基本上都在描述这个部分,这个部分主要是如果把具体的业务过程转换成产品代码,并能适应视图层、数据层,第三方组件或系统的对接或集成,以及代码的复杂度,逻辑完整性的适度。如果把这部分单一的拿出来思考 ,很容易,当需要考虑到各方面的适合度的时候就比较难处理了(就好象一帮人分配分钱一样,那个人分的多或分的少都会引起混乱)

类成员:表示类内部定义的所有元素,如属性,方法--构造,事件等等,包括实例和静态的

###实体类设计基本原则###

类的内部要求是高内聚,与外部的关联要求是低偶合的

如何做:

所有与当前类相关(更新与查询)的数据及操作的代码都必须写在这个类里面,反之不相关的则不可以写,只能引用 

通过构造方法的参数来指定与其它的关联

外部类与之相互,都必须调用静态成员来实现相互过程(包括更新和查询)

(这里说的模糊,下面会进一步说明)

(这里最好分清一下

@对象的生命周期(这个应该好理解)

@逻辑生命周期:业务实体对象的逻辑生命周期或叫做记录的生命周期:记录被创建到修改到删除的这个过程 , 它不是一个连续过程,但是一个先后过程,这个概念会与设计产生一些影响)

###属性分为###

持久属性:与数据表中一至,需要保存到数据库中的数据

辅助属性:(状态、格式化、外部(上下文)关联的对象,或理解为持久属性之外的属性吧)

###视图应用层(服务层)###

依据视图的要求,对实体对象进行组合或过滤,返回视图所对应的数据(控制数据包的size和复杂度),它是视图的服务提供者,且内部的方法是独立存在的,类似window的底层的API,方法之间没有直接的关系,如web service , 视图层的后台代码(我不太喜欢那种html与后面代码混写的方式,如asp,jsp,php前端写法,因为这样写法对相互的依赖十分大,如果要更换视图层,或增加视图的展现设备,如增加移动客户端,这样基本要重写一次视图层及服务层),如果服务层及视图相互部分没有设计好,也会带来这种不良影响,但这个内容不在我的讨论主题


###成员可以分成实例和静态,还有调用范围之分(如:public protected , private ,internal 等) ,我们在实例成员及调用范围的基础上,再分成可两个类别###

@可序列(属性,视图使用的成员)与非序列(非public属性及方法,服务层用到或只允许内部访问的成员)

Code:#属性:Name{ get:() ,  protected set() } ,  #方法:GetFullName(){ return this.Name +  this.CreateTime.ToString()  }

控制序列:因为实体对象之间关系就象一颗树一样,任意一个对象可以访问到任意一个对象, 并且对象与对象存在双向关联 , 如果我在设计实体类时,需要在序列的层面上阻断这种关系,以防止在序列时进入无限循环(不同于死循环,可理解为三角恋吧),开发工具很难察觉这个错误;或者把整个系统结构都序列出来,这个可不是我们想要的结果。

@矢量(把计算后的结果保存在变量中,调用时则直接返回结果--贪婪算法)返回与动态(时间计算)返回结果

(我不知道如何描述才能清楚,我说一下这个划分的作用是:业务层是处在视图与数据层之间的一个连接及加工桥梁,现实中想象成一个建在一座桥上的加 工厂,或流水线上的一个机器手,在业务层与视图层一般还有一个应用层(服务层)  )

(以上的都是一些概念性的描述,至于如何用,需要依据业务情况和视图情况来确定,没有什么公式性东西,如:可以把业务层的代码写到应用层,就是说在实体类与服务层或其它的代码都放在同一个dll中,或分散到不同的文件中去,这样做没有问题--这更多是与部署相关,但你在的脑海中存在这种概念的划分,是知道那些代码或类是属于你概念中的那一部分内容,如果这样,我觉得把在文件中没有分层也没有问题,只不过后期的代码维护和部署会有问题)。

说一些扩展案例:有一次的中途接收某个项目,打开代码文件,文件分类及类命名都做的很好,一看就知道每个类是做什么的,一目了然,我心想这次终于不用帮别人擦屁股了,后来进行第二阶段的开发后才发现我被它的外表给骗了,业务过程基本上是不完整的,业务层基本上调用一下数据库就返回给视图,大部的业务逻辑及第三方组件都集中到视图层(疼啊)。说真的上一手开发人员能做到这个份上我还真很佩服他们的。


随便说一下,中间会产生一些业务过程的分析文档,这里我觉得对理解主题的内容帮助不大,这里就不说了(属于需求分析的范畴)

如果上述的一些概念都基本理解了,我说下去代码具体实现:


上图为这个项目的类图,

###项目介绍###

这个项目是用html+css+ js + C# + mssql2008 开发的,分为web端和app端

没有用到第三方框架(用的是自己的一个框架),也没有用到aps.net的控件(为什么 不用,我后面会进行解释原因)

视图与服务层使用 AJAX +  json + 自定协议 进行交互

数据库只创建了表,表只有主键,没有建立外键关系,没有视图,没有存储过程(这样做考虑有二:@方便更换数据库,@分布式数据库)

大概知道一下就好,我找了一个代表的类来说

这个项目主要有三个角色:管理员、老师、学生 。

public abstract class Member : EntityBase //在库中只有一个表,记录了三个角色的基本信息,Member类是所角色的基类

public class Manager : Teacher

public class Teacher : Member

public class Student : Member

我主要讲解Member这个类,其它实体类形式上都基本上是一样的

EntityBase :主要是一些底层的功能:如:数据访问接口,信道接口、业务的基本配置信息,全局的业务ID标识创建

这样子,我分成三个部分来讲

@更新的操作

#创建及删除:定义为static ,方便外部调用这些操作

public static Result Insert(Member newMember)

 public static Result Insert_Regist(Member newMember, int status)

public static Result Delete(Member theMember)

#修改:由于修改操作是对当前对象内部分数据的改变 ,所以做成了实例方法 , 如以下访求的定义

public Result Update(string loginName, string loginPwd)

 public Result Update(string loginName, string loginPwd, string newPwd)

(可以发现,所有的更新操作都返回Result对象,因为一个更新操作需要做很多事,并不是上来就持久这么简单,所以每个处理都会返回不则的信息,如:验证,调试信息,是否返回对象,是否延迟返回等等的信息,来指导调用方的下步的处理;不是一个简单的int 就可以解决的 , 其实它也一种接口形式。另一个原因是要保持输出的类型的统一性和有效性(在任何情况下都是如此,否则在调用方看来就会觉得太随意了,他不好对结果进行定向处理),查询也是一样)

(另外,某个对象的更新,都能很方便被当前实体类的其它成员捕捉到,而又不会影响到其它的对象,主要是方便维护,修改代码无须考虑外部因素,这样出bug的可能性大大降低,即使随着系统复杂不断提高,代码的复杂度也不会提高)

@查询的操作

public static Member GetByID(string id) //因为这种面向对象的做法在性能上存在很大的问题,很多开发者都在这里受限,从而被逼放弃面相对象的阵地,这个方法对于解决这个问题起到关键作用;我把这个问题说完吧,不想留到后面说了,我把代码贴出来:


其实这个方法里有个缓存的作用,如果依据ID找到对象后并缓存起来,下次再查找同一个ID的对象时则返回缓存中的对象;

(注意,如果是分布式的或多进程的部署,就不能在当前进程中进行缓存的操作处理了,这里就要做须要一个接口与第三方的全局缓存组件进行对接,如mencache或monsql)

注意缓存器是statice的,在更新操作的时候会对这个缓存器进行同步的更新(因为所有的更新操作都在类的内部实现的),这样就表示缓存器的对象与数据库某条记录是一至,那么这样做与上述问题有什么关系呢,我们来看看,无任是修改还查询,都是对实体对象进行操作的,比如我想要查询一个对象集,但问题是底层返回是一个记录,这样查询多少打记录就要进行转换的多少次对象,并且这样查询是很频繁,如果用缓存,并且保持对象的更新与记录是同步的,那么,我们在进行转换前看看这个ID记录是否在缓存内存在,这样就免了转换的工作了,性能自然就提上去了。后面的进行实体对象的组合操作也容易多了。

另外,我留意到很多开发者在这里会这样做,如果视图需要一次返回多个表的查询对象集合,通常的做法一个联表查询,这样与实体类产生差异(无法进行转换),要么做多一个视图对象,要么直接返回数据集给视图,前者的处理还好点(进行了二次组装),后者看上去很方便,但存在数据完整性不可控的问题(出现了问题就会很不好处理),这样返回给视图层的数据结构会是扁平的,而面向对象应该层叠的有层次的,即使返回给视图层的也是如此。如果你能用扁平的结构解决上述的问题,并能控制代码的复杂度的话,这样也没有问题(但我尝过,似乎做不到,就象把多个问题交织在一起容易处理还是把问题分而自治的处理一样的道理)

面向对象的的结构应该是复合形的或立体的,我们可以通过某个对象访问到间接关系的对象(整个对象图有点想一个树一样,每个叶子视为一个对象,树枝视为对象之间的关系,每个叶子通过树枝的关系能访问到任何位置的其它叶子)


public static IList<Member> GetMember_NotSubmit(WorkersLessonFixup theFixup)

internal static int GetMemberCountByOrg(Organization theOrg)

public IList<Courseware> GetCoursewareByComments() //实例方法

最后 一个查询为实例方法,其它都是静态方法,有什么区别呢,实例的查询方法是依据当前对象的来进行查询的,如查某职员的工作记录,和查询某个部门下所有职员的工作记录,都是返回工作记录,但调用对象是不一样的,一个是通过某个职员对象调用自己的工作记录,一个是某个部门对象调用所有职员的工作记录

另外需要注意的是:方法的参数,有些开发人员习惯把条件参数定义为一个ID值,这样会有点问题,无法确定ID的有效性,因为我这里的对查找的返回要么是对象(返回null或有效对象),要么是集合(即使查询失败,也会一定返回有效的空集合),如果传入一个ID还要在方法内进行验证ID的有效性(而且这种验证代码会大量重复),出现问题如何返回,如果查询条件不是主ID值,而对象的其它属性值(可能是参考值),如有效时间区间或当前对象处理某种状态时再进行查询操作等。如果直接传入一个对象(只要是任意一个对象都获取与之相关的对象数据,因此,这里输入的不是一个单纯的对象,它还包括了其它关联信息),验证的问题在外部分就被处理了,再说验证的工作本就不应该由这个查询方法去做。(所在确定边界很重要要,层的边界,类的边界,方法的边界,无处不在)

@外部关联:我们通过这种关联把每个对象联系,最终形成一个对象树,当然这只是一种方式,方式还有很多种,但要保持代码的一至性,最好选择一种。

在当前Member对象其中有一个关联是:当前member对象是属于那个Organization(组织)对象的,这里的Organization类也是一个抽象类,而在外部使用时都是使用Organization的类型对象,如果在其它对象中需要判断Organization是那个类别的组织,可以能过Organization对象的类别属性来区别。

因为Member对象依赖Organization,如果我在创建或构造一个Member对象时必须指定一个Organization对象,这是创建Member对象的必须条件,如果下代码:

 protected Member( Organization the )

下面是内部的关联对象的访问:

public Organization GetMyOrganization()

 //获取当前成员所属的组织 ,通过个实例方法就能获取当前Member对象所在的组织信息,为什么做方法而不是属性呢:主要是视图显示一个Member对象时只须要显示它的组织名称,我用了一个OrgFullName做为属性来显示当前Member所在组织名称,另一个原因是想阻断上向序列,可以看一下关系图,如果继续向上序列,那整个数据包会很大了

如果某视图确实需要知道所在组织对象的所有信息的话,我只需要继承下当前的Member类,创建一个视图类,然后在Member类下定义一个static查询方法(返回的仍旧就Member类型,而不是继承类型,这样做是要保持边界的统一性,至于调用方想知道对象的子类型,在调用方去处理---《给你做什么,你就吃什么》),由需要的对象去引用就可以了;如果想更方便一些,又不怕代码乱的话,就直接在Member类下加上个Organization对象属性也行。

构造函数:protected Member(Organization the)  //强制在构造时就要指定一个Organization对象,保持任何时候都可以关联到所属的组织

注意到这个构造函数是protected,想要在外部New一个实例的话,须要调用 一个静态访求来创建一人新实例:

public static Member New(Organization org, E_MType mType)

 \\如果外部创建 一个Member对象的话,除了传入Organization对象,还要指定当前创建Member的类别,前面的代码已看到Member类是一个抽象类,如果要new 则只能new 它的子类,所以通过第二个参数来确定创建那个子类的实例。

基本上每个实体类都不能直接去new ,需要通过一个外部的静态方法来创建实体对象,这样做只是想区分一下是内部new 还是外部new 的一些控制,即使没有区别,也要控制一下,你不能保证,那一天需要返回它的子类对象(如视图改变了, 版本控制,数据表改变了:可能是拆分或合并的情况,这些情况者有可能创建子类)

原则:如果想要改变逻辑,扩展优先,修改代码次之(也就是改变逻辑,但不改变代码)


基本的设计思想分享到这里了。(同一个功能,一百个程序员可能有一百种设计)

附上源代码的下载地址:http://download.csdn.net/detail/bo111/8902321


接来说一下,第三方的框架,

我曾在项目接触过一些通用框架  如:EF(实体框架),Microsoft.Practices,J2EE--JDFrame,Asp.Net MVC , php-CpdeIgniter

还是很多我不知道一些框架,这些框架都很强大,至于开发效率是否能大副度的提高,我保留我的意见,我接触过用到第三方面框架的项目,这些功能占用率都不是很高(没有超过30%,有的5%都没有用到),为什么会是样,问题有很多方面,这里不多说了

我想说的一下Web系统中(如果是桌面应用系统就没有什么问题),用到的MVC框架, 

MVC这种设计思想,更多是站在开发者一方考虑问题的思想,它的关注点是:把视图与视图逻辑进行分离,把UI人员的工作与后端开发人员的工作分离出来(如果是小项目,UI和代码实现都是现一个人),为了达到这种目的,需要以性能代价来换取代码的可控性(反正现在的硬件都很便宜,计算速度也很快),这种技术通过在服务端对页面进行动态重构,最后转换成html,返回给浏览器。有的开发者为解决这种行性能上的问题,使用AJAX技术,的确能有很好的作用,但代码的复杂度却随着功能的增加而增加(因为Web MVC 与Ajax的代码风格完全不一样,即使把Ajax技术整合到MVC的框架中,问题也依然存在,只不过问题的程度不一样)。

我只是想说明一下有这种问题,但工具用的好不好,不在在工具自身,而是受使用者来决定,还要看当前情况来决定。

无任那种框架,业务层还是需要自己去构建的。

最后说一下:构建业务层也好,构建系统也好,设计要简单明了,即容易上手,也容易扩展和维护。 

祝你好运!




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《UML精粹:标准对象建模语言简明指南》是一本介绍UML(Unified Modeling Language,统一建模语言)的精华指南。UML是一种通用的建模语言,用于软件开发过程中的需求分析、系统设计、系统实现等阶段。该书的目的是帮助读者快速理解和掌握UML的核心概念和基本语法。 这本书的核心内容包括UML的基本概念、UML的主要图表和符号、UML的建模过程,以及常用的UML建模工具。通过简洁明了的语言和丰富的示例,读者可以轻松地理解UML的基本思想和应用方法。 在书中,作者首先介绍了UML的起源和发展历程,然后详细解释了UML的三个主要视图:结构视图、行为视图和交互视图。结构视图主要描述系统的静态结构,如类图、对象图等;行为视图主要描述系统的动态行为,如活动图、状态图等;交互视图主要描述系统的交互过程,如时序图、通信图等。读者可以根据不同需求选择合适的图表进行建模。 此外,本书还重点介绍了UML建模过程中的重要概念和技巧,如模型元素的定义和关系的建立,以及如何有效地利用UML进行系统设计和分析。同时,书中还提供了大量的实例,帮助读者更好地理解和应用UML。 总之,这本《UML精粹:标准对象建模语言简明指南》是一本简洁明了、实用性强的UML指南书籍,适合软件开发人员以及对UML感兴趣的读者阅读。通过学习本书,读者可以快速掌握UML的基本知识,并应用于实际的软件开发过程中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值