将域对象用作键是一种好习惯吗?

时间:2010-06-21 19:48:30

标签: java oop domain-driven-design

使用域对象作为地图(或“获取”方法)的键是一种好习惯,还是只使用域对象的id更好?

用一个例子来解释更简单。假设我有Person类,Club类和Membership类(连接其他两个)。即,

public class Person {
    private int id; // primary key
    private String name;
}

public class Club {
    private String name; // primary key
}

public class Membership {
    private Person person;
    private Club club;
    private Date expires;
}

或类似的东西。现在,我想向Club添加一个方法getMembership。问题是,如果此方法采用Person对象:

public Membership getMembership(Person person);

或者,一个人的身份:

public Membership getMembership(int personId);

哪个是最惯用的,哪个最方便,哪个最合适?

编辑:许多非常好的答案。我没有暴露id,因为“人”(你可能已经意识到,我的真实域名与人和俱乐部没有任何关系......)实例很容易获得,但现在它在内部存储一个HashMap在id上进行了哈希 - 但至少我在接口中正确地公开了它。

13 个答案:

答案 0 :(得分:5)

不要使用id的男人,这只是提到的所有原因的一个坏主意。你会锁定自己的设计。让我举个例子。

现在,您将会员资格定义为俱乐部与人员之间的映射。理所当然,您的会员资格应该是俱乐部对“会员”的地图,但您假设所有会员都是人,而且由于所有人的身份都是独一无二的,您认为您可以使用该身份证。

但是,如果将来您希望将您的会员资格概念扩展为“家庭成员资格”,您可以为此创建一个Family表和Family类。以良好的OO方式,您可以提取名为Member的Family和Person的界面。只要两个类都正确地实现了equals和hashCode方法,就不必触及其他代码。就个人而言,我会事先定义成员界面。

public interface Member {
}

public class Person implements Member {
    private int id; // primary key
    private String name;
}

public class Family implements Member {
   private int id;
   private String name;
}

public class Club {
    private String name; // primary key
}

public class Membership {
   private Member member;
   private Club club;
   private Date expires;
}

如果您在界面中使用了ID,则需要强制执行键值的跨表唯一性,或者维护两个单独的映射并放弃优秀的多态接口。

请相信我,除非您正在编写一次性的一次性应用程序,否则您希望避免在界面中使用ID。

答案 1 :(得分:4)

假设这是一个数据库ID或仅用于索引的东西(而不是类似SSN的东西),那么在理想的系统中,ID的存在是一个实现细节。

作为实现细节,我宁愿将其隐藏在其他域对象的界面中。因此,成员资格从根本上涉及个人而非数字。

当然,我确保已实施hashCodeequals(),并记录了它们的含义。

在这种情况下,我会明确记录两个Person对象的相等性仅基于ID 确定。这有点冒险,但如果你能确保代码,那么代码就更具可读性。当我的对象是不可变的时,我觉得更舒服,所以我实际上不会在程序的生命周期中最终得到两个具有相同ID但名称不同的Person对象。

答案 2 :(得分:4)

我认为第一种情况会被认为是“更纯粹”,因为getMembership方法可能需要来自人本身的更多特定数据而不是其id(让我们假设您不知道getMembership方法的内部,即使这没什么意义,因为它最有可能在同一个域中。)

如果事实证明它实际上需要来自Person实体的数据,那么它将不需要有关人员的DAO或工厂。

如果您的语言和/或ORM允许您使用代理对象(如果您有方便的方法来创建这些代理),则可以轻松调用此方法。

但老实说。如果您正在询问一个人的某些成员资格,那么当您调用此方法时,您很可能已将此Person实例存储在内存中。

在“基础设施土地”的路上,还有关于Uri在我写这个答案时已经提到的实施细节的概念(该死的,那是快速的兄弟!)。具体而言,如果您确定此“Person”概念突然在底层数据库中具有复合主键/标识符,那么您现在会使用标识符类吗?也许使用我们谈论的那个代理?

TL; DR版本

在短期内使用ID真的很容易,但是如果你已经使用了一个可靠的ORM,我认为没有理由不使用代理或其他方法来表达不泄漏的实体的面向对象的身份实施细节。

答案 3 :(得分:3)

如果你真的在练习面向对象的设计,那么你想要调用信息隐藏的想法。一旦开始在成员对象的方法的公共接口中挂起person对象的内部字段类型,就会开始强制对象的外部开发人员(用户)开始学习有关人物对象的各种信息,以及如何它被存储,它有什么样的ID。

更好的是,由于一个人可以拥有会员资格,为什么不把“getMemberships”方法挂在人员班级上。向一个人询问他们所拥有的会员资格似乎更合乎逻辑,而不是要求某个人可能属于某个俱乐部的“会员资格”......

