全局变量的替代

Alternatives to Global Variables

旧的,好的全局变量是解决实际问题的错误解决方案:您希望某些信息在较大的范围内可用。 在像Java这样的面向对象的语言中,我们通常没有所谓的全局变量。 相反,我们使用公共静态变量或单例对自己说谎,并否认它们完全是同一回事。 不,我们太面向对象了,开发人员太擅长使用全局变量。

将值传递给单个方法调用:更好吗?

好的,因此您悔改并决定放弃全局变量。 你是做什么? 您显式传递一个值。 凉。 然后,您将其传递给另一种方法,并传递给另一种方法,并且相同的值不断地传播,直到它在应用程序中的许多地方可用为止。 好多了,是吗? 您已经污染了太多方法的签名,并且如果您决定更改传递的值的类型,则将需要进行大量工作。

fire-bucket-brigade

您有一堆方法只是传递信息。 他们中的许多人对此价值绝对不做任何事情,但仍需要获得它才能沿链传递。

考虑一个具体的示例:我正在研究用于分析Java代码的工具,并且某些对象需要 TypeResolver 的实例:给定名称的对象(例如java.lang.String或my.shiny.Class)将 为他们提供类型的表示。 好。

让我们直接使用 TypeResolver typeResolverUsers调用类。 现在,使用typeResolverUsers的用户(我们称他们为usersOfTypeResolverUsers)都需要一个 TypeResolver ,以将其传递给正在调用的typeResolverUsers。 因此,每位usersOfTypeResolverUsers会在构造时询问 TypeResolver 并存储它,以便以后将其传递给实际需要它的类。 因此,现在实例化usersOfTypeResolverUsers之一的人都需要具有 TypeResolver 引用。 依此类推,直到太多的类意识到 TypeResolver 类的存在。

重构不好,它会污染您的方法。 我使用注册表解决了这种特殊情况。 它不是完美无缺,也不是那么简单,所以我想知道:是否有更好的解决方案-

另一个例子:Position和WorldSize

让我们考虑另一个示例:我们想在世界地图上表示一个位置(想想我最爱的世界生成器:WorldEngine)。 现在,位置当然由两个值定义:x和y。 但是,鉴于它是世界地图,我们希望能够左右包裹。 因此,要执行某些操作,我们需要访问世界的大小:如果我们知道世界的宽度为500个像元,那么当我们从x = 499的aPosition移到右侧时,我们最终会得到x = 0。

在这种情况下,我们可以:

  • 使用全局变量:这意味着在我们的程序中可能存在一个世界大小。 它存储在整个应用程序可访问的某个位置。 如果此假设成立,则有效。 相反,如果我们同时在多个世界上工作,我们需要一个替代方案。

  • 我们可以将世界大小存储在Position实例中。 这意味着,在我们每次要创建位置的情况下,都需要引用世界尺寸。 给定应用程序始终与Positioninstances进行交易,这意味着许多方法(也许大多数方法)将获得WorldSizeinstance并将其传递到链下。

  • 我对这两种选择都不满意。 我想拥有几乎像全局变量一样的东西,但是范围有限且受控制。 或者,如果您愿意,我想隐式传递一些上下文信息,而不会污染所有方法的签名。

    我认为Position实际上仅由x和y定义。 它需要知道WorldSize才能执行某些任务,但这是上下文信息,而不是由Position控制或拥有的信息。

    上下文信息:这是什么问题?

    因此,我正在研究上下文信息的概念。 现在,我已经像这样实现了:

  • 我们可以声明某些值可能取决于上下文:我们将为每个值指定一个名称和一个类型

  • 我们可以根据动态范围为某个特定于上下文的变量分配值:这意味着我们不仅可以在某个语句块中使用该值,还可以递归该块中调用的所有函数和方法使用该值

  • 该值只能在给定线程中访问(是的,请使用ThreadLocal进行救援)

  • 例子

    这是在我的编程语言都灵中实现的当前语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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) {
         ...
       }
    }

    替代解决方案

    有很多方法可以使值对您的应用程序可用,而无需使用显式的全局变量。 它使用单例的一种方式,另一种是声明静态变量。

    另一个解决方案是RegistryPattern:

    注册表是从键到对象的全局关联,允许从任何地方访问对象。 它涉及两种方法:一种采用键和一个对象,然后将对象添加到注册表中,另一种采用键并返回该键的对象。


    优点缺点
    <铅>

    关于全局变量,我们可以将值的范围限制在非静态而是动态的上下文中,无论如何,完全不相关的代码段都不能与我们使用的相同全局变量混淆。

    该解决方案的主要问题是,我们无法静态确定在给定上下文中值是否可用。 但是,我们向用户返回了Optionalleaving责任,以验证该值是否存在。

    关于ContextObject模式,我们提供类型检查:我们声明了上下文值的类型,因此我们可以验证是否仅向其分配了兼容的值。