Полезно ли использовать доменные объекты в качестве ключей? - PullRequest
17 голосов
/ 21 июня 2010

Является ли хорошей практикой использование доменных объектов в качестве ключей для карт (или методов "get") или лучше просто использовать идентификатор объекта домена?

Проще объяснить на примере. Допустим, у меня есть класс 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;
}

Или что-то в этом роде. Теперь я хочу добавить метод getMembership в Club. Вопрос в том, должен ли этот метод принимать объект Person:

public Membership getMembership(Person person);

или идентификатор лица:

public Membership getMembership(int personId);

Какой самый идиоматичный, какой самый удобный, какой самый подходящий?

Редактировать: Много очень хороших ответов. Я пошел с тем, чтобы не раскрывать идентификатор, потому что экземпляры "Person" (как вы, возможно, поняли, мой настоящий домен не имеет никакого отношения к людям и клубам ...) легко доступны, но сейчас они хранятся внутри HashMap хэшируется на id - но по крайней мере я правильно выставляю его в интерфейсе.

Ответы [ 13 ]

5 голосов
/ 22 июня 2010

Не используйте этого человека, это просто плохая идея по всем упомянутым причинам. Вы заперетесь в дизайне. Позвольте мне привести пример.

Прямо сейчас вы определяете свое членство как отображение между Клубами и Людьми. По праву, ваше Членство должно быть картой Клубов для «Участников», но вы предполагаете, что все Участники - Люди, и, поскольку все идентификаторы людей уникальны, вы думаете, что можете просто использовать ID.

Но что если в будущем вы захотите расширить концепцию членства на «членство в семье», для которой вы создадите таблицу Family и класс Family. Хорошим образом вы извлекаете интерфейс Family and Person под названием Member. До тех пор, пока оба класса правильно реализуют методы equals и hashCode, никакого другого кода не нужно трогать. Лично я бы сразу определил интерфейс Member.

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

Если вы использовали идентификаторы в вашем интерфейсе, вам нужно будет либо обеспечить уникальность значений ключей в кросс-таблицах, либо поддерживать две отдельные Карты и отказаться от приятного полиморфного интерфейса.

Поверьте, если вы не пишете одноразовые одноразовые приложения, вы не должны использовать идентификаторы в своем интерфейсе.

4 голосов
/ 21 июня 2010

Я думаю, что первый случай будет считаться «более чистым» в том смысле, что для метода getMembership могут потребоваться более конкретные данные от самого человека, кроме его идентификатора (предположим, вы не знаете внутренностей метода getMembership, хотяэто не имеет особого смысла, поскольку, скорее всего, оно находится в одном и том же домене).

Если выясняется, что для этого требуются данные от сущности Person, то для рассматриваемого лица не потребуется DAO или фабрика.

Это можно легко назвать, если ваш язык и / или ORM позволяет вам использовать прокси-объекты (и если у вас есть удобный способ создания этих прокси).

Но давайте будем честными.Если вы спрашиваете о каком-либо членстве человека, скорее всего, у вас уже есть этот экземпляр Person в памяти, когда вы вызываете этот метод.

В дальнейшем в «инфраструктурной стране» есть и это понятиео деталях реализации, которые Uri уже упоминали, когда я писал этот ответ (черт, это было быстро, братан!).Если быть точным, что если бы вы решили, что у этой концепции «Персона» внезапно появился составной первичный ключ / идентификатор в базовой базе данных ... Вы бы теперь использовали класс идентификатора?Возможно, использовать тот прокси, о котором мы говорили?

TL; версия DR

Использование идентификаторов действительно проще в краткосрочной перспективе, но если вы уже используете надежный ORM, я не вижу причинне использовать прокси или другие средства для выражения объектно-ориентированной идентичности сущности, которая не пропускает детали реализации.

4 голосов
/ 21 июня 2010

Если предположить, что это идентификатор базы данных или что-то, что используется только для индексации (а не что-то вроде SSN), то в идеальной системе наличие идентификатора является деталью реализации.

В качестве детали реализации, я бы предпочел скрыть это в интерфейсе других объектов домена.Таким образом, членство включает, в основном, людей, а не числа.

Конечно, я бы удостоверился, что реализовал hashCode и equals() и хорошо задокументировал, что они имели в виду.

В этом случае Я бы прямо задокументировал, что равенство двух объектов Person определяется исключительно на основе идентификатора .Это несколько рискованное предложение, но делает код более читабельным, если вы можете это гарантировать.Я чувствую себя более комфортно, когда мои объекты неизменны, поэтому я не получу два объекта Person с одним и тем же идентификатором, но с разными именами во время жизни программы.

3 голосов
/ 22 июня 2010

