业务对象-容器还是功能?


19

这是我在SO上问过的一个问题,但在这里可能会得到更好的讨论...

在我工作的地方,我们已经在这个问题上来回多次,正在寻找健全性检查。问题是:业务对象应该是数据容器(更像DTO),还是应该包含可以对该对象执行某些功能的逻辑。

示例-以一个客户对象为例,它可能包含一些通用属性(名称,ID等),该客户对象是否还应包含函数(保存,计算等)?

一条推理路线说将对象与功能(单一责任主体)分开,并将功能放在业务逻辑层或对象中。

另一道理说,不,如果我有一个客户对象,我只想调用Customer.Save并完成它。如果使用该对象,为什么需要了解另一个类来节省客户?

我们的最后两个项目已将对象与功能分开,但是关于新项目的争论再次出现。

哪个更有意义,为什么?


1
您能告诉我们更多有关您的项目的信息吗?您的项目的性质将决定哪种解决方案更好。

Answers:


5

如果您认为客户是域模型的一部分,那么拥有该对象的属性和操作是有道理的(尤其是在DDD的上下文中,但不仅限于此)。

话虽如此,我认为您所使用的示例是一个糟糕的示例,也是引起争论的原因。如果您在谈论持久性,那么客户通常不会“保存”自己;无论您使用哪种持久性工具。有道理的是,任何种类的持久性都应属于持久性层/分区。这通常是存储库模式背后的思路。**诸如Customer.UpgradeWarranty()或Customer.PromoteToPreferred()之类的方法使参数更清晰。

这也不会消除使用DTO的可能性。考虑一下您要将客户信息传递到远程服务的情况。对于客户而言,创建自己的DTO进行传输可能没有任何意义,这是体系结构上的考虑,但是可以在持久性或网络层/分区/代码/所拥有的内容中找到理由。在这种情况下,这样的对象可以具有如下所示的方法

public static CustomerDTO GetDTO(Customer c) { /* ... */ }

public static Customer GetCustomer(CustomerDTO cdto) { /* ... */ }

因此,总而言之,对域对象执行与域中逻辑操作一致的操作是非常有意义的。

Google对于“ Persistence Ignorance”问题进行了许多精彩的讨论(此SO问题及其接受的答案是一个不错的起点)。

**这与某些OR / M软件有些混淆,在其中您被迫从具有“保存”方法的持久性基类继承。


3

实际上,我最近刚刚修改了一些代码,以将对象与数据分离。原因是数据是通过单独的服务获得的,并且只将原始数据传递到服务器或从服务器传递原始数据要容易得多,而不是来回传递整个对象并且必须处理服务器上的验证错误要容易得多。

对于小型项目,让对象包含其业务逻辑和数据可能很好,但是对于大型项目或可能随时间扩展的项目,我肯定会分开这些层。


3

我认为实现这两种方法都有其好处,但是我将从面向对象或域驱动的角度来看:不同类和对象的职责是什么。

因此,在您的具体情况下,我会问自己:客户是否应该知道如何进行自我保存?

在我看来,答案是否定的:在我看来,这没有任何意义,而且客户完全不必了解任何持久性框架(职责分离)。

至于SnOrfus带有Customer.UpgradeWarranty()或Customer.PromoteToPreferred()的示例,它们显然比Customer.Save()更面向业务逻辑。有不同的解决方法,但是如果您问自己是否应该升级客户的保修,则答案可能是肯定还是否,这取决于您的看法:

  • 是的:客户当然可以升级保修
  • 否:客户可以要求升级,但升级由其他人完成

回到您最初的问题;哪种方法更有意义,可能取决于您询问的人和他们的首选方法,但是最重要的事情可能是坚持一种方法。

不过,以我的经验来看,将数据和(业务)逻辑分开将使体系结构更简单,尽管并不那么令人兴奋。


2

我认为您是在专门讨论ActiveRecord模式与Repository模式之间的区别。在前者中,实体知道如何持久化自己,在后者中,存储库知道持久性。我认为后者提供了更好的关注点分离。

