论坛首页 Java企业应用论坛

续数据建模 vs 对象建模后,OOAD 过程中的一些问题!

浏览 7411 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2004-11-24  
robbin写道:

引用
你可以自己写一个Persister类来映射存储过程。

不会有两种建模的,应该只是业务建模,至少在开发阶段如此,在将来的维护升级阶段,特别是数据库查询优化的时候,会有一些数据建模的工作需要做。我自己做设计的步骤如下:

分析软件需求,以用户的角度来使用软件,找出发生的scenerio,抽象成为一个一个Use Case,分析出Use Case之间的关系,这一步是非常重要的,这一步做好了,设计就成功了一半。Use Case的抽象有一些可以遵循的原则,这里不详细谈。

然后用语言描述每一个Use Case,描述用户使用一个Use Case发生的主事件流以及异常流。

这样就完成了需求分析阶段。

接下来做概要设计,针对每个Use Case,读Use Case的描述,看事件流,找出所有的实体类,这也有一些可以遵循的原则,例如找出所有的名词,画表格排除等等方法。

然后分析实体类之间的关系,是包含,聚合还是依赖,是1:1,还是1:n,还是其他....,根据这些关系,就可以得出实体类和别的实体类想关联的属性,然后再找出每个实体类本身重要的属性。

然后再次分析Use Case的事件流,一方面check实体类的设计是否合理,另一方面你可以找出动词,分析对实体类的控制逻辑,这样就可以可以设计出业务控制类,一般你可以一个实体类一个控制类,也可以业务逻辑相关的实体类由一个Facade Session Bean(非EJB含义)来统一控制,这里面的控制类的颗粒度就由你自己来掌握了。一般来说先可以设计一些细颗粒度的控制类,然后再按照模块,用粗粒度封装细颗粒度的控制类,提供给Web层一个Facade。

然后你可以画序列图,就是用序列图来表达事件流,在这个过程中,你需要不断回到类图,给控制类添加方法,而序列图就是控制类的方法调用。

至此,你已经在Rose里面完成了概要设计,当然你不可能一次设计完善,会有很多次迭代,因此你不能一开始把类设计的太详细,只抓住主要的属性和方法,特别需要注意的是,是抽象的设计,不要用具体的编程语言来表达类。


然后你就可以抛开Rose了,转到Eclipse+Togehter里面,根据那些类,规划一下package层次,然后在Together里面进行类的详细设计,所有需要的属性一一写上,当然你还是不可能一下把所有的属性方法写全,不过没有关系,把重要的写好就行了。

然后类框架已经生成好了,给所有的实体类加上xdoclet,然后生成hbm,然后用Hibernate的ExportScheme生成DDL,运行一遍自动创建好所有的表。这样所有的实体相关类全部做好了。

你现在就集中精力把控制类那些方法里面的代码填写上就OK了,在这个过程,你会发现有些实体类缺属性,没有关系,加上属性,然后写好xdoclet,运行一遍,自动生成hbm,自动创建好表,然后继续写你的方法,也有可能你发现控制类缺方法,那么就加上。

基本上实体类就是getter/setter,和少量的实体相关方法,所有的控制逻辑都写在控制类里面。

最后你的软件就基本写好了,用Eclipse生成好一堆你的testCase运行测试,反复修改,除bug。

看看使用OOAD的设计思路,是多么的爽的事情阿!你只需要把精力放到Use Case的抽象,实体类的关系总结,控制类的归纳。而当你使用Eclipse+Together之后,你所需要写的代码只不过是控制类的方法实现代码,其他的都已经生成好了。另外可能需要写少量工具类。




我有几个问题想听听robbin的看法和大家的意见:

1。在分析和设计的过程中,画序列图的时候,个人习惯的起点是直接从与表示层相关的控制类(如:action )到业务逻辑层到DAO层到DBCOMMON层,试图将重点直接放在业务逻辑层和DAO层两层上面,也试图将三层的结构也表现出来.
   不知道大家起点是从哪里开始的,重点在什么地方?

