Являются ли геттеры и сеттеры плохим дизайном?Замеченный противоречивый совет [дубликат]

StackOverflow https://stackoverflow.com/questions/565095

Вопрос

На этот вопрос уже есть ответ здесь:

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

После беглого просмотра моего кода выяснилось, что большая его часть состояла из геттеров и сеттеров (60%) по сравнению с остальным, что действительно необходимо для логики игры.

Пара поисковых запросов Google утверждали, что геттеры и сеттеры - это зло, в то время как другие утверждали, что они необходимы для хорошей практики OO и отличных программ.

Так что же мне следует делать?Каким он должен быть?Должен ли я менять свои методы получения и настройки для моих личных переменных, или я должен придерживаться их?

Это было полезно?

Решение

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

// Game
private int score;
public void setScore(int score) { this.score = score; }
public int getScore() { return score; }
// Usage
game.setScore(game.getScore() + ENEMY_DESTROYED_SCORE);

это должно быть

// Game
private int score;
public int getScore() { return score; }
public void addScore(int delta) { score += delta; }
// Usage
game.addScore(ENEMY_DESTROYED_SCORE);

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

Идея состоит в том, чтобы создать методы, которые непосредственно делают то, что вы хотите сделать.Примером может служить то, как установить статус врагов "жив".У вас может возникнуть соблазн использовать метод setAlive (boolean alive).Вместо этого вы должны иметь:

private boolean alive = true;
public boolean isAlive() { return alive; }
public void kill() { alive = false; }

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

private int hp; // Set in constructor.
public boolean isAlive() { return hp > 0; } // Same method signature.
public void kill() { hp = 0; } // Same method signature.
public void damage(int damage) { hp -= damage; }

Другие советы

  • Очень злой:общедоступные поля.
  • Несколько злой:Добытчики и установщики там, где они не требуются.
  • Хорошо:Получатели и установщики только там, где они действительно необходимы - сделайте так, чтобы тип отображал "большее" поведение, которое происходит с использование его состояние, а не просто рассматривать тип как хранилище состояния, которым будут манипулировать другие типы.

Хотя это действительно зависит от ситуации - иногда вы действительно делай просто нужен тупой объект данных.

У вас уже было много хороших ответов на этот вопрос, так что я просто дам свои два цента.Добытчики и сеттеры очень, очень злые.По сути, они позволяют вам притворяться, что скрываете внутренние компоненты вашего объекта, когда большую часть времени все, что вы делали, это добавляли избыточный код, который ничего не делает для сокрытия внутреннего состояния.Для простого POJO нет причин, по которым getName() и setName() нельзя заменить на obj.name = "Tom".

Если вызов метода просто заменяет присваивание, то все, что вы получили, отдав предпочтение вызову метода, - это раздувание кода.К сожалению, язык закрепил использование геттеров и установщиков в спецификации JavaBeans, поэтому Java-программисты вынуждены использовать их, даже когда в этом нет никакого смысла.

К счастью, Eclipse (и, вероятно, другие IDE также) позволяет вам автоматически генерировать их.И для забавного проекта я однажды создал для них генератор кода на XSLT.Но если есть что-то, от чего я бы избавился в Java, так это чрезмерная зависимость от геттеров и сеттеров.

Получатели и установщики обеспечивают реализацию концепции инкапсуляция в объектно-ориентированном программировании.

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

Есть несколько преимуществ наличия геттеров и сеттеров:

1.Разрешение будущих изменений без внесения изменений в код, использующий измененный класс.

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

Например, предположим, что есть setValue метод, который устанавливает value частная переменная в объекте:

public void setValue(int value)
{
    this.value = value;
}

Но затем появилось новое требование, которое требовало отслеживать количество раз value был изменен.С установщиком на месте изменение довольно тривиально:

public void setValue(int value)
{
    this.value = value;
    count++;
}

Если в value поле было общедоступным, нет простого способа вернуться позже и добавить счетчик, который отслеживает, сколько раз значение было изменено.Следовательно, наличие геттеров и сеттеров - это один из способов "защититькласс от изменений в будущем", которые могут произойти позже.

2.Применение средств, с помощью которых объектом можно манипулировать.

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

Например, an ImmutableArray объект содержит int вызываемый массив myArray.Если бы массив был общедоступным полем, он просто не был бы неизменяемым:

ImmutableArray a = new ImmutableArray();
int[] b = a.myArray;
b[0] = 10;      // Oops, the ImmutableArray a's contents have been changed.

Чтобы реализовать действительно неизменяемый массив, получатель для массива (getArray метод) должен быть написан так, чтобы он возвращал копию своего массива:

public int[] getArray()
{
    return myArray.clone();
}

И даже если произойдет следующее:

ImmutableArray a = new ImmutableArray();
int[] b = a.getArray();
b[0] = 10;      // No problem, only the copy of the array is affected.

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

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

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