Если вы действительно практикуете объектно-ориентированное проектирование, тогда вы хотите использовать идею сокрытия информации. Как только вы начинаете вешать внутренние типы полей объекта person в общедоступном интерфейсе методов объекта членства, вы начинаете заставлять внешних разработчиков (пользователей) ваших объектов начинать изучение всех видов информации о том, что такое объект person и как он хранится и какой у него идентификатор.

Еще лучше, поскольку человек может иметь членство, почему бы вам просто не повесить метод "getMemberships" на класс этого человека. Кажется, гораздо более логично спросить человека, какое членство у него есть, чем спросить «членство», к каким клубам может принадлежать данное лицо ...

Редактировать - поскольку ОП обновился, чтобы указать, что он заинтересован в самом членстве, а не просто в качестве отношения между Персоном и Клубом, я обновляю свой ответ.

Короче говоря, класс "Клуб", который вы определяете, теперь вы просите вести себя как "клубный список". У клуба есть список , это не список . Реестр может иметь несколько функций, в том числе способы поиска лиц, принадлежащих к клубу. В дополнение к поиску человека по его идентификатору клуба, вы можете захотеть найти его по SSN, имени, дате вступления и т. Д. Для меня это говорит о том, что в классе "Club" есть метод getRoster (), который возвращает структуру данных, которая может искать всех людей в клубе. Назовите это коллекцией. Тогда возникает вопрос, можете ли вы использовать методы в уже существующих классах коллекций для удовлетворения потребностей, которые вы определили до сих пор, или вам нужно создать собственный подкласс коллекции, чтобы предоставить соответствующий метод для поиска записи о членстве.

Поскольку ваш класс иерархии, скорее всего, поддерживается базой данных, и вы, вероятно, занимаетесь загрузкой информации из базы данных и не обязательно хотите получать всю коллекцию только для того, чтобы получить одно членство, вы можете создать новый класс. Этот класс можно назвать, как я сказал, «Ростер». Вы получите его экземпляр из вызова getRoster () класса "club". Вы добавляете методы «поиска» в класс на основе любых критериев поиска, которые вам нужны, чтобы это была «общедоступная» информация о человеке ... имя, clubID, personID, SSN и т. Д. ...

Мой первоначальный ответ применяется только в том случае, если «членство» является чисто отношением, указывающим, к каким клубам и каким лицам относятся.

1 голос
/ 26 июня 2010

Как описано другими: используйте объект.

Я работаю в системе, где у нас был какой-то старый код, который использовал int для представления идентификаторов транзакций. Угадай, что? У нас закончились идентификаторы, потому что мы использовали int.

Переход на long или BigNumber оказался хитрым, потому что люди стали очень изобретательны в именовании. Некоторые использовали

int tranNum

некоторые использовали

int transactionNumber

некоторые использовали

int trannNum

(в комплекте с орфографическими ошибками).

Некоторые люди стали действительно изобретательными ...

Это был беспорядок, и разбираться с ним было кошмаром. В итоге я вручную просмотрел весь код и преобразовал его в объект TransactionNumber.

Скрыть детали, где это возможно.

1 голос
/ 22 июня 2010

Вау.Резервное копирование здесь.Метод getMembership() не принадлежит Club.Он принадлежит к набору всех членств, которые вы не реализовали.

1 голос
/ 21 июня 2010

ИМО, я думаю, что это очень сильно зависит от потока приложения - у вас есть Person, когда вы хотите получить Membership данные? Если да, перейдите с:
public Membership getMembership(Person person);

Кроме того, я не вижу никакой причины, по которой Club не может отслеживать членство на основе идентификатора Person, а не фактического объекта - я думаю, это означало бы, что вам не нужно реализовывать hashCode() и equals() методы. (Хотя это всегда хорошая передовая практика).

Как сказал Ури, вам следует задокументировать замедление того, что два Person объекта равны, если их ID равен.

0 голосов
/ 22 июня 2010

На самом деле я бы назвал это идентификатором, но немного изменив исходный дизайн:

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;
}
0 голосов
/ 22 июня 2010

Я бы, вероятно, использовал идентификаторы. Зачем? Взяв удостоверения, я делаю более безопасные предположения о звонящем.

Если у меня есть удостоверение личности, сколько стоит работа, чтобы получить Человека? Может быть и «легко», но для этого нужно попасть в хранилище данных, что медленно ...

Если у меня есть объект Person, сколько стоит получить идентификатор? Простой членский доступ. Быстро и доступно.

0 голосов
/ 22 июня 2010

Если в другом месте нет существенного преимущества, можно сказать, что ключи на карте должны быть однозначными, если это вообще возможно.Тем не менее, обращая внимание на equals () и hashCode (), вы можете заставить любой объект работать в качестве ключа, но equals () и hashCode () не очень приятные вещи, на которые стоит обратить внимание.Вы будете счастливее придерживаться идентификаторов в качестве ключей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...