如何处理对象删除时引用的判断

往往有这样的逻辑,一个产品如果被订单引用,则产品不能删除。因此删除产品的逻辑必须去检测是否有订单引用了该产品。原本订单模块依赖于产品模块的,但产品模块中却要知道“订单”这个概念。这显然产生了双向依赖的问题,如果再加入一个新的模块引用到产品,就要去修改产品的删除逻辑代码。如果避免双向依赖的话,我们可能会想到观察者模式,通过在删除逻辑前执行PreDeleteProductionEvent的事件,在订单中来实现对产品删除事件的注册。似乎可以解决这个问题,但考虑到对象被引用时删除检测这种情况在系统中是很常见的——你开发的任何业务对象都有可能再新的对象引用,而在你设计这个对象时是未知的。那么都采用观察者模式,好象给设计带来很大的工作量。不知道前辈们是如何处理这个常见的问题的。

>原本订单模块依赖于产品模块的,但产品模块中却要知道“订单”这个概念。这显然产生了双向依赖的问题

设计模式属于设计细节,前提必须是分析建模完成的情况下,现在你的建模就有问题,依靠设计模式再去解决问题,就很牵强。

“产品”很显然不应该关联“订单”的,避免双向关联,同时也避免双向依赖,一般产生双向依赖,主要分析方法没有采取Evans DDD这样建模方法来实现,将行为和属性混淆在一起,说到底,还是面向围绕数据表分析设计思维习惯造成的。

你现在需要做的是:
1.寻找一个OO的建模方法,重新分析需求,对"产品" "订单"等建模。
2.避免双向关联。
3.围绕模型,采取Model/Service架构来分离行为。

>“产品”很显然不应该关联“订单”的

我没有说“产品”要关联“订单”。而是在对产品进行删除的类里,必须要对订单进行查询,才能知道是否有订单引用到要删除的产品,这是客观存在的双向关联,难道这是“需求分析”的错误吗?现在的目的就是为了避免模块间的这种双向关联。

例如(不好意思,下面是用C的代码,意思大家应该能明白吧):

产品模块:

public class Production
{
}

public class ProductionRepository
{
public static int Delete(Production prod)
{
if(OrderRepository.QueryOrderByProduction(prod).Count > 0)
{
//....提示不可以删除
}
//...执行删除
}
}

订单模块:

public class Order
{
public IList OrderItem;
}

public class OrderItem
{
public Production Prod;
public decimal Quantity;
}

public class OrderRepository
{
public IList QueryOrderByProduction(Production prod)
{
// ...查询订购prod的所有订单
}
}

大家可以看出,产品模块和订单模块,应该是订单模块依赖于产品模块,但是为了要判断是否有订单引用到要删除的产品,则在ProductionRepository.Delete里必须去调用OrderRepository。那么我现在的一种解决办法是:

产品模块:

public class ProductionRepository
{
public static event EventHandle BeforeDelete;

public static int Delete(Production prod)
{
if(BeforeDelete != null)
{
BeforeDelete(this, new ProductionDeleteArgs(prod));
}
}
}

public class ProductionDeleteArgs : EventArgs
{
private Production m_prod;

public ProductionDeleteArgs(Production prod)
{
m_prod = prod;
}
}

订单模块:

public class OrderRepository
{
public OrderRepository()
{
ProductionRepository.BeforeDelete += new EventHandle(this.OnBeforeDeleteProduction);
}

public IList QueryOrderByProduction(Production prod)
{
// ...查询订购prod的所有订单
}

public void OnBeforeDeleteProduction(object sender, EventArgs e)
{
if(OrderRepository.QueryOrderByProduction(prod).Count > 0)
{
//....提示不可以删除
}
}
}

上面用到了C里的委托的技术,实际上就是一个观察者模式的做法。通过BeforeDelete这个事件委托,在ProductionRepository.Delete里,只需要判断是否有存在删除前的事件,如果有则执行。而OrderRepository则将OnBeforeDeleteProduction注册到BeforeDelete上。这样实现了在删除产品里对订单的引用进行判断。同样的,在系统增加其它的模块的时候,可以采用同样的机制来增加产品删除前的引用判断。而在产品模块就不需反复变更代码。

用“产品模块”和“订单模块”是我想表达我的问题的一个简单例子,并不是我实际工作中的需求。但我想大家应该都会遇上这样的问题,不知道大家是用什么方式来解决的。