从广义上讲,如果实体的行为更像数据结构,则它们不应具有行为,但是,如果它们具有行为,则不应将其用作数据结构。例:

数据结构:

class CustomerController
{
    public int MostRecentOrderLines(Customer customer)
    {
        var o = (from order in customer.Orders
                orderby order.OrderDate desc
                select order).First();
        return o.OrderLines.ToList().Count;
    }
}

非数据结构:

class Customer
{
    public int MostRecentOrderLines()
    {
        // ... same ...
    }
}

在第一种情况下,您可以在工作代码中的任何位置无问题地浏览模型的数据结构树,这没关系,因为您将其视为数据结构。在后一种情况下,Customer更像是给定客户的服务,因此您不应在结果上调用方法。您可以通过在Customer对象上调用一个方法来获取您想要了解的关于Customer的所有信息。

那么,您是否希望您的实体成为数据结构或服务?为了保持一致性,最好坚持一个。在前一种情况下,请将“服务”类型的逻辑放在其他位置,而不是放在实体中。


1

我不能真正回答您的问题,但我觉得很有趣,我们也在学校的一个项目中也进行了讨论。我自己想将逻辑与数据分开。但是很多同学说这个对象必须保存所有逻辑和数据。

我将总结一下他们提出的事实:

  • 业务类对象代表事物,因此应包含所有数据和逻辑。(例如,如果您想打开一扇门,您自己动手,不问别人。我知道这个坏榜样)

  • 更容易理解bu对象的代码和功能。

  • 设计复杂度降低

我告诉他们他们很懒,我会这样:

  • 是的,业务类别代表事物,因此它保存数据。但是,保存自己,甚至复制自己,感觉是错误的。您无法在rl中做到这一点。

  • 使对象对所有事物负责,将使其在将来的耐用性和可维护性方面变差。如果您有负责保存的单独类,则在设计中添加新对象后,就可以轻松实现其保存功能。而不是在该类中再次对其进行编码。

  • 通过使一个对象能够保留数据,该对象可以保持数据库连接,并且所有数据库流量都在该对象处进行引导。(基本上是其数据访问权限层),否则所有Business对象都必须保持连接。(这增加了复杂性)

好吧,我要问一位老师。完成此操作后,如果您愿意,我也会在此处发布他的答案。;)

编辑:

我忘了提这个项目是关于一个书店的。


您的同学是对的,除了他们将业务逻辑与其他形式的逻辑(在这种情况下是架构逻辑或持久性逻辑)相混淆。
史蒂文·埃弗斯

1

答案实际上将取决于您的体系结构/设计是什么-在对象设计方面,具有域模型的DDD体系结构将与以CRUD数据为中心的模型有很大不同。

通常,请记住,在面向对象的设计中,您试图封装状态并公开行为。这意味着您通常会尝试使与某个行为相关联的状态尽可能接近该行为-当您不这样做时,您将被迫以一种或另一种形式公开状态。

在域模型中,您希望将业务模型与基础结构问题区分开来-因此,您绝对希望避免使用'.Save'之类的方法。我不记得我上次在现实生活中“拯救”一位客户了!

在CRUD应用程序中,数据是第一等公民,因此“ .Save”是完全合适的。

大多数可观的实际应用程序将混合使用这些范式-快速变化或复杂的业务规则的域模型,用于跨边界传输数据的DTO,CRUD(或CRUD(或CRUD和域模型之间的某种内容,例如activerecord))在其他地方。没有任何一种适合所有人的规则。


0

我一直在考虑同一问题一段时间-我认为数据组件和逻辑组件必须分开。我之所以这样认为,是因为它将业务逻辑视为提供含义的数据的接口,从而使您进入正确的思路。

我还从上面修改了Scott Whitlock的观点(除了我没有新成员的身份),数据或业务逻辑类不必真正担心对象如何持久存储。

话虽如此,如果您要处理现有产品,只要您拥有干净的合同界面,那也是可以维护的……

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.