Является ли хорошей практикой использование объектов домена в качестве ключей?
Является ли хорошей практикой использование объектов домена в качестве ключей для карт (или методов "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);
Какой самый идиоматический, что наиболее удобно, наиболее подходит?
Изменить: Много очень хороших ответов. Я пошел, не раскрывая идентификатор, потому что "Личность" (как вы, возможно, поняли, мой реальный домен не имеет ничего общего с людьми и клубами...) экземпляры легко доступны, но пока они внутренне хранятся в HashMap hashed на id - но, по крайней мере, я разоблачаю его правильно в интерфейсе.
Ответы
Ответ 1
Не используйте id man, это всего лишь плохая идея по всем упомянутым причинам. Вы закроете себя дизайном. Позвольте мне привести пример.
Сейчас вы определяете, что вы являетесь Членством в качестве сопоставления между Клубами для Людей. Справедливо, что ваше членство должно быть картой клубов для "членов", но вы предполагаете, что все члены - это люди, и что, поскольку все идентификаторы людей уникальны, вы считаете, что можете просто использовать идентификатор.
Но что, если в будущем вы хотите расширить свою концепцию членства до "членства в семье", для которой вы создаете таблицу Family и класс Family. В хорошем стиле OO вы извлекаете интерфейс Family и 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;
}
Если вы использовали идентификатор в своем интерфейсе, вам нужно будет либо обеспечить уникальность ключевых значений кросс-таблицы, либо поддерживать два отдельных Карты и отказаться от приятного полиморфного интерфейса.
Поверьте мне, если вы не пишете одноразовые одноразовые приложения, вы хотите избежать использования идентификатора в вашем интерфейсе.
Ответ 2
Предполагая, что это идентификатор базы данных или что-то, что используется только для индексирования (а не как нечто вроде SSN), то в идеальной системе наличие идентификатора является деталью реализации.
В качестве детали реализации я бы предпочел скрыть ее в интерфейсе других объектов домена. Таким образом, членство связано, в основном, с людьми, а не с цифрами.
Конечно, я бы удостоверился, что реализовал hashCode
и equals()
и хорошо документировал, что они имели в виду.
В этом случае я бы явно указал, что равенство двух объектов Person определяется исключительно на основе ID. Это несколько рискованное предложение, но делает код более читаемым, если вы можете его обеспечить. Я чувствую себя более комфортно, когда мои объекты неизменяемы, поэтому на самом деле у меня не было бы двух объектов Person с тем же идентификатором, но с разными именами в течение всей жизни программы.
Ответ 3
Я думаю, что первый случай будет считаться "более чистым" в том смысле, что метод getMembership может потребовать более конкретных данных от самого человека, кроме его идентификатора (предположим, вы не знаете внутренних элементов метода getMembership, хотя это мало смысла, поскольку он, скорее всего, находится в том же домене).
Если окажется, что на самом деле требуются данные из объекта Person, для него не требуется DAO или factory.
Это можно легко вызвать, если ваш язык и/или ORM позволяют использовать прокси-объекты (и если у вас есть удобный способ создания этих прокси).
Но давайте будем честными. Если вы спрашиваете о каком-то членстве человека, вы, скорее всего, уже имеете этот экземпляр Person в памяти, когда вы вызываете этот метод.
Далее по дороге в "инфраструктурную землю" есть также такое представление о деталях реализации, которые Uri уже упоминалось, когда я писал этот ответ (черт возьми, это был быстрым братом!). Чтобы быть конкретным, что, если вы решили, что эта концепция "Person" внезапно имеет составной первичный ключ/идентификатор в базовой базе данных... Вы бы теперь использовали класс идентификатора? Возможно, используйте этот прокси-сервер, о котором мы говорили?
TL; версия DR
Использование идентификатора в кратчайшие сроки действительно проще, но если вы уже используете твердую ORM, я не вижу причин не использовать прокси или какие-либо другие средства для выражения объектно-ориентированной идентификации объекта, который не протекает подробности реализации.
Ответ 4
Если вы действительно практикуете объектно-ориентированный дизайн, тогда вы хотите использовать идею скрытия информации. Как только вы начнете висячие внутренние типы полей объекта person в общедоступном интерфейсе методов объекта членства, вы начинаете принуждать внешних разработчиков (пользователей) к вашим объектам начинать изучать все виды информации о том, что такое объект person, и как он хранится и какой идентификатор он имеет.
Еще лучше, поскольку человек может иметь членство, почему бы вам просто не повесить метод getMemberships в класс person. Кажется гораздо логичнее задавать человеку, у кого есть членство, чем задавать "членство", в котором клубы данного лица могут принадлежать...
Изменить - поскольку OP обновился, чтобы указать, что именно его член заинтересован в нем, а не просто используется как связь между Лицом и Клубом, я обновляю свой ответ.
Короче говоря, класс "Клуб", который вы определяете, вы теперь просите вести себя как "клубный список". У клуба есть список, это не список. В реестре могут быть несколько функций, включая способы поиска людей, принадлежащих клубу. В дополнение к поиску человека по их ID клуба, вы можете посмотреть их по SSN, имени, дате присоединения и т.д. Для меня это говорит о методе класса "Club" под названием getRoster(), который возвращает структуру данных, которая может искать всех лиц в клубе. Назовите это коллекцией. Затем возникает вопрос, можете ли вы использовать методы в существующих классах коллекций для выполнения тех задач, которые вы определили до сих пор, или вам нужно создать подклассу пользовательских коллекций, чтобы предоставить соответствующий метод для поиска записи о членстве.
Поскольку ваша иерархия вашего класса, скорее всего, поддерживается базой данных, и вы, вероятно, берете на себя загрузку информации из базы данных и не обязательно хотите, чтобы вся коллекция просто получила одно членство, вы можете захотеть создать новый класс. Этот класс можно назвать, как я сказал "Реестр". Вы получите экземпляр его из вызова getRoster() в классе "club". Вы бы добавили методы "поиска" в класс на основе любых критериев поиска, которые вы хотели, которые были "общедоступными" сведениями о человеке. Name, clubID, personID, SSN и т.д.
Мой первоначальный ответ применяется только в том случае, если "членство" - это просто отношение, указывающее, к каким клубам относятся люди.
Ответ 5
IMO, я думаю, что это очень зависит от потока приложения - есть ли у вас Person
, когда вы хотите получить детали Membership
? Если да, перейдите:
public Membership getMembership(Person person);
Кроме того, я не вижу причин, по которым Club
не может отслеживать членство на основе идентификатора Person
, а не фактического объекта. Я думаю, это означало бы, что вам не нужно внедрять hashCode()
и equals()
. (Хотя это всегда хорошая передовая практика).
Как сказал Ури, вы должны задокументировать замедление, что два объекта Person
равны, если их ID равен.
Ответ 6
Ого. Резервное копирование здесь. Метод getMembership()
не принадлежит Club
. Он принадлежит к набору всех членов, которые вы не реализовали.
Ответ 7
Как описано другими: используйте объект.
Я работаю над системой, в которой у нас был старый код, который использовал int для представления идентификаторов транзакций. Угадай, что? Мы начали исчерпывать идентификаторы, потому что мы использовали int.
Переход на длинный или BigNumber оказался сложным, потому что люди стали очень изобретательными с именованием. Некоторые используемые
int tranNum
некоторые используемые
int transactionNumber
некоторые используемые
int trannNum
(в комплекте с орфографическими ошибками).
Некоторые люди действительно изобретательны...
Это был беспорядок, и его сортировка была кошмаром. Я закончил с помощью всего кода и преобразовал его в объект TransactionNumber.
Скрыть детали, где это возможно.
Ответ 8
Я бы обычно придерживался меньше, больше. Чем меньше информации требуется для вызова вашего метода, тем лучше. Если вы знаете идентификатор, требуется только идентификатор.
Если вы хотите, предоставьте дополнительные перегрузки, которые принимают дополнительные параметры, такие как весь класс.
Ответ 9
Если у вас уже есть объект, нет причин вытаскивать идентификатор, чтобы получить хэш-ключ.
Пока идентификаторы всегда уникальны, реализуйте hashCode(), чтобы вернуть идентификатор и реализовать equals().
Коэффициенты - каждый раз, когда вам понадобится членство, у вас уже будет Личность, поэтому впоследствии он сохраняет код и путаницу.
Ответ 10
Прежде всего, я поместил бы какие-то геттеры такой природы в DAO (а не на модель). Затем я использовал бы сам объект как параметр, и то, что происходит внутри метода, является деталью реализации.
Ответ 11
Если не существует значительного преимущества, полученного в другом месте, можно сказать, что ключи на карте должны быть однозначными, если это вообще возможно. Тем не менее, обращая внимание на equals() и hashCode(), вы можете заставить любой объект работать как ключ, но equals() и hashCode() не очень приятные вещи, на которые нужно обратить внимание. Вы будете счастливее придерживаться идентификаторов в качестве ключей.
Ответ 12
Я бы, вероятно, использовал идентификаторы. Зачем? Принимая идентификаторы, я делаю более безопасные предположения о вызывающем абоненте.
Если у меня есть идентификатор, сколько работы нужно, чтобы получить Личность? Возможно, это будет "легко", но для этого требуется ударить хранилище данных, которое медленно...
Если у меня есть объект Person, сколько работы это получить ID? Простой доступ к члену. Быстро и доступно.
Ответ 13
Собственно, то, что я хотел бы сделать, это вызвать его по 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;
}