>“产品”很显然不应该关联“订单”的,避免双向关联,同时也避免双向依赖,一般产生双向依赖,主要分析方法没有采取Evans DDD这样建模方法来实现,将行为和属性混淆在一起,说到底,还是面向围绕数据表分析设计思维习惯造成的。

请问Banq兄,我是思想里真的有“面向围绕数据表分析设计思维习惯”吗?本身我是最反对用“表分析思维”的,也许是我的思维真的有错,还请指教,谢谢!

>是否有订单引用到要删除的产品,这是客观存在的双向关联,难道这是“需求分析”>的错误吗?现在的目的就是为了避免模块间的这种双向关联。

现在才对你这个案例情况有进一步了解,其实我们陷入了双向关联或双向依赖的误区。

删除产品时,必须了解是否有订单引用到要删除的产品,这个是业务需求,按照Evans DDD,这应该是在领域层实现的,实际上这属于删除的一个约束条件,或者认为是是一条业务规则,或规格specification

在Evans DDD这书中说:业务规则不适合放于实体或值对象,但是也不能移出领域层,可以设计一个“谓词”对象。

由于对你的整个系统设计不清楚,我个人觉得你武断分了订单模块和产品模块,而不是依据领域层和服务层来划分。这个问题已经在下面帖子里讨论:
http://www.jdon.com/article/31216.html

所以,目前看来还是由于你分析需求提炼模型时没有考虑仔细,导致这样问题需要依靠设计模式来解决,但是目前看牵强一些,当然不是不可以。

以上观点仅供参考。
[该贴被banq于2007年03月27日 12:00修改过]

>由于对你的整个系统设计不清楚,我个人觉得你武断分了订单模块和产品模块,而不是依据领域层和服务层来划分

我的问题是“如何处理对象删除时引用的判断 ”,“订单模块”和“产品模块”只是我用于表述我问题的一个假设。当然也可以举例成“产品管理模块”和“财务管理模块”。

我想软件按业务的功能范围来进行模块的划分,应该和DDD不冲突吧。领域层和服务层只是架构的一个划分,和模块应该不是同一个分类标准吧。我想Eric Evans也有提到模块的概念吧。引Evans DDD(第五章5.5节 P78页):一个优秀的设计模型,它的元素能很好地协同工作,适当地选择模块,将那些紧密联系的模型元素集中到一起。这些高度内聚的对象,具有相关的职责,可以将建模和设计的工作集中在单独一个模块中,从而把复杂度限制在一个范围内,使开发人员更容易处理。….如果你的模型是一本书,那么模块就是这本书的章节…

>在Evans DDD这书中说:业务规则不适合放于实体或值对象,但是也不能移出领域层,可以设计一个“谓词”对象。

我的设计里,也没有把删除逻辑放在实体“Production”里呀。那么就这个谓词对象的代码写的时候,它无法预知道未来会有什么新的对象会引用到要删除的“产品”呢。所以我才引入了观察者模式来改变依赖。如果一个程序员开发一个业务模块的话,这个问题才会上升到模块中来。也就是我可以先开发出“产品管理模块”,再开发出“订单管理模块”或“财务管理模块”,那么在开发“产品模块”时,不知道后面还会有财务管理模块,不知道新的模块中有什么类会产生对产品的引用,导致产品不可以物理删除吧。

> 导致这样问题需要依靠设计模式来解决
呵呵,我开这个贴,目的就是想知道“如何处理对象删除时引用的判断 ”这个问题时,大家是怎么解决的。是否可以把您的做法告知一二呢,谢谢

写成的不错 我顶班

可能对DDD有些认识分歧,讨论一下:

>我想软件按业务的功能范围来进行模块的划分,应该和DDD不冲突吧。
个人认为不应该按业务的功能范围来进行模块的划分,Evans DDD的第15章 精练中,专门提到隔离核心,按照是否属于核心领域来进行模块划分,分为核心模块和辅助子模块等。

你的这个案例,product和Order显然属于业务核心,Product和Order是一个关联关系,不能将其强行切开分离到产品模块和订单模块,它们应该属于你这个业务系统的核心模块。

如果按照我的这个想法划分,就不存在你提出的问题,所以,我觉得问题出在分析建模划分上。

>也没有把删除逻辑放在实体“Production”里呀。那么就这个谓词对象的代码写的>时候,它无法预知道未来会有什么新的对象会引用到要删除的“产品”呢

