代码之家  ›  专栏  ›  技术社区  ›  waxwing

使用域对象作为密钥是一个好的实践吗?

  •  17
  • waxwing  · 技术社区  · 14 年前

    使用域对象作为映射的键(或“get”方法)是一个好的实践,还是只使用域对象的ID更好?

    举例说明更简单。假设我有个人班、俱乐部班和会员班(连接其他两个班)。即。,

    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;
    }
    

    或者类似的。现在,我想添加一个方法 getMembership 去俱乐部。问题是,如果此方法采用个人对象:

    public Membership getMembership(Person person);
    

    或者,一个人的身份证:

    public Membership getMembership(int personId);
    

    哪一个最惯用,哪一个最方便,哪一个最合适?

    编辑: 很多很好的答案。我没有公开身份,因为“人”(你可能已经意识到,我真正的域与人和俱乐部没有任何关系)实例很容易获得,但目前它内部存储在一个散列在ID上的散列映射中-但至少我在接口中正确地公开了它。

    13 回复  |  直到 14 年前
        1
  •  5
  •   dsmith    14 年前

    别用身份证上的人,这只是个坏主意,因为上面提到的所有原因。你会把自己锁在一个设计里。让我举个例子。

    现在,你把你的会员定义为俱乐部与人之间的映射。正确地说,你的会员应该是俱乐部到“会员”的地图,但是你假设所有会员都是人,因为所有的人ID都是唯一的,所以你认为你可以只使用ID。

    但是如果将来你想把你的成员概念扩展到“家庭成员资格”,为此你可以创建一个家庭表和一个家庭类。以良好的OO方式,您提取一个家庭和被称为成员的人的接口。只要这两个类都正确实现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。

        2
  •  4
  •   Uri    14 年前

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

    作为实现细节,我希望将其隐藏在其他域对象的接口中。因此,从根本上说,成员关系包括个人而不是数字。

    当然,我会确保我执行 hashCode equals() 记录了他们的意思。

    在那种情况下, 我将明确地证明两个人对象的相等性完全是基于ID确定的。 . 这是一个有点冒险的建议,但是如果您能确保的话,这会使代码更加可读。当我的对象是不可变的时,我会感到更舒服,所以在程序的生命周期中,我不会最终得到两个ID相同但名称不同的人对象。

        3
  •  4
  •   Community Egal    7 年前

    我认为第一种情况会被认为是“更纯粹的”,因为getmembership方法可能需要更多的来自人员本身的特定数据,而不是其ID(假设您不知道getmembership方法的内部,尽管这一点毫无意义,因为它很可能在同一个域中)。

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

    如果您的语言和/或ORM允许您使用代理对象(并且您有一种方便的方法来创建这些代理),那么可以很容易地调用这个函数。

    但让我们诚实点。如果您正在查询某个人的某些成员身份,那么当您调用此方法时,很可能已经在内存中保存了此人实例。

    在“基础设施用地”中,还有一个关于实施细节的概念, Uri 我写这个答案的时候已经提到了(该死,那太快了,兄弟!)。具体来说,如果您决定这个“人”概念突然在基础数据库中有一个复合主键/标识符,该怎么办?现在可以使用标识符类吗?也许使用我们所说的代理?

    DR版本

    从短期来看,使用ID确实更容易,但是如果您已经使用了一个实体ORM,我认为没有理由不使用代理或其他方法来表示不泄漏实现细节的实体的面向对象标识。

        4
  •  3
  •   Zak    14 年前

    如果您真的在实践面向对象的设计,那么您需要调用信息隐藏的思想。一旦开始在成员对象方法的公共接口中挂起Person对象的内部字段类型,就开始强制对象的外部开发人员(用户)开始学习关于Person对象是什么、如何存储以及它具有何种ID的各种信息。

    更好的是,既然一个人可以拥有成员资格,为什么不把“getmemberships”方法挂到Person类上呢?问一个人拥有哪些会员资格似乎比问一个“会员”可能属于哪个俱乐部更合乎逻辑…

    编辑 -因为OP已经更新,表明他对会员本身感兴趣,而不仅仅是作为个人和俱乐部之间的关系,所以我正在更新我的答案。

    长话短说,你定义的“俱乐部”类,现在你要求表现得像“俱乐部花名册”。俱乐部 有一个 花名册,不是 是一个 名册。花名册可以有几个特点,包括查找俱乐部成员的方法。除了通过他们的俱乐部ID查找一个人之外,您可能还需要通过SSN、姓名、加入日期等查找他们。对我来说,这意味着类“club”上有一个名为getRoster()的方法,它返回一个可以查找club中所有人的数据结构。称之为收藏。接下来的问题是,您是否可以使用现有集合类上的方法来满足到目前为止定义的需求,或者您是否需要创建自定义集合子类来提供适当的方法来查找成员记录。

    由于您的类继承权很可能是由数据库支持的,并且您可能正在着手从数据库中加载信息,并且不一定只想获取一个成员身份而获取整个集合,因此您可能需要创建一个新类。这门课可以叫我说的“花名册”。您可以从类“club”上的getroster()调用中获取它的实例。您可以根据您想要的任何搜索条件向类中添加“搜索”方法,这些条件是关于此人的“公开可用”信息。姓名、俱乐部ID、个人ID、SSN等…

    我最初的回答只适用于“会员”纯粹是一个关系,表明哪些俱乐部的人属于。

        5
  •  1
  •   RonK    14 年前

    依我看,这在很大程度上取决于应用程序的流程——你有没有 Person 当你想得到 Membership 细节?如果是,请选择:
    public Membership getMembership(Person person);

    而且,我也不明白为什么 Club 无法根据跟踪成员身份 的ID而不是实际对象-我认为这意味着您不需要实现 hashCode() equals() 方法。(尽管这始终是一个好的最佳实践)。

    如乌里所说,你应该记录下这两个 如果对象的 身份证件 是平等的。

        6
  •  1
  •   John    14 年前

    哇。后退一秒。这个 getMembership() 方法不属于 Club . 它属于所有成员资格的集合,您尚未实现。

        7
  •  1
  •   Fortyrunner    14 年前

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

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

    变长或变大的数字被证明是很棘手的,因为人们在命名方面变得非常有创造力。一些使用

    int tranNum
    

    一些使用

    int transactionNumber
    

    一些使用

    int trannNum
    

    (包括拼写错误)。

    有些人真的很有创造力…

    这真是一团糟,整理起来简直是一场噩梦。最后,我手动对所有代码进行gping,并转换为TransactionNumber对象。

    尽可能隐藏细节。

        8
  •  0
  •   Nate CSS Guy    14 年前

    我通常会坚持越少越好。调用方法所需的信息越少越好。如果你知道ID,只需要ID。

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

        9
  •  0
  •   Dean J    14 年前

    如果您已经有了这个对象,就没有理由拉出ID来获取哈希键。

    只要身份证 总是 唯一,实现hashcode()以返回ID,并实现equals()。

    很可能每次你需要会员资格的时候,你就已经有了这个人,所以以后它就节省了代码和混乱。

        10
  •  0
  •   cherouvim    14 年前

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

        11
  •  0
  •   user372388    14 年前

    除非在其他地方得到了显著的好处,否则可以说地图中的键应该是单值的,如果可能的话。也就是说,通过关注equals()和hashcode(),您可以使任何对象作为键工作,但是equals()和hashcode()并不需要关注什么。你会更高兴把身份证作为钥匙。

        12
  •  0
  •   Frank Schwieterman    14 年前

    我可能会用身份证。为什么?通过使用ID,我对来电者进行了更安全的假设。

    如果我有一个身份证,要找这个人需要多少工作?可能是“容易”,但它确实需要访问数据存储,这是缓慢的…

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

        13
  •  0
  •   Pau    14 年前

    实际上,我要做的是按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;
    }