Зачем продолжать использовать геттеры с неизменяемыми объектами? - программирование
Подтвердить что ты не робот

Зачем продолжать использовать геттеры с неизменяемыми объектами?

Использование неизменяемых объектов становится все более распространенным, даже если программа под рукой никогда не должна запускаться параллельно. И все же мы по-прежнему используем геттеры, для которых требуется 3 строки шаблона для каждого поля и 5 дополнительных символов при каждом доступе (в вашем любимом обычном языке OO). Хотя это может показаться тривиальным, и многие редакторы в любом случае удаляют большую часть нагрузки со стороны программиста, это все еще кажется ненужным усилием.

В чем причины дальнейшего использования аксессуаров против прямого доступа к полю неизменяемых объектов? В частности, существуют ли преимущества для того, чтобы заставить пользователя использовать устройства доступа (для клиента или библиотеки), и если да, то каковы они?


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

4b9b3361

Ответ 1

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

Я не уверен, что вы знакомы с С#, но его дизайн, инструменты и т.д. очень интуитивно понятны и удобны для программистов.
Одна особенность С# (которая также существует в Python, D и т.д.), Которая помогает этому свойству; свойство в основном представляет собой пару методов (геттер и/или сеттер), которые снаружи выглядят точно так же, как поле экземпляра: вы можете назначить ему, и вы можете читать из него точно так же, как переменная экземпляра.
Внутренне, конечно, это метод, и он может что-то сделать.

Но типы данных С# также иногда имеют методы GetXYZ() и SetXYZ(), и иногда они даже открывают свои поля напрямую... и это задает вопрос: как вы выбираете, что делать, когда?

Microsoft имеет отличное руководство для свойств С# и когда вместо использования геттеров/сеттеров:

Свойства должны вести себя так, как если бы они были полями; если метод не может, он не должен быть изменен на свойство. Методы лучше, чем свойства в следующих ситуациях:

  • Метод выполняет трудоемкую операцию. Метод воспринимается медленнее, чем время, необходимое для установки или получения значения поля.
  • Метод выполняет преобразование. Доступ к полю не возвращает преобразованную версию данных, которые он хранит.
  • Метод Get имеет наблюдаемый побочный эффект. Извлечение значения поля не вызывает никаких побочных эффектов.
  • Порядок выполнения важен. Установка значения поля не зависит от возникновения других операций.
  • Вызов метода два раза подряд создает разные результаты.
  • Метод статический, но возвращает объект, который может быть изменен вызывающим. Получение значения поля не позволяет вызывающему абоненту изменять данные, хранящиеся в поле.
  • Метод возвращает массив.

Обратите внимание на, что вся цель этих рекомендаций состоит в том, чтобы сделать все свойства похожими на внешние поля.

Таким образом, единственными реальными причинами использования свойств вместо полей будут:

  • Вы хотите инкапсуляцию, yada yada.
  • Вам нужно подтвердить ввод.
  • Вам нужно извлечь данные из (или отправить данные) в другое место.
  • Вам нужна двусторонняя (ABI) совместимость. Что я имею в виду? Если вы когда-нибудь, по дороге, решите, что вам нужно добавить какую-то проверку (например), затем сменить поле на свойство и перекомпилировать вашу библиотеку, он сломает любые другие исполняемые файлы, которые зависят от него. Но на уровне исходного кода ничего не изменится (если вы не принимаете адреса/ссылки, которых вы, вероятно, не должны быть в любом случае).

Теперь вернемся к Java/С++ и неизменяемым типам данных.

Какой из этих пунктов применим к нашему сценарию?

  • Иногда это не применяется, потому что целая точка неизменной структуры данных состоит в том, чтобы хранить данные, а не иметь (полиморфное) поведение (скажем, тип данных String).
    Какой смысл хранить данные, если вы собираетесь скрыть это и ничего не делать с этим?
    Но иногда это применяется (например, у вас есть неизменяемое дерево) - вы можете не захотеть раскрывать метаданные.
    Но тогда в этом случае вы, очевидно, скроете данные, которые не хотите выставлять, и вы бы не задавали этот вопрос в первую очередь!:)
  • Не применяется; там нет ввода для проверки, потому что ничего не меняется.
  • Не применяется, иначе вы не можете использовать поля!
  • Может или не может применяться.

Теперь у Java и С++ нет свойств, но методы занимают их место - и поэтому приведенный выше совет все еще применяется, и правило для языков без свойств становится:

Если (1) вам не нужна совместимость с ABI, и (2) ваш получатель будет вести себя точно так же, как поле (то есть оно удовлетворяет требованиям в документации MSDN выше), тогда вы должны использовать поле вместо геттера.