Они абсолютно злые.

@coobird к сожалению, они абсолютно не "применяют концепцию инкапсуляции", все, что они делают, это заставляют вас думать, что вы инкапсулируете данные, когда на самом деле вы предоставляете данные через свойство с иллюзиями величия метода.Все, что получатель / установщик делает в общедоступном поле, работает лучше.

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

object.field = value;

вместо более когнитивно интенсивного

object.setField(value);

где клиент теперь должен проверить метод getter / setter, чтобы увидеть, имеет ли он какие-либо побочные эффекты.

Во-вторых, если вам действительно нужно сделать что-то еще в методе, зачем называть это методом get / set, когда у него больше обязанностей, чем просто получение или настройка?Либо следуйте SRP, либо вызовите метод, который на самом деле сообщает вам, что весь методу действительно нравятся примеры Зарконнена, которые он упомянул, например.

public void kill(){
    isAlive = false;
    removeFromWorld(this);
}

вместо того , чтобы

public void setAlive(boolean isAlive){
    this.isAlive = isAlive;
    if (isAlive)
        addToWorld(this);
    else
        removeFromWorld(this);
}

где метод setAlive (boolean) сообщает клиенту, что в качестве побочного эффекта он удалит объект из мира?Зачем клиенту иметь какие-либо знания о поле IsAlive?Плюс, что происходит, когда объект повторно добавляется в мир, должен ли он быть повторно инициализирован?почему клиента должно волновать что-либо из этого?

ИМХО, мораль заключается в том, чтобы назвать методы, чтобы точно сказать, что они делают, следовать SRP и избавиться от геттеров / сеттеров.Если возникают проблемы без средств получения / установки, скажите объектам, чтобы они сами выполняли свою грязную работу внутри своего собственного класса, вместо того чтобы пытаться что-то делать с ними в других классах.

на этом моя тирада заканчивается, извините за это ;)

Это скользкий путь.

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

Существует также случай с классом, который предоставляет некоторые "ручки управления".;пользовательский интерфейс вашего автомобильного радиоприемника, вероятно, можно понимать как отображение чего-то вроде getVolume, setVolume, getChannel, и setChannel, но его реальная функциональность заключается в приеме сигналов и излучении звука.Но эти ручки не раскрывают особых деталей реализации;из этих функций интерфейса вы не узнаете, является ли радио транзисторами, в основном программным обеспечением, или электронными лампами.

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

Итак..."зло"?Не совсем.Но каждый раз, когда вы склонны придавать значение и выставлять напоказ и то, и другое get...и set...основываясь на этом значении, спросите себя, почему и какова на самом деле ответственность этого объекта.Если единственный ответ, который вы можете дать себе, это "Сохранить эту ценность для меня", тогда может быть здесь происходит что-то помимо ОО.

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

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

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

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

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

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

Один пример, который я прочитал выше, был future-proofing ваш код.Например:

public void setValue(int value)
{
    this.value = value;
}

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

Итак:

public void setValue(int value)
{
    this.value = value;
    count++;
}

Это прекрасно.Я понимаю это.Однако в Ruby разве следующее не служило бы той же цели?

someobject.my_value = 100

Позже вам нужно будет отследить, сколько раз my_value был установлен.Что ж, тогда не могли бы вы просто переопределить сеттер ТОГДА и только ТОГДА?

def my_value=(value)
    @my_value = value
    @count++
end

Я полностью за красивый код, но я должен признать, что просматривать горы имеющихся у нас классов Java и видеть буквально тысячи и тысячи строк кода, которые являются НИЧЕМ иным, как базовыми средствами получения / установки, уродливо и раздражающе.

Когда я разрабатывал на C # полный рабочий день, мы постоянно использовали общедоступные свойства и делали пользовательские геттеры / сеттеры только при необходимости.Сработало как по волшебству, и это ничего не сломало.

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

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

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

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

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

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

(Это не обязательно делает Python лучше Java, просто отличается.)

Просто К твоему сведению:В дополнение ко всем отличным ответам в этой теме помните, что из всех причин, которые вы можете привести за или против геттеров / сеттеров, производительность не является одной (как некоторые могут подумать).JVM достаточно умен, чтобы встроить тривиальные геттеры / сеттеры (даже не-final единицы, до тех пор, пока они на самом деле не переопределены).

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

Если вам нужен внешний доступ к отдельным значениям полей, используйте геттеры и / или установщики.Если нет, то не делай этого.Никогда не используйте общедоступные поля.Все очень просто, вот так!(Хорошо, это никогда это просто, но это хорошее эмпирическое правило).

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

Я программирую на Java несколько месяцев назад, и я узнал, что мы должны использовать getters & setters только тогда, когда это необходимо для приложения

получайте удовольствие :)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top