如果划分到核心模块,那么谓词对象是在核心模块中,你会为Order建立一个规则谓词对象,作为删除的约束...。

如果你将来有新的会引用到要删除的“产品”呢,注意这个对象潜含义是模型对象,模型发生变化,一定是分析源头有变化,那么模型类图都要变化,整个分析设计代码再来一次迭代,因为这涉及业务的变化,我们程序员不会拍脑袋想出一个新模型对象。

如果想将这新的对象延伸开来,不想指模型对象,那么我们还是要就事论事,看这个对象首先是不是模型对象,如果是,按照上面思路实现;如果不是,再看具体情况,这个时候才可能会使用设计模式来解决问题。

个人意见..

我个人感觉banq的讨论有些牵强,有些跑题.
>你的这个案例,product和Order显然属于业务核心.
OK,我们可以划分到核心业务中,没有什么问题.但是我们实际项目中很可能对核心业务再划分成N个功能模块.
对这些核心业务下面的一些引用规则业务判断怎么处理? 以至整个系统业务引用规则怎么判断,系统怎么实现,才是这个贴的讨论主题吧

这位 RoamsWind 兄弟说的很有道理
其实 举一个范围最广的例子,
系统的用户管理
所有系统的业务操作,基本上都会和该系统的用户有关联,
那么我删除用户的时候,肯定会考虑到该用户关联的所有的业务数据该怎么处理的问题,也就是本帖的贴主提出的问题,要校验和该用户相关的那些记录该怎么处理,是否需要进行校验等等。

我现在也想知道的是,banq遇到是怎么处理这个问题的呢?

我一般的解决思路是:要么不让删除,要么逻辑删除。
[该贴被elegantyu于2008-03-28 17:01修改过]

我前面已经说了,可能没说明白:如果你将来有新的会引用到要删除的“产品”呢,注意这个对象潜含义是模型对象,模型发生变化,一定是分析源头有变化。

我的观点就是说如果想找一个自动设计机制帮助你解决业务领域"处理对象删除时引用的判断"是无解的,因为你不可能预测将来,这属于过度设计了,这就象有人试图通过开发一个系统能够对付大多数企业应用,也就是说:试图通过设计领域来解决前端分析领域的问题,有些本末倒置。

在分析业务模型时,通过双向关联来保证对象一致性,至于未来可能发生的关联,你无法预测,也不可能通过一个设计机制一劳永逸,到时针对具体情况,还必须返回分析建模阶段,具体情况具体分析。

才进来的新人,虽然这个是老帖,但是对我来说还是新问题,而且很感兴趣,banq的回复很理论,对于我们初学者有点抽象,没法把它对应成我们的具体设计,希望banq能针对这样的细节问题给出一个可视化的简单示例或伪代码层次划分可能更好理解。个人看法。顺便提下,看完后没有发现解决问题的具体方案。

我个人觉得。楼主你的业务分析本来就是有问题的。

你说产品在删除时,要检测是否有订单关联了这个产品。

我认为你的产品本来就不应该被删除。 比方说。现在用户删除一个产品。你的处理方式可能有这么2种

第一种删除产品的同时,也删除订单,显然不行。 第二种不让用户删除这个产品,也别扭。

其实这里最好的办法是,不让用户删除产品。 而应该是 当用户不想让商品上架时, 就下架就好了

实际的做法是。你可以给商品一个属性用来标明商品是否上架。

呵呵,个人愚见。

回复:tianqiq
比较同意你的这个观点!

这就是逻辑删除吧,在电信计费系统中在处理这方面一般都是逻辑的。
商品下架,其实就是这个对象的一个状态改变。
有些对象是可以永久删除的,有些是不行。毕竟有些对象的信息 是需要 历史“存根“的。

感觉LZ提到的删除只是个表面问题,实质上的问题是:数据的一致性。

似乎还有很多相似的问题。比如,一个数据如果产生了两个拷贝,这两个拷贝如何进行同步修改。如果修改了整体的一部分,整体的关系如何进行修改。不一而足。

或许,A依赖于B,则B的修改A应该得到通知?Observer?这样的话,A倒有点像MVC里的V的样子了。

有“系统边界”这个概念,那么有没有“类边界”这个概念呢?一个类的职责范围在哪里呢?

另外,昨天又想到一个解决方案,就是Null-Object模式,《重构》一书。