2。在分析和设计的过程中,我试图得到是一堆有关联的实体类和三层中每层的控制类及控制类的方法.
   不知道robbin提到的"控制类"是指单纯业务逻辑控制类还是包括表示层,业务逻辑层,持久层三层中每一层的控制类?

3。在分析和设计的过程中,主事件流以及异常流将如何表达和表现出来?
   发表时间:2004-11-24  
我通常从表示层中的UI层到交互层的接口入手的,因为这最简单,用例的每次交互可以对应接口中的一个方法,几次交互就对应几个方法。
然后是交互层到服务层的接口。这层接口需要较多数量的用例才能比较好的定义出来。
接口定义出来后,就可以分头行动了。

这里说的交互层和服务层的区别是交互层和用例对应,提供有状态的服务,服务层无状态,通过更加通用的服务。

另控制类该是业务层的概念吧。起源于RUP分析中的边界类-控制类-实体类的划分。
0 请登录后投票
   发表时间:2004-11-24  
那你的业务逻辑层呢?业务逻辑才是用例的灵魂。
0 请登录后投票
   发表时间:2004-11-24  
已经有了接口。就可以根据方法的职责或返回驱动详细设计。
如果你是想知道如何设计业务层,那可是大话题了。
不过我可以简单的说说:
明确业务流程的动作主体,通常是party。
以业务流程作为骨架。
利用接口在业务流程中嵌入业务规则的扩展点。

注意去掉不必要的关联。
能够使用单向关联的就不要实现双向关联。
对业务对象进行分层来明确这种单向关联的方向。
.....
0 请登录后投票
   发表时间:2004-11-25  
to partech: 最近正在做一个东西的设计,正为这个分层的问题头疼,需要考虑的方面非常之多,包括remote,security,domainobject,persistence等,希望你能就此展开一下.

partech 写道

我通常从表示层中的UI层到交互层的接口入手的,因为这最简单,用例的每次交互可以对应接口中的一个方法,几次交互就对应几个方法。
然后是交互层到服务层的接口。这层接口需要较多数量的用例才能比较好的定义出来。
接口定义出来后,就可以分头行动了。
这里说的交互层和服务层的区别是交互层和用例对应,提供有状态的服务,服务层无状态,通过更加通用的服务。


这也是我的思路,从UI入手,层层划分,直到DomainObject,每一个层次的出现都应该是reasonable的.目前的层次大致如下(附一个例子,以做垂直切面):

UI [用户界面层]
即 Actions
比如用户管理的 "修改用户密码" 对应一个 action
public class ChangPassword implements action {
    private Interaction interaction;
    public String execute(); {
        interaction.changePassword(currentUserId, theUserId, newPassword);;
        return SUCCESS;
    }
}


interaction [业务交互层]
可以认为是下面一个层次业务方法的 Lean Interface (即,其中方法的参数可能多为ID,而非Object),
UI层可能local也可能remote访问.从减少remote调用次数的角度考虑,应该是一个Action访问一次交互层的方法会比较合适.
比如,上面功能对应为
public interface Interaction {
    public void changePassword(String currentUserId, String theUserId, String newPassword);;
}


business [业务方法层]
可以认为是用例交互方法的 Rich Interface (即,方法的参数已经转换为Object了) 这个层次需要关注操作权限,即,有必要表明 (Who do What on Which) 的语义.
比如, 上面功能对应为
public interface Business {
    public void changePassword(User currentUser, User theUser, String newPassword);;
}


domain [领域模型]
可以认为是包装比较通用的功能
比如,上面功能对应为(后台界面管理员改用户密码和前台界面用户改自己的密码都对应这个方法)
public interface User {
    public void changePassword(String newPassword);;
}