编辑 - 由于OP已经更新以表明他感兴趣的是会员资格本身,而不仅仅是用作人与俱乐部之间的关系,我正在更新我的答案。

长话短说,你所定义的“俱乐部”课程,你现在要求表现为“俱乐部名单”。俱乐部有一个名单,它不是名单。名册可以有几个功能,包括查找属于俱乐部的人的方法。除了通过他们的俱乐部ID查找一个人之外,你可能想通过SSN,姓名,加入日期等来查找他们。对我来说,这表示在“俱乐部”类中有一种叫做getRoster()的方法,返回一个可以查找俱乐部所有人的数据结构。称之为集合。接下来的问题是,您是否可以使用预先存在的集合类上的方法来满足您到目前为止定义的需求,或者您是否需要创建自定义集合子类以提供查找成员资格记录的适当方法。

由于您的类heirarchy很可能是由数据库支持的,并且您可能正在从数据库中加载信息,并且不一定希望获得整个集合只是为了获得一个成员资格,您可能想要创建一个新的课程。当我说“名册”时,可以调用这个课程。你可以从类“club”上的getRoster()调用中获取它的实例。您可以根据您想要的任何搜索条件向该类添加“搜索”方法,该搜索条件是关于此人的“公开可用”信息..姓名,clubID,personID,SSN等...

我的原始答案仅适用于“会员资格”纯粹是指示哪些俱乐部属于哪个俱乐部的关系。

答案 4 :(得分:1)

IMO,我认为这在很大程度上取决于应用程序的流程 - 如果您想获得Person详细信息,是否有Membership可用?如果是,请使用:
public Membership getMembership(Person person);

此外,我没有看到Club无法根据Person的ID而不是实际对象跟踪成员资格的任何原因 - 我认为这意味着你不会需要实施hashCode()equals()方法。 (虽然这总是一个很好的最佳实践)。

正如Uri所说,如果 ID 相等,你应记录两个Person个物体相等的减速度。

答案 5 :(得分:1)

哇。在这里备份一秒。 getMembership()方法不属于Club。它属于所有成员资格的集合,您尚未实施。

答案 6 :(得分:1)

正如其他人所描述的:使用对象。

我在一个系统上工作,我们有一些使用int来表示事务ID的旧代码。你猜怎么着?我们开始耗尽id,因为我们使用了int。

改为long或BigNumber证明是棘手的,因为人们在命名方面变得非常有创造力。一些使用

int tranNum

一些使用

int transactionNumber

一些使用

int trannNum

(完成拼写错误)。

有些人真的很有创造力......

这是一团糟,整理出来是一场噩梦。我最终手动完成了所有代码并转换为TransactionNumber对象。

尽可能隐藏详细信息。

答案 7 :(得分:0)

我通常坚持少就多了。调用方法所需的信息越少越好。如果您知道ID,则只需要ID。

如果需要,请提供额外的重载,以接受额外的参数,例如整个类。

答案 8 :(得分:0)

如果您已经拥有该对象,则没有理由提取ID来获取哈希密钥。

只要ID 始终唯一,就实现hashCode()以返回ID,并实现equals()。

每次你需要会员资格时,你都已经拥有了这个人,所以它会在以后节省代码和混淆。

答案 9 :(得分:0)

首先,我会把任何这种性质的吸气剂放在DAO中(而不是在模型上)。然后我将实体本身用作参数,方法内部发生的是实现细节。

答案 10 :(得分:0)

除非在其他地方获得显着的好处,否则可以说地图中的键应该是单值的东西,如果可能的话。也就是说,通过关注equals()和hashCode(),您可以使任何对象作为键,但equals()和hashCode()不是非常令人愉快的事情需要注意。你会更乐意坚持使用ID作为密钥。

答案 11 :(得分:0)

我可能会使用ID。为什么?通过获取ID,我对调用者做出了更安全的假设。

如果我有身份证,那么获得该人的工作量是多少?可能很“简单”,但确实需要点击数据存储,这很慢......

如果我有Person对象,获取ID需要多少工作?简单的会员访问。快速可用。

答案 12 :(得分:0)

实际上,我要做的是通过id调用它,但重构一下原始设计:

public class Person {
    private int id; // primary key
    private String name;
}

public class Club {
    private String name; // primary key
    private Collection<Membership> memberships;
    public Membership getMembershipByPersonId(int id);
}

public class Membership {
    private Date expires;
    private Person person;
}

public class Person {
    private int id; // primary key
    private String name;
    private Membership membership;
    public Membership getMembership();
}

public class Club {
    private String name; // primary key
    private Collection<Person> persons;
    public Person getPersonById(int id);
}

public class Membership {
    private Date expires;
}