Alternatives to Global Variables
旧的,好的全局变量是解决实际问题的错误解决方案:您希望某些信息在较大的范围内可用。 在像Java这样的面向对象的语言中,我们通常没有所谓的全局变量。 相反,我们使用公共静态变量或单例对自己说谎,并否认它们完全是同一回事。 不,我们太面向对象了,开发人员太擅长使用全局变量。
将值传递给单个方法调用:更好吗?
好的,因此您悔改并决定放弃全局变量。 你是做什么? 您显式传递一个值。 凉。 然后,您将其传递给另一种方法,并传递给另一种方法,并且相同的值不断地传播,直到它在应用程序中的许多地方可用为止。 好多了,是吗? 您已经污染了太多方法的签名,并且如果您决定更改传递的值的类型,则将需要进行大量工作。
您有一堆方法只是传递信息。 他们中的许多人对此价值绝对不做任何事情,但仍需要获得它才能沿链传递。
考虑一个具体的示例:我正在研究用于分析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模式,我们提供类型检查:我们声明了上下文值的类型,因此我们可以验证是否仅向其分配了兼容的值。