persistence [持久层]
是实现上述功能的持久层对象,每个pojo对应一个dao,这里dao从略.
public interface UserPojo {
    public void setPassword(String password);;
}


问题:
1.业务交互层
partech 写道

这里说的交互层和服务层的区别是交互层和用例对应,提供有状态的服务,服务层无状态,通过更加通用的服务。

我同意这个"交互层和用例对应,提供有状态的服务,服务层无状态,提供更加通用的服务"的划分方式.这有利于编程.
但拿ejb模式的经验来类比,这个层次如果是无状态的,可能会比较好(这里存在疑问,Gavin King自己又曾撰文为stateful-sessionbean的性能平反)
这里如何选择,还需探讨.

2.业务方法层
这个层次实际上是为权限判断的需要而出现的.因为:
其一,为了 remote 方便,之上的业务交互层的方法设计得比较 lean ,缺乏进行权限判断得足够数据支持.
其二,为了通用,其下的领域模型层方法是无状态的,即,它的操作中,大多缺乏操作者的语义.
我也觉得这个层次不伦不类,仅仅是上一个层次的 object wrapper,
但因为其上的业务交互可能需要remote,所以,将这个很rich的层次合并到业务交互层是不合适的(rich方法可能需要load很多对象)
这里是否有更好的办法,还需讨论.

3.领域对象层
这个层次大家都说要有,但是目前又没有见到令人信服的实例.
它到底应该包含哪些逻辑.
是将field logic, relation logic, business logic合起来放在这里?
还是提供一系列的manager对象,完成pojo的cudq就可以了?
又或者直接用pojo+dao就可以了呢?
这个层次也比较让人疑惑.
0 请登录后投票
   发表时间:2004-11-25  
首先看看我的部署方案:
1web方案
客户端             [UI层前端(浏览器/js+html)]
                                 --[remote]--
web服务器           [UI层后端/交互层](保持状态)
                                 --[remote]--
后台服务器           [通用业务服务层/业务层/持久层]
                                 --[remote]--
数据库服务器         [数据库]

2RichClient方案
客户端               [UI层/交互层](保持状态)
                               --[remote]--
后台服务器           [通用业务服务层/业务层/持久层]
                              --[remote]--
数据库服务器         [数据库]

可以看出对应于不同的应用,后台的服务是相同的。
所以通用业务服务层同用例无关,这是业务后台通用业务服务不保留状态的另一个理由。
因为状态和用例的交互方式有关。同样的业务不同的用例将导致不同的状态。

关于领域层
通常对于更新业务对象的操作可以抽象出某个业务活动的概念。比如:开户,存钱,挂失等等。
这些业务可以使用类来表示。比如:OpenAccountAct,DepositAct。
通常业务需要执行才有效果,但是他们执行的具体内容都不同因此他们都重写了一个doRun()方法。
好了在OpenAccountAct中,我可以调用User.create(...)方法创建一个新的用户。
对于DepositAct也一样,我调用captialAccount.deposit(...)来完成存款。
仅从这里看你会觉得DepositAct有些多余,服务层直接调用CaptialAccount.deposit(...)不就可以了吗?
其实不然,比如现在有条业务规则是“挂失的帐户不能存钱”。
那么我可以在DepositAct中在调用doRun前插入一个接口CutomerAgrement 提供一个方法affirmCanDo(User user)来检查是否客户已经挂失。
而不是在CaptialAccount.deposit(...)中进行该项检查。因为影响客户存款的业务规则不是稳定,但是存钱的实质是稳定的。
当然可能会有人这样争辩“我在CaptialAccount.deposit(...)中也调用接口
CutomerAgrement的方法affirmCanDo(this.user)不就行了。”
ok,在目前的需求下没问题。
现在新的需求又来了“只有客户指定的了员工才能给客户作存钱业务”。
当然为了讨论我刚才是没有把最终CutomerAgrement的接口方法写对
最终的应该是下面的方法签名affirmCanDo(Act act)是所有Act都要实现
的接口。这里需要多说几句因为Act表示业务活动,所以我可以添加BusinessType,Operator,OccurPlace,OccurDateTime等属性。
按照DepositAct的做法调用不用改变。实现affirmCanDo(Act act)的类
需要变更。通过配置可以完全消除这种变更。
但是看看在CaptialAccount.deposit(...)中实现的方式,问题是到哪里找到
目前正在操作的员工呢?

