Я не видел, чтобы кто-нибудь обсуждал это, поэтому я заберу больше пищи для размышлений. Короткий ответ/совет не используют переменные экземпляра вместо локальных переменных только потому, что вы считаете, что им легче возвращать значения. Вы очень сильно усложняете работу с вашим кодом, если не используете локальные переменные и переменные экземпляра соответствующим образом. Вы будете производить некоторые серьезные ошибки, которые действительно трудно отследить. Если вы хотите понять, что я имею в виду под серьезными ошибками, и что это может выглядеть как чтение.
Давайте попробуем использовать только переменные экземпляра, как вы предлагаете писать в функции. Я создам очень простой класс:
public class BadIdea {
public Enum Color { GREEN, RED, BLUE, PURPLE };
public Color[] map = new Colors[] {
Color.GREEN,
Color.GREEN,
Color.RED,
Color.BLUE,
Color.PURPLE,
Color.RED,
Color.PURPLE };
List<Integer> indexes = new ArrayList<Integer>();
public int counter = 0;
public int index = 0;
public void findColor(Color value) {
indexes.clear();
for(index = 0; index < map.length; index++) {
if(map[index] == value) {
indexes.add(index);
counter++;
}
}
}
public void findOppositeColors(Color value) {
indexes.clear();
for(index = 0; i < index < map.length; index++) {
if(map[index] != value) {
indexes.add(index);
counter++;
}
}
}
}
Это глупая программа, которую я знаю, но мы можем использовать его, чтобы проиллюстрировать концепцию, используя переменный экземпляр для таких вещей, как это чрезвычайно плохая идея. Самое большое, что вы обнаружите, это то, что эти методы используют все переменные экземпляра, которые у нас есть. И он изменяет индексы, счетчик и индекс каждый раз, когда они вызываются. Первая проблема, которую вы обнаружите, заключается в том, что вызов этих методов один за другим может изменять ответы предыдущих прогонов. Так, например, если вы написали следующий код:
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
idea.findColor(Color.GREEN); // whoops we just lost the results from finding all Color.RED
Поскольку FindColor использует переменный экземпляр для отслеживания возвращаемых значений мы можем только вернуть один результат за один раз. Давайте попробуем и сохраняем ссылку на эти результаты, прежде чем мы называем это снова!?
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findColor(Color.GREEN); // this causes red positions to be lost! (i.e. idea.indexes.clear()
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
В этом втором примере мы сохранили красные позиции на 3-й линии, но то же самое случилось Почему мы теряем их ?! Поскольку idea.indexes был очищен вместо выделенного, то может быть только один ответ, используемый за раз. Вы должны полностью завершить этот результат, прежде чем называть его снова. Когда вы снова вызовете метод, результаты будут очищены, и вы потеряете все. Чтобы исправить это, вам придется выделять новый результат каждый раз, так что красные и зеленые ответы являются отдельными. Итак, давайте клонировать наши ответы, чтобы создать новые копии вещей:
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes.clone();
int redCount = idea.counter;
idea.findColor(Color.GREEN);
List<Integer> greenPositions = idea.indexes.clone();
int greenCount = idea.counter;
Итак, у нас есть два отдельных результата.Результаты красного и зеленого теперь разделены. Но нам нужно было много узнать о том, как BadIdea работала внутри, прежде чем программа работала, не так ли? Нам нужно помнить, что каждый раз, когда мы называем это, мы должны клонировать возвращения, чтобы убедиться, что наши результаты не сбиты. Почему звонящий вынужден запомнить эти детали? Не было бы легче, если бы нам не пришлось это делать?
Также обратите внимание, что вызывающий абонент должен использовать локальные переменные для запоминания результатов, поэтому, когда вы не использовали локальные переменные в методах BadIdea, вызывающий должен использовать их для запоминания результатов. Так что же вы на самом деле сделали? Вы действительно просто переместили проблему на вызывающего, заставляя их делать больше. И работа, которую вы натолкнули на вызывающего, не является простым правилом, потому что в правиле есть несколько исключений.
Теперь попробуем сделать это с помощью двух разных методов. Обратите внимание, как я был «умным», и я повторно использовал те же переменные экземпляра для «сохранения памяти» и сохранил код. ;-)
BadIdea idea = new BadIdea();
idea.findColor(Color.RED);
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findOppositeColors(Color.RED); // this causes red positions to be lost again!!
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
То же самое произошло! Черт, но я был таким «умным» и экономия памяти, а код использует меньше ресурсов !!! Это реальная опасность использования переменных экземпляра, таких как методы вызова, теперь зависит от порядка. Если я изменю порядок вызова метода, результаты будут разными, даже если я не изменил базовое состояние BadIdea. Я не изменил содержание карты. Почему программа дает разные результаты, когда я вызываю методы в другом порядке?
idea.findColor(Color.RED)
idea.findOppositeColors(Color.RED)
Производит другой результат, чем если бы я сменил эти два метода:
idea.findOppositeColors(Color.RED)
idea.findColor(Color.RED)
Эти типы ошибок очень трудно отследить, особенно, когда эти линии не рядом друг с другом. Вы можете полностью разбить свою программу, просто добавив новый вызов в любом месте между этими двумя строками и получив совершенно разные результаты. Конечно, когда мы имеем дело с небольшим количеством строк, легко обнаружить ошибки. Но в более крупной программе вы можете потратить несколько дней, пытаясь воспроизвести их, даже если данные в программе не изменились.
И это касается только однопоточных проблем. Если BadIdea используется в многопоточной ситуации, ошибки могут стать действительно причудливыми. Что произойдет, если findColors() и findOppositeColors() вызывают одновременно? Crash, все ваши волосы выпадают, Смерть, пространство и время сворачиваются в сингулярность, и вселенная поглощается? Вероятно, по крайней мере два из них. Потоки, вероятно, сейчас над вашей головой, но, надеюсь, мы можем оттолкнуть вас от неприятностей, поэтому, когда вы попадаете в темы, эти плохие практики не вызывают у вас настоящей душевной боли.
Вы заметили, насколько осторожны вы должны были при вызове методов? Они переписывали друг друга, они поделились памятью, возможно, случайным образом, вам приходилось запоминать детали того, как она работала внутри, чтобы заставить ее работать снаружи, изменяя порядок, в котором они назывались, производят очень большие изменения в следующих строках вниз, и он мог работать только в ситуации с одним потоком. Делая такие вещи, вы получите действительно хрупкий код, который, кажется, разваливается всякий раз, когда вы его касаетесь. Эти практики, которые я показал, непосредственно влияли на хрупкость кода.
Хотя это может показаться инкапсуляцией, это полная противоположность, потому что технические сведения о том, как вы ее написали, должны быть известны вызывающему абоненту. Вызывающий должен написать свой код очень определенным образом, чтобы заставить их работать, и они не могут этого сделать, не зная о технических деталях вашего кода. Это часто называют Leaky Abstraction, потому что класс предположительно скрывает технические детали за абстракцией/интерфейсом, но технические подробности просачиваются, заставляя вызывающего абонента изменять свое поведение.Каждое решение имеет некоторую степень протечки, но используя любой из вышеперечисленных методов, таких как эти гарантии, независимо от того, какую проблему вы пытаетесь решить, будет ужасно непроходимым, если вы их примените. Итак, давайте посмотрим на GoodIdea.
переписывают Давайте с помощью локальных переменных:
public class GoodIdea {
...
public List<Integer> findColor(Color value) {
List<Integer> results = new ArrayList<Integer>();
for(int i = 0; i < map.length; i++) {
if(map[index] == value) {
results.add(i);
}
}
return results;
}
public List<Integer> findOppositeColors(Color value) {
List<Integer> results = new ArrayList<Integer>();
for(int i = 0; i < map.length; i++) {
if(map[index] != value) {
results.add(i);
}
}
return results;
}
}
Это устраняет все проблемы, мы обсуждали выше. Я знаю, что я не отслеживаю счетчик или возвращаю его, но если бы я сделал, я мог бы создать новый класс и вернуть его вместо List. Иногда я могу использовать следующий объект для быстрого получения нескольких результатов:
public class Pair<K,T> {
public K first;
public T second;
public Pair(K first, T second) {
this.first = first;
this.second = second;
}
}
Длинный ответ, но очень важная тема.
Можно ли определить другие классы? –