Зачем использовать геттеры и сеттеры / средства доступа?
В чем преимущество использования геттеров и сеттеров - которые только получают и устанавливают - вместо простого использования открытых полей для этих переменных?
Если геттеры и сеттеры когда-либо делают больше, чем просто get / set, я могу понять это очень быстро, но я не на 100% уверен в том, как:
public String foo;
хуже чем:
private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }
В то время как первый занимает гораздо меньше стандартного кода.
41 ответ
На самом деле есть много веских причин для того, чтобы рассмотреть возможность использования методов доступа, а не непосредственно выставлять поля класса - кроме просто аргумента инкапсуляции и облегчения будущих изменений.
Вот некоторые из причин, которые мне известны:
- Инкапсуляция поведения, связанного с получением или установкой свойства - это позволяет более легко добавлять дополнительные функции (например, проверку) позже.
- Скрытие внутреннего представления свойства при выставлении свойства с использованием альтернативного представления.
- Изоляция вашего открытого интерфейса от изменений - позволяет общему интерфейсу оставаться постоянным, пока реализация меняется, не затрагивая существующих потребителей.
- Управление семантикой свойства времени жизни и управления памятью (распоряжения) - особенно важно в неуправляемых средах памяти (таких как C++ или Objective-C).
- Предоставление точки перехвата отладки для случая, когда свойство изменяется во время выполнения - отладка, когда и где свойство изменилось на определенное значение, может быть довольно трудной без этого на некоторых языках.
- Улучшенная совместимость с библиотеками, предназначенными для работы с методами получения / установки свойств - на ум приходят Mocking, Serialization и WPF.
- Позволяет наследникам изменять семантику поведения и представления свойства путем переопределения методов получения / установки.
- Позволяет получить / получить сеттер как лямбда-выражения, а не значения.
- Получатели и установщики могут разрешать разные уровни доступа - например, получение может быть открытым, но набор может быть защищен.
Потому что через 2 недели (месяцы, годы), когда вы поймете, что ваш установщик должен делать больше, чем просто установить значение, вы также поймете, что свойство использовалось непосредственно в 238 других классах:-)
Открытое поле не хуже пары получатель / установщик, которая ничего не делает, кроме как возвращает поле и присваивает ему значение. Во-первых, ясно, что (в большинстве языков) функциональной разницы нет. Любая разница должна быть в других факторах, таких как ремонтопригодность или удобочитаемость.
Часто упоминаемое преимущество пар геттер / сеттер - это не так. Есть утверждение, что вы можете изменить реализацию, и ваши клиенты не должны перекомпилироваться. Предположительно, сеттеры позволяют вам добавить функциональность, такую как проверка, и вашим клиентам даже не нужно знать об этом. Однако добавление проверки к установщику является изменением его предварительных условий, нарушением предыдущего контракта, который, по сути, заключался в том, что "вы можете положить сюда что-нибудь, и вы можете получить то же самое позже от получателя".
Итак, теперь, когда вы нарушили контракт, изменение каждого файла в кодовой базе - это то, что вы должны хотеть делать, а не избегать. Если вы избегаете этого, вы делаете предположение, что весь код предполагал, что контракт для этих методов был другим.
Если это не должно было быть контрактом, то интерфейс позволял клиентам переводить объект в недопустимые состояния. Это полная противоположность инкапсуляции. Если это поле не может быть действительно установлено с нуля с самого начала, почему не было проверки с самого начала?
Этот же аргумент применим к другим предполагаемым преимуществам этих пар сквозных методов получения / установки: если вы позже решите изменить установленное значение, вы нарушаете контракт. Если вы переопределяете функциональность по умолчанию в производном классе, выходя за рамки нескольких безобидных изменений (таких как ведение журнала или другое ненаблюдаемое поведение), вы нарушаете контракт базового класса. Это является нарушением принципа замещаемости Лискова, который рассматривается как один из принципов ОО.
Если у класса есть эти тупые методы получения и установки для каждого поля, то это класс, который не имеет никаких инвариантов, никаких контрактов. Это действительно объектно-ориентированный дизайн? Если все, что есть в классе, это те геттеры и сеттеры, это просто тупой держатель данных, и тупые держатели данных должны выглядеть как тупые держатели данных:
class Foo {
public:
int DaysLeft;
int ContestantNumber;
};
Добавление пар сквозного получения / установки в такой класс не имеет значения. Другие классы должны обеспечивать значимые операции, а не только операции, которые уже предоставляют поля. Вот как вы можете определить и поддерживать полезные инварианты.
Клиент: "Что я могу сделать с объектом этого класса?"
Дизайнер: "Вы можете читать и писать несколько переменных".
Клиент: "Ох... круто, наверное?"
Есть причины использовать геттеры и сеттеры, но если этих причин не существует, создание пар геттер / сеттер во имя ложных богов инкапсуляции не очень хорошая вещь. Действительные причины для создания или получения включают в себя такие вещи, которые часто упоминаются как потенциальные изменения, которые вы можете сделать позже, такие как проверка или другие внутренние представления. Или, может быть, значение должно быть доступно для чтения клиентами, но не доступно для записи (например, при чтении размера словаря), так что простой метод получения - хороший выбор. Но эти причины должны быть, когда вы делаете выбор, а не просто как потенциальная вещь, которую вы можете захотеть позже. Это пример YAGNI (онтебе не нужен).
Многие люди говорят о преимуществах добытчиков и сеттеров, но я хочу сыграть в адвоката дьявола. Прямо сейчас я отлаживаю очень большую программу, в которой программисты решили делать все, что нужно: геттеры и сеттеры. Это может показаться хорошим, но это кошмар обратного инжиниринга.
Допустим, вы просматриваете сотни строк кода и сталкиваетесь с этим:
person.name = "Joe";
Это красивый кусок кода, пока вы не поймете, что это сеттер. Теперь вы следите за этим установщиком и обнаруживаете, что он также устанавливает person.firstName, person.lastName, person.isHuman, person.hasReallyCommonFirstName и вызывает person.update(), который отправляет запрос в базу данных и т. Д. где произошла утечка памяти.
Понимание локального фрагмента кода на первый взгляд является важным свойством хорошей читабельности, которое склонны нарушать методы получения и установки. Вот почему я стараюсь избегать их, когда могу, и минимизирую то, что они делают, когда я их использую.
В чистом объектно-ориентированном мире геттеры и сеттеры это ужасный анти-паттерн. Прочитайте эту статью: Getters / Setters. Злой. Период Короче говоря, они поощряют программистов думать об объектах как о структурах данных, и этот тип мышления является чисто процедурным (как в COBOL или C). В объектно-ориентированном языке нет структур данных, а есть только объекты, демонстрирующие поведение (не атрибуты / свойства!)
Вы можете найти больше о них в Разделе 3.5 " Элегантных объектов" (моя книга об объектно-ориентированном программировании).
Есть много причин. Мой любимый, когда вам нужно изменить поведение или регулировать то, что вы можете установить для переменной. Например, допустим, у вас был метод setSpeed (int speed). Но вы хотите, чтобы вы могли установить максимальную скорость только 100. Вы бы сделали что-то вроде:
public void setSpeed(int speed) {
if ( speed > 100 ) {
this.speed = 100;
} else {
this.speed = speed;
}
}
Теперь, что если ВЕЗДЕ в своем коде вы используете открытое поле, а потом поняли, что вам нужно вышеуказанное требование? Получайте удовольствие, охотясь за каждым использованием открытого поля, а не просто изменяя ваш сеттер.
Мои 2 цента:)
Одним из преимуществ аксессоров и мутаторов является то, что вы можете выполнять валидацию.
Например, если foo
был публичным, я мог легко установить его null
и тогда кто-то другой может попытаться вызвать метод объекта. Но его больше нет! С setFoo
метод, я мог бы гарантировать, что foo
никогда не был установлен на null
,
Средства доступа и мутаторы также допускают инкапсуляцию - если вы не должны видеть значение после его установки (возможно, оно установлено в конструкторе, а затем используется методами, но никогда не должно изменяться), оно никогда не будет никем замечено. Но если вы можете позволить другим классам видеть или изменять его, вы можете предоставить надлежащий метод доступа и / или мутатор.
Спасибо, это действительно прояснило мое мышление. Вот (почти) 10 (почти) веских причин НЕ использовать геттеры и сеттеры:
- Когда вы поймете, что вам нужно сделать больше, чем просто установить и получить значение, вы можете просто сделать поле приватным, что мгновенно сообщит вам, где вы непосредственно получили к нему доступ.
- Любая проверка, которую вы выполняете, может быть только контекстно-свободной, что на практике редко происходит.
- Вы можете изменить установленное значение - это абсолютный кошмар, когда звонящие передают вам значение, которое они [шок ужас] хотят, чтобы вы хранили как есть.
- Вы можете скрыть внутреннее представление - фантастика, поэтому вы уверены, что все эти операции симметричны, верно?
- Вы изолировали свой общедоступный интерфейс от изменений под листами - если вы проектировали интерфейс и не были уверены, что прямой доступ к чему-либо был в порядке, то вам следовало продолжить проектирование.
- Некоторые библиотеки ожидают этого, но не так много - отражение, сериализация, фиктивные объекты прекрасно работают с открытыми полями.
- Унаследовав этот класс, вы можете переопределить функциональность по умолчанию - другими словами, вы можете ДЕЙСТВИТЕЛЬНО сбить с толку вызывающих, не только скрывая реализацию, но и делая ее несовместимой.
Последние три я просто ухожу (N/A или D/C)...
Зависит от вашего языка. Вы пометили это как "объектно-ориентированный", а не "Java", поэтому я хотел бы отметить, что ответ ChssPly76 зависит от языка. Например, в Python нет причин использовать геттеры и сеттеры. Если вам нужно изменить поведение, вы можете использовать свойство, которое заключает в себе метод получения и установки базового доступа к атрибутам. Что-то вроде этого:
class Simple(object):
def _get_value(self):
return self._value -1
def _set_value(self, new_value):
self._value = new_value + 1
def _del_value(self):
self.old_values.append(self._value)
del self._value
value = property(_get_value, _set_value, _del_value)
РЕДАКТИРОВАТЬ: Я ответил на этот вопрос, потому что есть множество людей, изучающих программирование, спрашивающих об этом, и большинство ответов очень технически компетентны, но их не так легко понять, если вы новичок. Мы все были новичками, поэтому я решил попробовать свои силы в более дружелюбном ответе новичка.
Двумя основными из них являются полиморфизм и валидация. Даже если это просто глупая структура данных.
Допустим, у нас есть этот простой класс:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Очень простой класс, который определяет, сколько в нем жидкости, и какова ее емкость (в миллилитрах).
Что происходит, когда я делаю:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacity = 1000;
Ну, ты не ожидал, что это сработает, верно? Вы хотите, чтобы была какая-то проверка здравомыслия. И что еще хуже, что если бы я никогда не указывал максимальную вместимость? О, дорогой, у нас есть проблема.
Но есть и другая проблема. Что, если бутылки были только одним типом контейнера? Что если бы у нас было несколько контейнеров, все с емкостями и количеством жидкости, заполненной? Если бы мы могли просто создать интерфейс, мы могли бы позволить остальной части нашей программы принять этот интерфейс, и бутылки, канистры и все виды вещей будут работать взаимозаменяемо. Разве это не было бы лучше? Поскольку интерфейсы требуют методов, это тоже хорошо.
Мы бы получили что-то вроде:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
public void setCapcityMl(int capacityMl);
}
Большой! А теперь мы просто изменим бутылку на это:
public class Bottle extends LiquidContainer {
private int capacityMl;
private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {
this.capacityMl = capacityMl;
this.amountFilledMl = amountFilledMl;
checkNotOverFlow();
}
public int getAmountMl() {
return amountFilledMl;
}
public void setAmountMl(int amountMl) {
this.amountFilled = amountMl;
checkNotOverFlow();
}
public int getCapacityMl() {
return capacityMl;
public void setCapcityMl(int capacityMl) {
this.capacityMl = capacityMl;
checkNotOverFlow();
}
private void checkNotOverFlow() {
if(amountOfWaterMl > capacityMl) {
throw new BottleOverflowException();
}
}
Я оставлю определение исключение BottleOverflowException в качестве упражнения для читателя.
Теперь обратите внимание, насколько это надежнее. Теперь мы можем иметь дело с любым типом контейнера в нашем коде, приняв LiquidContainer вместо Bottle. И то, как эти бутылки справляются с такими вещами, может отличаться. У вас могут быть бутылки, которые записывают свое состояние на диск при его изменении, или бутылки, которые сохраняются в базах данных SQL или GNU знает, что еще.
И все это может иметь разные способы справиться с различными возгласами. Бутылка просто проверяет, и если она переполнена, она генерирует исключение RuntimeException. Но это может быть неправильно. (Есть полезная дискуссия об обработке ошибок, но я намеренно держу ее здесь очень просто. Люди в комментариях, скорее всего, укажут на недостатки этого упрощенного подхода.;))
И да, похоже, что мы перешли от очень простой идеи к быстрому получению гораздо лучших ответов.
Также есть третья вещь, к которой обращаются не все: методы получения и получения используют вызовы методов. Это означает, что они везде выглядят как обычные методы. Вместо того, чтобы иметь странный специфический синтаксис для DTO и прочего, у вас везде одно и то же.
Ну, я просто хочу добавить, что даже если иногда они необходимы для инкапсуляции и безопасности ваших переменных / объектов, если мы хотим закодировать реальную объектно-ориентированную программу, нам нужно ОСТАНОВИТЬ ПРЕОДОЛЕНИЕ АКСЕССОРОВ, потому что иногда мы сильно зависимы на них, когда это действительно не нужно, и это делает почти то же самое, как если бы мы открыли переменные.
Я знаю, что уже немного поздно, но я думаю, что есть люди, которые заинтересованы в производительности.
Я сделал небольшой тест производительности. Я написал класс "NumberHolder", который, ну, в общем, содержит целое число. Вы можете прочитать это Integer, используя метод getter.anInstance.getNumber()
или путем прямого доступа к номеру с помощью anInstance.number
, Моя программа читает число 1 000 000 000 раз обоими способами. Этот процесс повторяется пять раз, и время печатается. Я получил следующий результат:
Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms
(Время 1 - прямой путь, Время 2 - получатель)
Видите ли, геттер (почти) всегда немного быстрее. Затем я попробовал с разным количеством циклов. Вместо 1 миллиона я использовал 10 миллионов и 0,1 миллиона. Результаты, достижения:
10 миллионов циклов:
Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms
С 10 миллионами циклов времена почти одинаковы. Вот 100 тысяч (0,1 миллиона) циклов:
Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms
Также с разным количеством циклов геттер немного быстрее обычного. Я надеюсь, что это помогло вам.
Не используйте установщики-получатели, если они не нужны для вашей текущей поставки. Т.е. не слишком задумывайтесь о том, что произойдет в будущем, если что-либо изменить, это запрос на изменение в большинстве производственных приложений, систем.
Думайте просто, легко, добавляйте сложность, когда это необходимо.
Я бы не воспользовался незнанием владельцев бизнеса глубоких технических ноу-хау только потому, что я думаю, что это правильно, или мне нравится подход.
У меня есть огромная система, написанная без методов установки геттеров, только с модификаторами доступа и некоторыми методами для проверки логики выполнения biz. Если вам абсолютно необходимо. Используйте что угодно.
Я потратил некоторое время на обдумывание этого случая с Java, и я считаю, что настоящие причины таковы:
- Код для интерфейса, а не реализация
- Интерфейсы только указывают методы, а не поля
Другими словами, единственный способ указать поле в интерфейсе - это предоставить метод для записи нового значения и метод для чтения текущего значения.
Эти методы являются печально известными методами получения и установки....
Мы используем геттеры и сеттеры:
- для повторного использования
- выполнить проверку на более поздних этапах программирования
Методы получения и установки являются открытыми интерфейсами для доступа к закрытым членам класса.
Инкапсуляционная мантра
Мантра инкапсуляции - сделать поля приватными, а методы - открытыми.
Методы получения: мы можем получить доступ к закрытым переменным.
Методы установки: мы можем модифицировать приватные поля.
Хотя методы getter и setter не добавляют новую функциональность, мы можем передумать вернуться позже, чтобы сделать этот метод
- лучше;
- безопаснее; а также
- Быстрее.
Везде, где можно использовать значение, можно добавить метод, который возвращает это значение. Вместо:
int x = 1000 - 500
использование
int x = 1000 - class_name.getValue();
С точки зрения непрофессионала
Предположим, нам нужно хранить детали этого Person
, это Person
имеет поля name
, age
а также sex
, Это включает в себя создание методов для name
, age
а также sex
, Теперь, если нам нужно создать другого человека, становится необходимым создать методы для name
, age
, sex
снова и снова
Вместо этого мы можем создать бин class(Person)
с методами получения и установки. Так что завтра мы можем просто создать объекты этого Боба class(Person class)
всякий раз, когда нам нужно добавить нового человека (см. рисунок). Таким образом, мы повторно используем поля и методы класса bean, что намного лучше.
Это может быть полезно для отложенной загрузки. Скажем, рассматриваемый объект хранится в базе данных, и вы не захотите получить его, если он вам не нужен. Если объект получен получателем, то внутренний объект может быть нулевым, пока кто-то не запросит его, тогда вы можете получить его при первом обращении к получателю.
У меня был базовый класс страниц в проекте, который мне передали, который загружал некоторые данные из пары разных вызовов веб-службы, но данные в этих вызовах веб-службы не всегда использовались на всех дочерних страницах. Веб-сервисы, несмотря на все преимущества, являются пионерами новых определений "медленных", поэтому вы не хотите звонить через веб-сервис, если в этом нет необходимости.
Я перешел из открытых полей к получателям, и теперь получатели проверяют кеш, и если его там нет, вызывают веб-службу. Таким образом, с небольшой упаковкой было предотвращено множество вызовов веб-служб.
Таким образом, получатель спасает меня от попыток выяснить на каждой дочерней странице, что мне понадобится. Если мне это нужно, я звоню получателю, и он находит его для меня, если у меня его еще нет.
protected YourType _yourName = null;
public YourType YourName{
get
{
if (_yourName == null)
{
_yourName = new YourType();
return _yourName;
}
}
}
Один аспект, который я пропустил в ответах, - спецификация доступа:
- для членов у вас есть только одна спецификация доступа для настройки и получения
- для сеттеров и геттеров вы можете точно настроить его и определить отдельно
В языках, которые не поддерживают "свойства" (C++, Java) или требуют перекомпиляции клиентов при изменении полей на свойства (C#), использование методов get/set проще изменить. Например, добавление логики проверки в метод setFoo не потребует изменения открытого интерфейса класса.
В языках, которые поддерживают "реальные" свойства (Python, Ruby, может быть, Smalltalk?), Нет смысла получать / устанавливать методы.
Один из основных принципов ОО дизайна: инкапсуляция!
Это дает вам много преимуществ, одним из которых является то, что вы можете изменить реализацию метода получения / установки за кулисами, но любой потребитель этого значения будет продолжать работать до тех пор, пока тип данных остается прежним.
Вы должны использовать геттеры и сеттеры, когда:
- Вы имеете дело с чем-то, что концептуально является атрибутом, но:
- Ваш язык не имеет свойств (или какого-либо подобного механизма, такого как переменные трассировки Tcl), или
- Поддержка свойств вашего языка недостаточна для этого варианта использования, или
- Идиоматические соглашения вашего языка (или иногда вашей платформы) поощряют геттеры или сеттеры для этого варианта использования.
Так что это очень редко общий вопрос ОО; это вопрос конкретного языка, с разными ответами для разных языков (и разных вариантов использования).
С точки зрения теории ОО, геттеры и сеттеры бесполезны. Интерфейс вашего класса - это то, что он делает, а не его состояние. (Если нет, вы написали неправильный класс.) В очень простых случаях, когда то, что делает класс, это просто, например, представляет точку в прямоугольных координатах,* атрибуты являются частью интерфейса; геттеры и сеттеры просто заволакивают это. Но в любом другом случае, кроме очень простых, ни атрибуты, ни методы получения и установки не являются частью интерфейса.
Другими словами: если вы считаете, что потребители вашего класса не должны даже знать, что у вас есть spam
атрибут, тем более уметь изменить его волей-неволей, а затем дать им set_spam
метод это последнее, что вы хотите сделать.
* Даже для этого простого класса вы не обязательно хотите разрешить установку x
а также y
ценности. Если это действительно класс, не должно ли быть таких методов, как translate
, rotate
, так далее.? Если это только класс, потому что ваш язык не имеет записей / структур / именованных кортежей, то это не вопрос ОО…
Но никто никогда не делает общий дизайн ОО. Они занимаются дизайном и реализацией на определенном языке. А в некоторых языках геттеры и сеттеры далеко не бесполезны.
Если у вашего языка нет свойств, то единственный способ представить что-то, что концептуально является атрибутом, но на самом деле вычисляется или проверяется и т. Д., - через геттеры и сеттеры.
Даже если у вашего языка есть свойства, могут быть случаи, когда они недостаточны или неуместны. Например, если вы хотите разрешить подклассам управлять семантикой атрибута, в языках без динамического доступа подкласс не может заменить вычисленное свойство для атрибута.
Что касается "что если я захочу изменить свою реализацию позже?" вопрос (который повторяется несколько раз в разных формулировках как в вопросе OP, так и в принятом ответе): если это действительно чистое изменение реализации, и вы начали с атрибута, вы можете изменить его на свойство, не влияя на интерфейс. Если, конечно, ваш язык не поддерживает это. Так что это опять тот же случай.
Кроме того, важно следовать идиомам языка (или структуры), который вы используете. Если вы пишете красивый код в стиле Ruby на C#, любой опытный разработчик C#, кроме вас, будет испытывать трудности при его чтении, и это плохо. Некоторые языки имеют более сильные культуры в своих соглашениях, чем другие. И это не может быть совпадением, что Java и Python, которые находятся на разных концах спектра того, насколько идиоматичны получатели, оказались с двумя из самых сильных культур.
Помимо читателей-людей, будут библиотеки и инструменты, которые ожидают, что вы будете следовать соглашениям, и, если вы этого не сделаете, сделаете вашу жизнь тяжелее. Привязка виджетов Interface Builder к чему угодно, кроме свойств ObjC, или использование определенных библиотек Java-макетов без геттеров только усложняет вашу жизнь. Если инструменты важны для вас, не боритесь с ними.
Код развивается. private
отлично подходит, когда вам нужна защита членов данных. В конце концов, все классы должны быть своего рода "минипрограммами", которые имеют четко определенный интерфейс, который вы не можете просто испортить.
Тем не менее, разработка программного обеспечения не сводится к установке окончательной версии класса, как будто вы нажимаете на чугунную статую с первой попытки. Пока вы работаете с ним, код больше похож на глину. Он развивается по мере того, как вы его разрабатываете и узнаете больше о проблемной области, которую вы решаете. Во время разработки классы могут взаимодействовать друг с другом, чем они должны (зависимость, которую вы планируете выделить), объединяться или разделяться. Поэтому я думаю, что спор сводится к людям, не желающим писать религиозно
int getVar() const { return var ; }
Так что у тебя есть:
doSomething( obj->getVar() ) ;
Вместо
doSomething( obj->var ) ;
Не только getVar()
визуально шумно, это создает иллюзию gettingVar()
это как-то более сложный процесс, чем на самом деле. Как вы (как классный писатель) относитесь к святости var
Это особенно сбивает с толку пользователя вашего класса, если у него есть промежуточный установщик - тогда похоже, что вы открываете эти ворота, чтобы "защитить" то, на чем вы настаиваете, ценно (святость var
) но все же даже ты уступаешь var
защита не имеет большого значения, если кто-то может просто прийти и set
var
на любую ценность, которую они хотят, даже если вы не смотрите на то, что они делают.
Поэтому я программирую следующим образом (предполагая подход "гибкого" типа - то есть когда я пишу код, не зная точно, что он будет делать / не имеет времени или опыта для планирования сложного набора интерфейсов стилей водопада):
1) Начните со всех открытых членов для основных объектов с данными и поведением. Вот почему во всех моих "примерных" кодах на C++ вы заметите, что я использую struct
вместо class
везде.
2) Когда внутреннее поведение объекта для члена данных становится достаточно сложным (например, ему нравится сохранять внутреннее std::list
в некотором порядке) написаны функции типа доступа. Поскольку я программирую сам, я не всегда устанавливаю член private
сразу, но где-то вниз по эволюции класса член будет "повышен" до protected
или же private
,
3) Классы, которые полностью реализованы и имеют строгие правила относительно своих внутренних органов (то есть они точно знают, что они делают, и вам не нужно "трахать" (технический термин) с его внутренними элементами), class
обозначение, закрытые члены по умолчанию и только несколько избранных членов могут быть public
,
Я считаю, что этот подход позволяет мне не сидеть и не религиозно писать геттеры / сеттеры, когда многие члены данных перемещаются, перемещаются и т. Д. На ранних этапах развития класса.
If you don't require any validations and not even need to maintain state i.e. one property depends on another so we need to maintain the state when one is change. You can keep it simple by making field public and not using getter and setters.
I think OOPs complicates things as the program grows it becomes nightmare for developer to scale.
A simple example; we generate c++ headers from xml. The header contains simple field which does not require any validations. But still as in OOPS accessor are fashion we generates them as following.
const Filed& getfield() const
Field& getField()
void setfield(const Field& field){...}
which is very verbose and is not required. a simple
struct
{
Field field;
};
is enough and readable. Functional programming don't have the concept of data hiding they even don't require it as they do not mutate the data.
Есть веская причина рассмотреть использование аксессоров, если нет наследования свойств. Смотрите следующий пример:
public class TestPropertyOverride {
public static class A {
public int i = 0;
public void add() {
i++;
}
public int getI() {
return i;
}
}
public static class B extends A {
public int i = 2;
@Override
public void add() {
i = i + 2;
}
@Override
public int getI() {
return i;
}
}
public static void main(String[] args) {
A a = new B();
System.out.println(a.i);
a.add();
System.out.println(a.i);
System.out.println(a.getI());
}
}
Выход:
0
0
4
Есть разница между DataStructure и Object.
Datastructure должен раскрывать свое внутреннее, а не поведение.
Объект не должен открывать свои внутренности, но он должен раскрывать свое поведение, которое также известно как Закон Деметры.
В основном DTO считаются скорее структурой данных, а не объектом. Они должны раскрывать только свои данные, а не поведение. Наличие Setter / Getter в DataStructure раскрывает поведение, а не данные внутри него. Это еще больше увеличивает шанс нарушения Закона Деметры.
Дядя Боб в своей книге «Чистый код» объяснил Закон Деметры.
Существует хорошо известная эвристика под названием Закон Деметры, согласно которой модуль не должен знать о внутренностях объектов, которыми он манипулирует. Как мы видели в предыдущем разделе, объекты скрывают свои данные и предоставляют операции. Это означает, что объект не должен раскрывать свою внутреннюю структуру через методы доступа, потому что это означает раскрытие, а не скрытие своей внутренней структуры.
Точнее, Закон Деметры гласит, что метод f класса C должен вызывать только следующие методы:
- C
- Объект, созданный f
- Объект, переданный в качестве аргумента функции f
- Объект, хранящийся в переменной экземпляра C
Метод не должен вызывать методы для объектов, возвращаемых какой-либо из разрешенных функций. Другими словами, разговаривайте с друзьями, а не с незнакомцами.
Итак, в соответствии с этим, примером нарушения LoD является:
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Здесь функция должна вызывать метод своего непосредственного друга, который здесь ctxt. Она не должна вызывать метод своего непосредственного друга друга. но это правило не распространяется на структуру данных. Итак, здесь, если ctxt, option, scratchDir являются структурой данных, тогда зачем оборачивать их внутренние данные каким-либо поведением и нарушением LoD.
Вместо этого мы можем сделать что-то вроде этого.
final String outputDir = ctxt.options.scratchDir.absolutePath;
Это удовлетворяет наши потребности и даже не нарушает LoD.
В духе «Чистого кода» Роберта К. Мартина (дядя Боб)
Методы получения и установки используются для реализации двух фундаментальных аспектов объектно-ориентированного программирования:
- абстракция
- Инкапсуляция
Предположим, у нас есть класс Employee:
package com.highmark.productConfig.types;
public class Employee {
private String firstName;
private String middleName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName(){
return this.getFirstName() + this.getMiddleName() + this.getLastName();
}
}
Здесь детали реализации полного имени скрыты от пользователя и не доступны непосредственно пользователю, в отличие от публичного атрибута.
С точки зрения проектно-ориентированного проектирования обе альтернативы могут повредить обслуживанию кода, ослабляя инкапсуляцию классов. Для обсуждения вы можете заглянуть в эту прекрасную статью: http://typicalprogrammer.com/?p=23
Кроме того, это "будущее" вашего класса. В частности, переход от поля к свойству является разрывом ABI, поэтому, если позже вы решите, что вам нужно больше логики, чем просто "установить / получить поле", то вам нужно разбить ABI, что, конечно, создает проблемы для чего-либо еще уже скомпилировано против вашего класса.
Еще одно использование (в языках, которые поддерживают свойства) заключается в том, что сеттеры и геттеры могут подразумевать, что операция является нетривиальной. Как правило, вы хотите избегать делать что-либо, что вычислительно дорого в собственности.
Методы получения и установки являются методами доступа, что означает, что они, как правило, являются открытым интерфейсом для изменения закрытых членов класса. Вы используете методы getter и setter для определения свойства. Вы получаете доступ к методам получения и установки как к свойствам вне класса, даже если вы определяете их в классе как методы. Эти свойства вне класса могут иметь имя, отличное от имени свойства в классе.
Существуют некоторые преимущества использования методов получения и установки, такие как возможность создавать элементы со сложной функциональностью, к которой у вас есть доступ к аналогичным свойствам. Они также позволяют создавать свойства только для чтения и только для записи.
Несмотря на то, что методы getter и setter полезны, вы должны быть осторожны, чтобы не злоупотреблять ими, потому что, среди других вопросов, они могут усложнить сопровождение кода в определенных ситуациях. Кроме того, они предоставляют доступ к реализации вашего класса, как публичные члены. Практика ООП препятствует прямому доступу к свойствам в классе.
Когда вы пишете классы, вам всегда рекомендуется делать как можно больше ваших переменных экземпляра закрытыми и соответственно добавлять методы getter и setter. Это связано с тем, что несколько раз вы не можете позволить пользователям изменять определенные переменные в ваших классах. Например, если у вас есть закрытый статический метод, который отслеживает количество экземпляров, созданных для определенного класса, вы не хотите, чтобы пользователь изменял этот счетчик с помощью кода. Только оператор конструктора должен увеличивать эту переменную всякий раз, когда она вызывается. В этой ситуации вы можете создать личную переменную экземпляра и разрешить метод получения только для переменной счетчика, что означает, что пользователи могут получить текущее значение только с помощью метода получения, и они не смогут устанавливать новые значения. используя метод установки. Создание геттера без установщика - это простой способ сделать определенные переменные в вашем классе доступными только для чтения.
Одно относительно современное преимущество методов получения / установки состоит в том, что он облегчает просмотр кода в редакторах теговых (индексированных) кодов. Например, если вы хотите увидеть, кто устанавливает участника, вы можете открыть иерархию вызовов установщика.
С другой стороны, если член является общедоступным, инструменты не позволяют фильтровать доступ для чтения / записи к члену. Таким образом, вы должны пройти через все виды использования члена.