今天有事,回头再聊。
0 请登录后投票
   发表时间:2004-11-25  
partech 写道
首先看看我的部署方案:
1web方案
客户端             [UI层前端(浏览器/js+html)]
                                 --[remote]--
web服务器           [UI层后端/交互层](保持状态)
                                 --[remote]--
后台服务器           [通用业务服务层/业务层/持久层]
                                 --[remote]--
数据库服务器         [数据库]

2RichClient方案
客户端               [UI层/交互层](保持状态)
                               --[remote]--
后台服务器           [通用业务服务层/业务层/持久层]
                              --[remote]--
数据库服务器         [数据库]

可以看出对应于不同的应用,后台的服务是相同的。
所以通用业务服务层同用例无关,这是业务后台通用业务服务不保留状态的另一个理由。
因为状态和用例的交互方式有关。同样的业务不同的用例将导致不同的状态。

关于领域层
通常对于更新业务对象的操作可以抽象出某个业务活动的概念。比如:开户,存钱,挂失等等。
这些业务可以使用类来表示。比如:OpenAccountAct,DepositAct。
通常业务需要执行才有效果,但是他们执行的具体内容都不同因此他们都重写了一个doRun()方法。
好了在OpenAccountAct中,我可以调用User.create(...)方法创建一个新的用户。
对于DepositAct也一样,我调用captialAccount.deposit(...)来完成存款。
仅从这里看你会觉得DepositAct有些多余,服务层直接调用CaptialAccount.deposit(...)不就可以了吗?
其实不然,比如现在有条业务规则是“挂失的帐户不能存钱”。
那么我可以在DepositAct中在调用doRun前插入一个接口CutomerAgrement 提供一个方法affirmCanDo(User user)来检查是否客户已经挂失。
而不是在CaptialAccount.deposit(...)中进行该项检查。因为影响客户存款的业务规则不是稳定,但是存钱的实质是稳定的。
当然可能会有人这样争辩“我在CaptialAccount.deposit(...)中也调用接口
CutomerAgrement的方法affirmCanDo(this.user)不就行了。”
ok,在目前的需求下没问题。
现在新的需求又来了“只有客户指定的了员工才能给客户作存钱业务”。
当然为了讨论我刚才是没有把最终CutomerAgrement的接口方法写对
最终的应该是下面的方法签名affirmCanDo(Act act)是所有Act都要实现
的接口。这里需要多说几句因为Act表示业务活动,所以我可以添加BusinessType,Operator,OccurPlace,OccurDateTime等属性。
按照DepositAct的做法调用不用改变。实现affirmCanDo(Act act)的类
需要变更。通过配置可以完全消除这种变更。
但是看看在CaptialAccount.deposit(...)中实现的方式,问题是到哪里找到
目前正在操作的员工呢?

今天有事,回头再聊。


谢谢,已经很详尽了.

以业务交互为入口,识别出更新领域对象的通用业务活动.
以command的方式提供通用业务活动的实现.
在业务活动对象中,将业务分解为易变的业务规则和相对稳定的业务实质.
在业务实质中,将各相关流程委托给具体的领域对象.
领域对象通过操纵持久层的DAO和POJO完成数据的修改.

在通用业务活动的实现中使用command解耦和封装是概念的关键.
0 请登录后投票
   发表时间:2004-11-25  
很不错的设计!

有一个问题,单独设计POJO的目的是不是为了持久化的目的?为什么不直接将领域对象持久化?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics