请教对象设计

虽然长期关注J道关于对象设计的帖子,但是自己真正去设计的时候还是会被对象的职责混淆。请高手指点。
首先说说业务:在一个地图上画一个区域,实现对这个区域的管理。
1。划分区域
a.首先提示用户在地图上某水平上画一个区域,区域不能与同水平的其他区域相交(是三维的地图)。区域的图形必须保存到图形空间中才可以显示,
b.用户指定区域的名称,点击保存。把这个区域保存到数据库中。
c.关闭界面时,如果用户在地图上画了区域,但是又没有保存这个区域,提示用户保存。否则关闭界面。
2.查询区域
a.查询一个水平的所有区域,然后点击显示可以在地图上显示出来。
3.修改区域
a.查询某个区域后可以对这个区域进行修改,修改主要是修改区域的名字,和区域的图形,修改后保存,
b.如果修改后的区域与同一水平的其他区域相交,提示用户区域相交,图形恢复到以前。
c.如果区域图形修改后未保存,提示用户区域已修改是否保存。
以下是我设计的类图:
Region:区域类,领域核心
RegionId:区域标识
RegionGraphise:区域图形
Graphise:图形
GraphiseId;图形标识:把图形放入图形空间中,图形空间会给图形一个标识。
GraphiseUtil:图形工具,可以提示用户在地图上画一个区域,也可以提示用户修改这个区域图形
GraphiseSpace:图形空间:提示用户画的区域必须加入到图形空间中才可以显示
Point:区域图形由点构成
RegionRespository:区域仓库
RegionService:应用服务
下面粘贴部分代码:


public class RegionServiceImp implements RegionService {

private RegionRepositroy regionRepository;

@Override
public RegionGraphise DrawRegionGraphise() {
// TODO Auto-generated method stub
//清楚未保存的图形
GraphiseSpace.getInstance().clearUnSavedGraphise();
//绘图
RegionGraphise regionGraphise=GraphiseUtil.drawRegion();
//判断是否有一个区域与此区域相交
boolean isNotIntersect=isNotIntersectWithOtherRegion(regionGraphise);
if(isNotIntersect)
{
//存入图形空间
GraphiseId gi=GraphiseSpace.getInstance().storeGraphise(regionGraphise):
return regionGraphise;
}
else
{
throw new RegionIntersectException(
"region is cross");
}
}
/**
*
* @param regionGraphise 区域图形
* @return 是否相交
*/

private boolean isNotIntersectWithOtherRegion(RegionGraphise regionGraphise)
{
//得到这个水平所有的区域
List<RegionGraphise> regionList=regionRepository.getAllRegionGraphiseByLevel(regionGraphise.getLevel());
for(RegionGraphise otherRegion :regionList)
{
if(regionGraphise.isIntersectWithRegion(otherRegion))
{
return false;
}
}
return true;
}
@Override
public void UpdateRegionGrapise(Region region) {
// TODO Auto-generated method stub
//设置一个备忘
region.setMemento();
//修改区域图形
GraphiseUtil.updateGraphise(region.getRegionGraphise());
//数据库更新
//regionRepository.UpdateRegion(region);
//图形空间更新
GraphiseSpace.getInstance().updateRegionGraphise(region.getRegionGraphise());

}
public void UpdateRegion(Region region)
{
//保存修改区域
regionRepository.UpdateRegion(region);
}
下面是划分区域界面的部分代码:
//画区域图形
private void button_Draw()
{
if(checkDataOfButtonDraw())
{

}
try
{

//话区域图形
regionGraphise=regionService.DrawRegionGraphise();

}
catch(RegionCrossException re)
{
logger.warn(re.getMessage());
}
catch(RegionIntersectException re)
{
logger.warn(re.getMessage());
}
}
//保存区域
private void button_Save()
{

regionService.SpercfiyRegion(regionName, regionGraphise);
}
//界面关闭
public void windowClose()
{
if(GraphiseSpace.getInstance().hasUnsavedGraphise())
{
MessageSend.sendMessage(
"有未保存的区域");
}
}

下面是更新区域界面的部分代码:
//修改图形
public void buttonUpdateGraphise()
{
Region updateRegion=...;
regionService.UpdateRegionGrapise(updateRegion);

}
//窗体关闭
public void windowClose()
{
Region updateRegion=null;
if(GraphiseSpace.getInstance().hasUnsavedGraphise())
{
MessageSend.sendMessage(
"有未保存的区域");
boolean notSave=true;
if(notSave)
{
updateRegion.Undo();
}
//图形回退
GraphiseSpace.getInstance().updateRegionGraphise(updateRegion.getRegionGraphise());
}
}

我有以下疑问:
1.我觉得区域和区域图形应该属于聚合关系,区域是聚合跟,区域图形必须依赖与区域图形存在,那么创建区域图形的职责是不是要区域来承担呢?在RegionService中有一个判断新画的区域图形是否与已经存在的区域图形相交的方法,这个方法的职责到底应该谁来承担呢?
2。用户在划分区域的时候,应该是先按绘制区域图形按钮画区域图形,然后在点保存按钮,把区域和区域的图形数据保存到数据库中。那么区域图形的生命周期应该是先与区域这个聚合跟。是不是说创建区域图形的职责不给区域,但是把修改区域图形的职责划分给区域。
呢,按我上面的做法,我怎么感觉,区域和区域图形的生命周期之间并没有太大的联系呢?它们好像独立的存在一样。如果我把区域图形的修改放在区域的职责里面,是不是就需要发出一个修改的区域图形的DomainEvent,然后在事件处理器里面处理呢?在事件处理器里面处理和在服务里面处理有什么区别呢?
3、我上面说了这么多的核心问题就是:到底区域和区域图形是否是聚合关系,聚合关系的两个实体,区域内部实体的创建,修改,删除是不是应该有聚合跟负责。但是区域图形的生命周期应该早于区域,那怎么让区域负责呢?
4。有时候又感觉区域图形是值对象,但是我却要记录区域图形是否保存到数据库这样一个状态,这个状态是否应该有区域图形对象来承担呢(这一般怎么实现呢,好像hibernate的Session一样)
5。整个代码我觉得很多职责的分配都有问题,但是我却找不到一个很好的思路,请大家指点。

在发下类图


实践才是难事。当设计时就要遵循一种成熟的思想,否则举步艰难。我看你提到领域核心。那你应该是遵循DDD来设计了。那么我也用这个思想去切入吧。

你说到的业务:在一个地图上画一个区域,实现对这个区域的管理。

我来分析一下这个业务,这句话当中,真正的业务是“区域的管理”。至于画一个区域,这个是VIEW的事,这不是业务,“画”这个动作就像我们输入信息一样,输入完整后才能成为业务交互对象,才能开始业务。若果真要定位“画”的话,那只能是功能需求。

在你这个“区域的管理”中,明显就是四个动作:CRUD(呃,注意这不是指数据库,我指的都是内存上,但我实在找不到其它词了)。

而你的Graphise一系列的类,应属于View的,不属于Domain,因为它就是领域模型的一种展示方式。你可以这么想,是Graphise得到Region状态,然后进行显示。Region状态相当于Struts中的RegionForm。Region内聚的是Point[],这个才是它的属性。


疑问回答:
1、区域与区域图形不是聚合关系,图形是一种展示方式,区域这个对象并没有图像属性。DDD中的模型,不可能内聚展示方式吧?试想现在增加一种展示方式:文字数据展示。那么这个也内聚到区域这个对象中?明显不合理的。“判断新画的区域图形是否与已经存在的区域图形相交”这个是一个策略,其关系到增加和修改,可把这个策略内聚到增加和修改中。(我怀疑,你是不是误解了你自己本身对Region的定义,Region是区域,本来带有Point[],为何还要搞RegionGraphise?单单作为容器?就像人本身就内聚手和脚,你现在就是人内聚四肢,然后在四肢中内聚手脚。可以说这是大量属性对象中进行规划属性的优化手段,但Region本身的Point[]跟RegionGraphise中的Point[]有着本质的区别,Region的Point[]是虚的,是我们引用参照物后的Point[],但RegionGraphise中的是界面图形,也就是程序上的Point[],尽管你可以认为参照物是程序上的(0,0)点,但这是认为,实物属性与展示手段的Point[]应该分开来思考,试想一下放大与缩小吧,图形可以放大缩小,但实体的属性是不会随展示方式的改变而改变的)。

2、这个还是功能性质的,属于View,View得到用户所画的“区域图形”,并把这个转化为一个Region新状态。至于保存这个动作,明显是应用层的,它接收到View的新状态,然后调用相关保存服务。区域与区域图形的确是独立存在的,关键点是:展示方式。

3,4,5、问题上面都有答到。


还有关于:“如果修改后的区域与同一水平的其他区域相交,提示用户区域相交,图形恢复到以前。”这一类功能性质的,可以放到View处理,因为你是读一个水平面。当然你放到应用层也没有问题。

至于若果问,为什么模型这么简单,这是因为,在这个业务中,并没有所谓的实体间的交互,只有一个单向的业务:用户对区域的CRUD。若果不单单是CRUD(类似多种实体协同工作)或不单单是区域(存在多种实体时),复杂性就会增加。


以上个人分析。

[该贴被SpeedVan于2011-01-22 11:30修改过]
[该贴被SpeedVan于2011-01-22 11:33修改过]

谢谢SpeedVand的指导。
我一直按照你思路去思考,并在一段时间内认为你的思路是正确的RegionGraphise是Region的视图,Region是聚合点的,后来越想我越觉得其实聚合点和聚合RegionGraphise并没有太大的区别,我觉得RegionGraphise并不是视图,它就是领域。虽然区域图形是区域的一个显示方式,但我觉得它并不是一般意义上的视图,就想我这个对象有照片和身份证一样,如果别人要我照片,我就给别人看我的照片,别人要看我的身份证我就给别人看我的身份证,虽然照片和身份证,现在就是我的显示方式,但是它并不是视图,是我的属性。区域图形就是区域的一个照片,但是至于你怎么显示这个照片,那是视图。
[该贴被OweJDao于2011-01-22 19:51修改过]
[该贴被OweJDao于2011-01-22 19:54修改过]

你这样思考也未尝不可,但这样思考很容易走入误区,我这里提出这种想法几点注意的地方:

1、“聚合点和聚合RegionGraphise并没有太大的区别”若果是认为是正确的话,那么RegionGraphise是一个值对象,它是对Region的Point[]一个封装,可以认为这个就是状态(或者状态的一部分)。

2、值对象没有唯一标识,也就是说要找到它,必须先找到实体。想要找到RegionGraphise,则要先找到Region。你Graphise中含有Id,在DDD中这是不认同的。判断值对象是否是同一个,是通过比较值对象当中的每一个值是否相等,而不是比较Id。

3、这个和身份证(或照片)不同,我们从身份证中可以得到实体是谁,但值对象是找不到实体的,因为这是单向依赖——值对象依赖实体而存在。实体的状态的本质就是值对象。你正在睡觉,我正在睡觉,你我不是同一实体,但睡觉是同一个值对象,从睡觉这个状态并不能找到谁(在睡觉)。所以RegionGraphise不可能内聚Region。

4、从领域到达表现层,是把一个Region状态送出,而不是把RegionGraphise送出,因为我们找的不是Graphise状态,而是实体Region状态。


暂时只想到这么多,希望能让你理清头绪~
[该贴被SpeedVan于2011-01-23 17:49修改过]

SpeedVan,有很多问题,我现在写也写不出来,你已经帮我理清了很多思路,我留下qq请教好嘛?qq 512668804

2011年01月23日 02:44 "SpeedVan"的内容
在DDD中这是不认同的。判断值对象是否是同一个,是通过比较值对象当中的每一个值是否相等,而不是比较Id。 ...

这个是真的吗?我一直对值对象和实体对象的应用和设计很迷惑

2011年01月23日 23:23 "OweJDao"的内容
这个是真的吗?我一直对值对象和实体对象的应用和设计很迷惑 ...

呵呵,其实值对象离我们很近的,String str = "123";"123"就是值对象,而在String pool中,如何得知是否已经存在"123"呢?就是比较值,若果已经存在则返回已经存在的"123"的引用。当然我们也可以为我们的值对象建立池,不过这是flyweight的优化做法,不是十分必要的话,偷懒了不写了o(∩_∩)o ,但若果效率不好,这就是一个优化的地方。

由此也带出了值对象的不可改变性(immutable),其实这里的值对象就是实体属性的值而已,意义上跟"123"同级,可以说目的在于状态属性的规划化。而实体中所有值对象的聚合根是实体状态(这其实也是一个值对象)。

我也不知道怎么来描述我的问题呢,因为他涉及到很多很细的职责分配,比如说我现在想比较两个区域是否相交,这个职责是不是应该在区域里面呢,区域内聚了形成区域的点,显然这是她的职责,但是现在比较两个区域是否相交,却要借助于第三方图形API实现,这样领域耦合第三方API。
谢谢SPEEDVan的帮组,我还是自己寻找答案吧,好好看看J道的论坛的代码吧
[该贴被OweJDao于2011-01-25 15:25修改过]

两个区域是否相交,是一个策略技术问题,这里就用到设计模式中的策略模式,你就试想你的“相交判定策略”是一个算法,你现在已经把算法封装好了,所以这里就可以松耦合了(注意业务与策略的区别)。至于这里的策略执行者,有两种观点,也就是jdon帖子经常出现的:交给实体,还是外界,我个人认为在这个案例下,出于方便可选第一种,出于更准确描述选用二种。

呵呵,你能说出问题,我就抽时间尽量回答。关于职责分配,可以看看banq的这个帖子对象的责任与职责