Код как:
public Thing[] getThings(){
return things;
}
Не имеет особого смысла, так как ваш метод доступа не делает ничего, кроме непосредственного возврата внутренней структуры данных. С тем же успехом вы можете просто заявить Thing[] things
о себе public
. Идея метода доступа состоит в том, чтобы создать интерфейс, который изолирует клиентов от внутренних изменений и не позволяет им манипулировать реальной структурой данных, за исключением скрытых способов, разрешенных интерфейсом. Как вы обнаружили, когда весь ваш клиентский код сломался, ваш метод доступа этого не сделал - это просто напрасный код. Я думаю, что многие программисты, как правило, пишут такой код, потому что они где-то узнали, что все должно быть инкапсулировано с методами доступа - но это по причинам, которые я объяснил. Делать это просто для того, чтобы «следовать форме», когда метод доступа не служит какой-либо цели, - это просто шум.
Я определенно рекомендую ваше предлагаемое решение, которое решает некоторые из наиболее важных задач инкапсуляции: предоставление клиентам надежного, незаметного интерфейса, который изолирует их от внутренних деталей реализации вашего класса и не позволяет им касаться внутренней структуры данных. ожидайте способами, которые вы решите, уместны - «закон наименее необходимой привилегии». Если вы посмотрите на большие популярные платформы ООП, такие как CLR, STL, VCL, предложенный вами шаблон широко распространен именно по этой причине.
Вы должны всегда делать это? Не обязательно. Например, если у вас есть вспомогательные или дружественные классы, которые, по сути, являются компонентом вашего основного рабочего класса и не являются «фронтальными», это необязательно - это излишнее количество, которое добавит много ненужного кода. И в этом случае я бы вообще не использовал метод доступа - как объяснено, это бессмысленно. Просто объявите структуру данных так, чтобы она была доступна только для основного класса, который ее использует - большинство языков поддерживают способы сделать это - friend
или объявив ее в том же файле, что и основной рабочий класс, и т. Д.
Единственный недостаток, который я вижу в вашем предложении, заключается в том, что это больше работы для кодирования (и теперь вам придется перекодировать ваши потребительские классы - но вы все равно должны были это делать). Но это не совсем недостаток - Вы должны сделать это правильно, а иногда это требует больше работы.
Одна из вещей, которая делает хорошего программиста хорошим, заключается в том, что они знают, когда дополнительная работа того стоит, а когда - нет. В конечном счете, добавление дополнительного сейчас окупится большими дивидендами в будущем - если не на этом проекте, то на других. Научитесь правильно кодировать и использовать свою голову, а не просто роботизированно следовать предписанным формам.
Обратите внимание, что коллекции, более сложные, чем массивы, могут требовать, чтобы вмещающий класс реализовал даже более трех методов только для доступа к внутренней структуре данных.
Если вы представляете всю структуру данных через содержащий класс, IMO, вам нужно подумать о том, почему этот класс вообще инкапсулирован, если не просто предоставить более безопасный интерфейс - «класс-обертку». Вы говорите, что содержащий класс не существует для этой цели - так что, возможно, что-то не так с вашим дизайном. Подумайте о том, чтобы разбить ваши классы на более дискретные модули и разбить их на слои.
Класс должен иметь одну ясную и незаметную цель и предоставлять интерфейс для поддержки этой функциональности - не более. Возможно, вы пытаетесь связать вещи, которые не принадлежат друг другу. Когда вы это сделаете, вещи будут ломаться каждый раз, когда вам нужно будет внести изменения. Чем меньше и деликатнее ваши занятия, тем легче изменить положение вещей: подумайте LEGO.
get(index)
,add()
,size()
,remove(index)
, иremove(Object)
. Используя предложенную технику, класс, который содержит этот ArrayList, должен иметь пять открытых методов только для делегирования внутренней коллекции. И цель этого класса в программе, скорее всего, заключается не в инкапсуляции этого ArrayList, а скорее в выполнении чего-то другого. ArrayList - это просто деталь. [...]remove
не только для чтения. Насколько я понимаю, ОП хочет обнародовать все - как в оригинальном коде до предлагаемого изменения.public Thing[] getThings(){return things;}
Это то, что мне не нравится.Вы спросили: я должен всегда полностью инкапсулировать внутреннюю структуру данных?
Краткий ответ: да, большую часть времени, но не всегда .
Длинный ответ: я думаю, что классы делятся на следующие категории:
Классы, которые инкапсулируют простые данные. Пример: 2D точка. Легко создавать публичные функции, которые предоставляют возможность получать / устанавливать координаты X и Y, но вы можете легко скрыть внутренние данные без особых проблем. Для таких классов раскрытие деталей внутренней структуры данных не требуется.
Контейнерные классы, которые инкапсулируют коллекции. STL имеет классические контейнерные классы. Я считаю
std::string
иstd::wstring
среди тех тоже. Они обеспечивают богатый интерфейс для борьбы с абстракциями , ноstd::vector
,std::string
иstd::wstring
также обеспечить возможность получить доступ к необработанным данным. Я бы не торопился называть их плохо разработанными классами. Я не знаю оправдания для этих классов, выставляющих их необработанные данные. Тем не менее, в своей работе я счел необходимым выставлять необработанные данные при работе с миллионами узлов сетки и данными на этих узлах сетки по соображениям производительности.Важным моментом раскрытия внутренней структуры класса является то, что вы должны долго и усердно думать, прежде чем дать ему зеленый сигнал. Если интерфейс является внутренним для проекта, изменить его в будущем будет дорого, но не невозможно. Если интерфейс является внешним по отношению к проекту (например, когда вы разрабатываете библиотеку, которая будет использоваться другими разработчиками приложений), может быть невозможно изменить интерфейс без потери ваших клиентов.
Классы, которые в основном функциональны по своей природе. Примеры:
std::istream
,std::ostream
, итераторы таких контейнеров STL. Совершенно глупо раскрывать внутренние детали этих классов.Гибридные классы. Это классы, которые инкапсулируют некоторую структуру данных, но также обеспечивают алгоритмическую функциональность. Лично я думаю, что это результат плохо продуманного дизайна. Однако, если вы их найдете, вам нужно решить, имеет ли смысл выставлять их внутренние данные в каждом конкретном случае.
В заключении: Единственный раз , когда я счел необходимым подвергать внутреннюю структуру данных класса, когда он стал узким местом.
источник
Вместо того, чтобы возвращать необработанные данные напрямую, попробуйте что-то вроде этого
Таким образом, вы, по сути, предоставляете собственную коллекцию, которая представляет любое лицо миру, которое вам нужно. Тогда в вашей новой реализации
Теперь у вас есть правильная инкапсуляция, скрыты детали реализации и обеспечена обратная совместимость (за плату).
источник
List
. Методы доступа, которые просто возвращают элементы данных, даже с помощью преобразования, чтобы сделать вещи более надежными, не очень хорошая инкапсуляция IMO. Рабочий класс должен обрабатывать все это, а не клиент. Чем «тупее» должен быть клиент, тем он будет надежнее. Кроме того, я не уверен, что вы ответили на вопрос ...Вы должны использовать интерфейсы для этих вещей. Не поможет в вашем случае, так как массив Java не реализует эти интерфейсы, но вы должны делать это с этого момента:
Таким образом , вы можете изменить
ArrayList
кLinkedList
или что - нибудь еще, и вы не будете нарушать любой код , так как все коллекции Java (кроме массивов) , которые имеют (псевдо?) Произвольный доступ, вероятно , реализуетList
.Вы также можете использовать
Collection
, которые предлагают меньше методов, чем,List
но могут поддерживать коллекции без произвольного доступа, илиIterable
которые могут даже поддерживать потоки, но не предлагают много с точки зрения методов доступа ...источник
List
как производные классы, это имело бы больше смысла, но просто «надеяться на лучшее» не очень хорошая идея. «Хороший программист смотрит в обе стороны на улице с односторонним движением».List
(илиCollection
, по крайней мереIterable
). В этом весь смысл этих интерфейсов, и обидно, что массивы Java не реализуют их, но они являются официальным интерфейсом для коллекций в Java, поэтому не так уж и сложно предположить, что любая коллекция Java будет их реализовывать - если эта коллекция не старше чемList
, и в этом случае очень легко обернуть его с помощью AbstractList .List
ихArrayList
, но это не значит, что реализация может быть защищена на 100% - вы всегда можете использовать отражение для доступа к закрытым полям. Делать это не одобряется, но кастинг также не одобряется (хотя и не так сильно). Смысл инкапсуляции не в том, чтобы предотвратить злонамеренный взлом, а скорее в том, чтобы пользователи не зависели от деталей реализации, которые вы, возможно, захотите изменить. ИспользованиеList
интерфейса делает именно это - пользователи класса могут зависеть отList
интерфейса, а не от конкретногоArrayList
класса, который может измениться.Это довольно часто, чтобы скрыть вашу внутреннюю структуру данных от внешнего мира. Иногда это излишне, особенно в DTO. Я рекомендую это для модели домена. Если вообще требуется его выставить, верните неизменную копию. Наряду с этим я предлагаю создать интерфейс с такими методами, как get, set, remove и т. Д.
источник