Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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

Ответ 10

Когда я начинал, я все еще находился под сильным влиянием HIDE YOUR DATA OO PRINCIPALS LOL. Я сидел и думал о том, что произойдет, если кто-то изменит состояние одного из объектов, выставленных свойством. Должен ли я читать их только для внешних абонентов? Должен ли я не раскрывать их вообще?

Коллекции довели эти тревоги до крайности. Я имею в виду, кто-то мог удалить все объекты в коллекции, пока я не смотрю!

В конце концов я понял, что если ваши объекты содержат такие жесткие зависимости от их внешне видимых свойств и их типов, которые, если кто-то прикасается к ним в плохом месте, вы начинаете бум, ваша архитектура испорчена.

Имеются веские причины для того, чтобы сделать ваши внешние свойства readonly и их типы неизменными. Но это угловой случай, а не типичный, imho.

Ответ 11

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

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

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

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