Учитывая инкапсуляцию объекта, должны ли получатели возвращать неизменное свойство? - PullRequest
18 голосов
/ 22 сентября 2008

Когда получатель возвращает свойство, например, возвращает List других связанных объектов, если этот список и его объекты неизменны, чтобы предотвратить код за пределами класса, изменяя состояние этих объектов, без основного родительского объекта познающий

Например, если у объекта Contact есть получатель getDetails, который возвращает List из ContactDetails объектов, то любой код, вызывающий этот получатель:

  1. может удалить ContactDetail объекты из этого списка, но объект Contact не знает об этом.
  2. может изменить каждый ContactDetail объект, не зная об этом Contact объекта.

Так что нам здесь делать? Должны ли мы просто доверять вызывающему коду и возвращать легко изменяемые объекты или идти сложным путем и создавать неизменный класс для каждого изменяемого класса?

Ответы [ 11 ]

6 голосов
/ 22 сентября 2008

Это зависит от контекста. Если список предназначен для изменчивости, нет смысла загромождать API основного класса методами для его изменения, когда List имеет совершенно хороший собственный API.

Однако, если основной класс не может справиться с мутациями, вам нужно будет вернуть неизменяемый список - и записи в списке также могут быть неизменными сами.

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

6 голосов
/ 22 сентября 2008

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

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

5 голосов
/ 22 сентября 2008

Если у вас есть контроль над вызывающим кодом, то самое главное, что ваш выбор задокументирован во всех нужных местах.

3 голосов
/ 24 сентября 2008

Джошуа Блох в своей превосходной книге «Эффективная Java» говорит, что вы должны ВСЕГДА делать защитные копии при возврате чего-то подобного. Это может быть немного экстремально, особенно если объекты ContactDetails не являются клонируемыми, но это всегда безопасный способ. В случае сомнений всегда отдавайте предпочтение безопасности кода, а не производительности - если только профилирование не показало, что клонирование является реальным узким местом производительности.

На самом деле вы можете добавить несколько уровней защиты. Вы можете просто вернуть член, который, по сути, дает любому другому классу доступ к внутренним компонентам вашего класса. Очень небезопасно, но по справедливости широко сделано. Это также вызовет у вас проблемы позже, если вы захотите изменить внутренние компоненты так, чтобы ContactDetails сохранялся в наборе. Вы можете вернуть вновь созданный список со ссылками на те же объекты во внутреннем списке. Это безопаснее - другой класс не может удалить или добавить в список, но он может изменить существующие объекты. В-третьих, верните вновь созданный список с копиями объектов ContactDetails. Это безопасный способ, но может быть дорогим.

Я бы сделал это лучше. Не возвращайте список вообще - вместо этого верните итератор по списку . Таким образом, вам не нужно создавать новый список (у List есть метод для получения итератора), но внешний класс не может изменить список. Он по-прежнему может изменять элементы, если вы не напишите свой собственный итератор, который клонирует элементы по мере необходимости. Если вы позже переключитесь на использование другой коллекции для внутреннего использования, она все равно может вернуть итератор, поэтому никаких внешних изменений не требуется.

3 голосов
/ 22 сентября 2008

В частном случае Коллекция, Список, Набор или Карта в Java легко вернуть неизменяемое представление в класс, используя return Collections.unmodifiableList(list);

Конечно, если возможно, что данные основы все еще будут изменены, вам необходимо сделать полную копию списка.

2 голосов
/ 22 сентября 2008

Зависит от контекста, действительно. Но, как правило, да, нужно писать как можно более защитный код (возвращая копий массива , возвращая обтекателей, доступных только для чтения, вокруг коллекции s и т. Д.). В любом случае, это должно быть четко задокументировано .

1 голос
/ 22 сентября 2008

Ваш первый императив должен следовать закону Деметры или «Скажи, не спрашивай»; сообщите экземпляру объекта, что делать, например

contact.print( printer ) ;  // or
contact.show( new Dialog() ) ; // or
contactList.findByName( searchName ).print( printer ) ;

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

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

1 голос
/ 22 сентября 2008

Я думаю, вы обнаружите, что очень редко каждый gettable может быть неизменным.

Что вы можете сделать, это запускать события, когда свойство изменяется в таких объектах. Также не идеальное решение.

Документация, пожалуй, самое прагматичное решение;)

1 голос
/ 22 сентября 2008

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

0 голосов
/ 24 сентября 2008

Прежде всего, сеттеры и геттеры являются признаком плохого ОО. Обычно идея ОО состоит в том, что вы просите объект сделать что-то для вас. Установка и получение противоположны. Sun должна была придумать другой способ реализации Java-бинов, чтобы люди не воспринимали этот шаблон и думали, что он «правильный».

Во-вторых, каждый ваш объект должен быть миром сам по себе - обычно, если вы собираетесь использовать сеттеры и геттеры, они должны возвращать довольно безопасные независимые объекты. Эти объекты могут быть или не быть неизменными, потому что они просто первоклассные объекты. Другая возможность состоит в том, что они возвращают нативные типы, которые всегда неизменны. Поэтому высказывание «Должны ли установщики и получатели возвращать что-то неизменное» не имеет особого смысла.

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

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

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