Важным моментом для понимания является то, что ничто из этого не является философским; все эти руководства основаны на том, что ожидает программист. Очевидно, что цель в конце дня - (1) выполнить задание и (2) сохранить код удобочитаемым/поддерживаемым. Было обнаружено, что руководство, приведенное выше, полезно для того, чтобы это произошло - и ваша цель должна заключаться в том, чтобы делать все, что вам нравится, что это произойдет.

Ответ 2

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

Тем не менее, нет причин, чтобы геттеры были особенно подробными. В частности, в Java-мире, даже если префикс "получить" очень хорошо укоренен, вы все равно найдете методы getter, названные после самого значения (то есть метод foo() вместо getFoo()), и что прекрасный способ сохранить несколько символов. Во многих других языках OO вы можете определить геттер и использовать синтаксис, который выглядит как доступ к полю, поэтому нет дополнительной многословности.

Ответ 3

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

Рассмотрим систему, в которой каждое измененное поле было скрыто за аксессуарами, в то время как каждое неизменяемое поле не было. Теперь рассмотрим следующий фрагмент кода:

class Node {
    private final List<Node> children;

    Node(List<Node> children) {
        this.children = new LinkedList<>(children);
    }

    public List<Node> getChildren() {
        return /* Something here */;
    }
}

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

  • Ничего. Поле children возвращается как есть, и вы не можете изменить список, потому что вы нарушите неизменность Node. Чтобы изменить List, вы должны скопировать его, операцию O (n).
  • Скопируется, например: return new LinkedList<>(children);. Это операция O (n). Вы можете изменить этот список.
  • Возвращается немодифицируемая версия, например: return new UnmodifiableList<>(children);. Это операция O (1). Опять же, чтобы изменить это List, вы должны скопировать его, операцию O (n).

Во всех случаях для изменения возвращаемого списка требуется операция O (n) для его копирования, тогда как доступ только для чтения происходит от O (1) или O (n). Здесь важно отметить, что, следуя проекту по контракту, вы не можете знать, какую реализацию выбрал редактор библиотеки, и поэтому он должен принять наихудший случай O (n). Следовательно, O (n) доступа и O (n) для создания вашей собственной модифицируемой копии.

Теперь рассмотрим следующее:

class Node {
    public final UnmodifiableList<Node> children;

    Node(List<Node> children) {
        this.children = new UnmodifiableList<>(children);
    }
}

Теперь, везде, где вы видите root.children, существует ровно одна возможность, а именно UnmodifiableList и, следовательно, вы можете предполагать O (1) доступ и O (n) для создания локально изменяемой копии.

Очевидно, что можно сделать выводы о характеристиках производительности доступа к полю в последнем случае, тогда как единственный вывод, который может быть сделан в первом, заключается в том, что производительность в худшем случае и, следовательно, случай, который мы должны принять, намного хуже, чем прямой доступ к полям. Напомним, что программист должен учитывать функцию сложности O (n) при каждом доступе.


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

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

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

Ответ 4

Джошуа Блох, в Эффективная Java (2nd Edition) "Пункт 14: В публичных классах используйте методы доступа, а не общедоступные поля", следующее, чтобы сказать об экспонировании неизменяемых полей:

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

и суммирует главу с:

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

Ответ 5

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

 public class Temp {
    public final List<Integer> list;

    public Temp() {
        this.list = new ArrayList<Integer>();
        this.list.add(42);
    }

   public static void foo() {
      Temp temp = new Temp();
      temp.list = null; // not valid
      temp.list.clear(); //perferctly fine, reference didn't change. 
    }
 }

Ответ 6

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

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

Edit Как указано @Thilo; Другая причина: Возможно, вам не нужно раскрывать, как хранится поле.

спасибо @Thilo.

Ответ 7

Одна очень практичная причина для продолжения практики генерации (надеюсь, что никто не пишет их вручную в наши дни) getters в Java-программах даже для неизменных объектов "ценности", где, на мой взгляд, это лишние накладные расходы:

Многие библиотеки и инструменты полагаются на старые соглашения JavaBeans (или, по крайней мере, часть getter и seters).

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

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

Итак, мы просто продолжаем писать геттеры даже для неизменяемых объектов.

Ответ 8

В чем причины дальнейшего использования аксессуаров против прямого доступа к полю неизменяемых объектов? В частности, существуют ли преимущества для того, чтобы заставить пользователя использовать устройства доступа (для клиента или библиотеки), и если да, то каковы они?

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