Старые хорошие глобальные переменные являются неправильным решением реальной проблемы : вы хотите, чтобы некоторая информация была доступна в большом контексте. В объектно-ориентированных языках, таких как Java, у нас часто нет чего-то, что мы называем глобальной переменной. Вместо этого мы лжем себе, используя публичные статические переменные или синглтоны и отрицая, что они — одно и то же. Нет, мы слишком объектно-ориентированные и слишком хорошие разработчики, чтобы использовать глобальные переменные.
Передача значения для всего одного вызова метода: намного лучше?
Итак, вы покаетесь и решите отказаться от глобальных переменных. Чем ты занимаешься? Вы передаете значение явно. Здорово. И затем вы передаете его другому методу и другому, и одно и то же значение снова и снова распространяется, пока оно не станет доступно во многих местах вашего приложения. Намного лучше, а? Вы загрязнили подписи слишком многими методами, и если вы когда-нибудь решите изменить тип передаваемого значения, это потребует большой работы.
У вас есть куча методов, просто передающих информацию. Многие из них абсолютно ничего не делают с этим значением, но все же должны получить его, чтобы иметь возможность передать его по цепочке.
Рассмотрим конкретный пример: я работал над инструментом для анализа кода Java, и для некоторых объектов требовался экземпляр TypeResolver : объект, которому дано имя (например, java.lang.String или my.shiny.Class ), предоставил бы им представления. типов. Хороший.
Давайте назовем классы напрямую используя TypeResolver typeResolverUsers . Теперь, кто бы ни использовал typeResolverUsers (назовем их usersOfTypeResolverUsers ), нужен TypeResolver передать его typeResolverUsers, который он вызывал . Таким образом, каждый usersOfTypeResolverUsers запрашивал TypeResolver во время создания и сохранял его, чтобы позже передать его классам, которые действительно нуждались в нем. Так что теперь, кто бы ни создавал экземпляр одного из usersOfTypeResolverUsers, должен был иметь TypeResolver ссылка. И так далее, пока слишком многие классы не знали о существовании класса TypeResolver .
Это не очень хорошо для рефакторинга, и это загрязняет ваши методы. Я решил этот конкретный случай с помощью реестра . Это не было безупречно и не так просто, поэтому я задавался вопросом: есть ли лучшее решение?
Другой пример: Position и WorldSize
Давайте подумаем о другом примере: мы хотим представить Позицию на карте мира (подумайте о моем любимом генераторе мира: WorldEngine ). Теперь позиция, конечно, определяется двумя значениями: х и у. Однако, учитывая, что это карта мира, мы хотим иметь возможность оборачивать ее влево-вправо. Таким образом, для выполнения определенных операций нам необходим доступ к размеру мира: если мы знаем, что мир имеет ширину 500 ячеек, когда мы переместимся из Позиции с x = 499 вправо, мы получим x = 0.
В этом сценарии мы могли бы:
- используйте глобальную переменную: это означает, что в нашей программе есть один возможный размер мира. Он хранится в каком-то месте, доступном из всего приложения. Это работает, если это предположение верно. Если мы вместо этого работаем над несколькими мирами одновременно, нам нужна альтернатива.
- мы могли бы сохранить размер мира в экземпляре Position . Это означает, что в каждом случае, когда мы хотим создать Позицию, нам нужна ссылка на размер мира. Поскольку приложение постоянно работает с экземплярами Position, это означает, что многие методы (возможно, большинство методов) получат экземпляр WorldSize и передадут его по цепочке.
Я не доволен ни одной из этих альтернатив. Я хотел бы иметь что-то, что почти похоже на глобальные переменные, но каким-то образом имеет ограниченную и контролируемую область. Или, если хотите, я бы хотел неявно передать некоторую контекстную информацию, не загрязняя сигнатуру всех методов.
Я думаю, что позиция действительно определяется только x и y. Он должен знать WorldSize для выполнения определенных задач, но это контекстная информация, а не то, что контролируется или принадлежит компании Position .
Контекстная информация: какого черта это?
По этой причине я работаю над концепцией контекстной информации. Сейчас я реализовал это так:
- мы можем объявить, что определенные значения могут зависеть от контекста: мы бы указали имя и тип для каждого из них
- мы можем присваивать значения определенной контекстно-зависимой переменной в соответствии с динамической областью действия : это означает, что мы сделаем это значение доступным не только в определенном блоке операторов, но также и для всех функций и методов, вызываемых в этом блоке, рекурсивно
- значение будет доступно только в данном потоке (да, ThreadLocal для спасения)
Примеры
Это текущий синтаксис, реализованный на моем языке программирования Турин :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
context WorldSize worldSize void myComplexOperation() { ... // here worldSize is empty ... context (worldSize = WorldSize( 600 , 400 ) { // here worldSize has a value mySubOperation() } ... // here worldSize is empty ... } void mySubOperation() { // here I can access worldSize from the context // because I was called in a context which had a value // for worldSize if (x > context.worldSize.get().width) { ... } } |
Альтернативные решения
Есть много способов сделать значения доступными для вашего приложения без использования явно глобальных переменных. Один способ использовать Singleton, другой — объявить статические переменные.
Другое решение — RegistryPattern :
Реестр — это глобальная ассоциация ключей и объектов, позволяющая получить доступ к объектам из любого места. Он включает два метода: один, который берет ключ и объект и добавляет объекты в реестр, и другой, который берет ключ и возвращает объект для ключа.
За и против
Что касается глобальной переменной, мы можем ограничить область действия значения контекстом, который не является статическим, но динамическим, в любом случае совершенно не связанные части кода не могут испортить те же глобальные переменные, которые мы используем.
Основная проблема этого решения заключается в том, что мы не можем статически определить, доступно ли значение в данном контексте. Однако мы возвращаем Optional, оставляя ответственность за пользователя, чтобы проверить, присутствует ли значение или нет.
Что касается шаблона ContextObject , мы предлагаем проверку типа: мы объявили тип значения контекста, чтобы мы могли убедиться, что ему назначены только совместимые значения.