替代全局变量或单例
Alternative to Global Variable or Singleton
我在各个地方都读到过,全局变量充其量只是一种代码味道,最好避免使用。目前,我正在努力将基于 PS 脚本的大函数重构为 classes,并考虑使用 Singleton。用例是一个大型数据结构,需要从许多不同的 classes 和模块中引用。
然后我发现 this,这似乎表明单身人士也是个坏主意。
那么,什么是正确的方法(在 PS 5.1 中)来创建需要被很多 classes 引用并被其中一些修改的单一数据结构?可能相关的事实是我不需要它是线程安全的。根据定义,队列将以非常线性的方式处理。
FWIW,我找到了引用的 link 寻找关于单例和继承的信息,因为我的单例只是许多具有非常相似行为的 classes 之一,我从包含下一个 class 的集合的单例,每个包含下一个 class 的集合,以创建一个分层队列。我想要一个基础 class 来处理所有常见的队列管理,然后将其扩展为每个 class 的不同功能。除了让第一个 extended class 成为单身人士之外,这很好用。这似乎是不可能的,对吗?
编辑:或者,是否可以使用通用列表 属性 方法中的嵌套 classes 来识别子项中的父项?这就是我处理这个基于函数的版本的方式。全局 [XML]
变量构成了数据结构,我可以单步执行该结构,使用 .SelectNode()
填充变量以传递给下一个函数,并使用 .Parent
从中获取信息更高,尤其是从数据结构的根部开始。
编辑:由于我现在似乎无法在此处粘贴代码,因此我在 GitHub 上有一些代码。 Singleton 出现的示例位于第 121 行,我需要验证是否还有尚未执行的同一任务的任何其他示例,因此我可以跳过除最后一个实例之外的所有示例。这是删除各种 Autodesk 软件的通用组件的概念证明,这些组件以非常特别的方式进行管理。所以我希望能够安装任何程序(包)的组合并按任何计划卸载,并确保最后一个具有共享组件卸载的包是卸载它的包。为了在最后一次卸载发生之前不破坏其他依赖程序。希望这是有道理的。 Autodesk 的安装过程充满了痛苦。如果你不必和他们打交道,那你就觉得自己很幸运。 :)
如评论中所述,您链接到的代码中没有任何内容需要单例。
如果您想保留 ProcessQueue
和相关 Task
实例之间的父子关系,可以从结构上解决。
只需要在 Task
构造函数中注入一个 ProcessQueue
实例:
class ProcessQueue
{
hidden [System.Collections.Generic.List[object]]$Queue = [System.Collections.Generic.List[object]]::New()
}
class Task
{
[ProcessQueue]$Parent
[string]$Id
Task([string]$id, [ProcessQueue]$parent)
{
$this.Parent = $parent
$this.Id = $id
}
}
实例化对象层次结构时:
$myQueue = [ProcessQueue]::new()
$myQueue.Add([Task]@{ Id = "id"; Parent = $myQueue})
... 或重构 ProcessQueue.Add()
以负责构建任务:
class ProcessQueue
{
[Task] Add([string]$Id){
$newTask = [Task]::new($Id,$this)
$Queue.Add($newTask)
return $newTask
}
}
此时您只需使用 ProcessQueue.Add()
作为 [Task]
构造函数的代理:
$newTask = $myQueue.Add($id)
$newTask.DisplayName = "Display name goes here"
下次您需要从单个 Task
实例中搜索相关任务时,您只需执行以下操作:
$relatedTasks = $task.Parent.Find($whatever)
为了补充 - 这可能是您问题的最佳解决方案 - 回答您原来的问题:
So, what IS the right way (in PS 5.1) to create a single data structure that needs to be referenced by a lot of classes, and modified by some of them [without concern for thread safety]?
要避免全局变量的主要原因是它们是会话 -global,意味着在之后执行你自己的代码也会看到这些变量,这可能会产生副作用。
您不能在 PowerShell 中实现真正的单例,因为 PowerShell classes 不支持 访问修饰符;值得注意的是,你不能使构造函数私有(非public),你只能使用hidden
关键字"hide"它,这只会使它不易被发现,同时仍然可访问。
您可以使用以下技术近似一个单例,它本身模拟一个static class(PowerShell 也不支持,因为 static
关键字仅在 class members 上受支持,而不是class整体)。
一个简单的例子:
# NOT thread-safe
class AlmostAStaticClass {
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static [string] $Message # static property
static [string] DoSomething() { return ([AlmostAStaticClass]::Message + '!') }
}
[AlmostAStaticClass]::<member>
(例如,[AlmostAStaticClass]::Message = 'hi'
)现在可以在定义 AlmostAStaticClass
的范围内使用 和所有后代范围 (但它 不 全局可用,除非定义范围恰好是全局范围。
如果您需要跨模块边界class访问,您可以将其作为参数传递(作为输入文字);请注意,您仍然需要 ::
来访问(总是静态的)成员;例如,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])
实现一个线程安全准单例 - 或许可以使用
使用 ForEach-Object -Parallel
(v7+) or Start-ThreadJob
(v6+,但可安装在 v5.1 上)- 需要 更多工作:
注:
然后需要 方法 来获取和设置概念上的属性,因为 PowerShell 不支持代码支持的 属性 getter 和 setter自 7.0 起(添加此能力是 this GitHub feature request 的主题)。
但是你仍然需要一个底层的属性,因为PowerShell不支持字段;同样,你能做的最好的事情就是 隐藏 这个 属性,但它在技术上仍然可以访问。
以下 示例使用 System.Collections.Concurrent
命名空间中的 System.Threading.Monitor
(which C#'s lock
statement is based on) to manage thread-safe access to a value; for managing concurrent adding and removing items from collections, use the thread-safe collection types。
# Thread-safe
class AlmostAStaticClass {
static hidden [string] $_message = '' # conceptually, a *field*
static hidden [object] $_syncObj = [object]::new() # sync object for [Threading.Monitor]
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static SetMessage([string] $text) {
Write-Verbose -vb $text
# Guard against concurrent access by multiple threads.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
[AlmostAStaticClass]::_message = $text
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
}
static [string] GetMessage() {
# Guard against concurrent access by multiple threads.
# NOTE: This only works with [string] values and instances of *value types*
# or returning an *element from a collection* that is
# only subject to concurrency in terms of *adding and removing*
# elements.
# For all other (reference) types - entire (non-concurrent)
# collections or individual objects whose properties are
# themselves subject to concurrent access, the *calling* code
# must perform the locking.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
$msg = [AlmostAStaticClass]::_message
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
return $msg
}
static [string] DoSomething() { return ([AlmostAStaticClass]::GetMessage() + '!') }
}
请注意,与跨越模块边界类似,使用线程也需要将 class 作为类型对象传递给其他线程,但是使用 $using:
范围说明符更方便;一个简单的(人为的)例子:
# !! BROKEN AS OF v7.0
$class = [AlmostAStaticClass]
1..10 | ForEach-Object -Parallel { ($using:class)::SetMessage($_) }
注意:由于目前 classes,这种跨线程使用实际上 从 v7.0 开始被破坏绑定到 定义 运行空间 - 参见 this GitHub issue。看会不会给出解决方案
如您所见,PowerShell classes 的局限性使得实现此类场景变得很麻烦;将 Add-Type
与临时编译的 C# 代码一起使用是值得考虑的替代方案。
此GitHub meta issue是与PowerShell相关的各种问题的汇编classes;虽然它们最终可能会得到解决,但 PowerShell 的 classes 不太可能达到与 C# 相同的功能;毕竟,OOP 不是 PowerShell 脚本语言的重点(使用 预先存在的对象除外)。
我在各个地方都读到过,全局变量充其量只是一种代码味道,最好避免使用。目前,我正在努力将基于 PS 脚本的大函数重构为 classes,并考虑使用 Singleton。用例是一个大型数据结构,需要从许多不同的 classes 和模块中引用。 然后我发现 this,这似乎表明单身人士也是个坏主意。
那么,什么是正确的方法(在 PS 5.1 中)来创建需要被很多 classes 引用并被其中一些修改的单一数据结构?可能相关的事实是我不需要它是线程安全的。根据定义,队列将以非常线性的方式处理。
FWIW,我找到了引用的 link 寻找关于单例和继承的信息,因为我的单例只是许多具有非常相似行为的 classes 之一,我从包含下一个 class 的集合的单例,每个包含下一个 class 的集合,以创建一个分层队列。我想要一个基础 class 来处理所有常见的队列管理,然后将其扩展为每个 class 的不同功能。除了让第一个 extended class 成为单身人士之外,这很好用。这似乎是不可能的,对吗?
编辑:或者,是否可以使用通用列表 属性 方法中的嵌套 classes 来识别子项中的父项?这就是我处理这个基于函数的版本的方式。全局 [XML]
变量构成了数据结构,我可以单步执行该结构,使用 .SelectNode()
填充变量以传递给下一个函数,并使用 .Parent
从中获取信息更高,尤其是从数据结构的根部开始。
编辑:由于我现在似乎无法在此处粘贴代码,因此我在 GitHub 上有一些代码。 Singleton 出现的示例位于第 121 行,我需要验证是否还有尚未执行的同一任务的任何其他示例,因此我可以跳过除最后一个实例之外的所有示例。这是删除各种 Autodesk 软件的通用组件的概念证明,这些组件以非常特别的方式进行管理。所以我希望能够安装任何程序(包)的组合并按任何计划卸载,并确保最后一个具有共享组件卸载的包是卸载它的包。为了在最后一次卸载发生之前不破坏其他依赖程序。希望这是有道理的。 Autodesk 的安装过程充满了痛苦。如果你不必和他们打交道,那你就觉得自己很幸运。 :)
如评论中所述,您链接到的代码中没有任何内容需要单例。
如果您想保留 ProcessQueue
和相关 Task
实例之间的父子关系,可以从结构上解决。
只需要在 Task
构造函数中注入一个 ProcessQueue
实例:
class ProcessQueue
{
hidden [System.Collections.Generic.List[object]]$Queue = [System.Collections.Generic.List[object]]::New()
}
class Task
{
[ProcessQueue]$Parent
[string]$Id
Task([string]$id, [ProcessQueue]$parent)
{
$this.Parent = $parent
$this.Id = $id
}
}
实例化对象层次结构时:
$myQueue = [ProcessQueue]::new()
$myQueue.Add([Task]@{ Id = "id"; Parent = $myQueue})
... 或重构 ProcessQueue.Add()
以负责构建任务:
class ProcessQueue
{
[Task] Add([string]$Id){
$newTask = [Task]::new($Id,$this)
$Queue.Add($newTask)
return $newTask
}
}
此时您只需使用 ProcessQueue.Add()
作为 [Task]
构造函数的代理:
$newTask = $myQueue.Add($id)
$newTask.DisplayName = "Display name goes here"
下次您需要从单个 Task
实例中搜索相关任务时,您只需执行以下操作:
$relatedTasks = $task.Parent.Find($whatever)
为了补充
So, what IS the right way (in PS 5.1) to create a single data structure that needs to be referenced by a lot of classes, and modified by some of them [without concern for thread safety]?
要避免全局变量的主要原因是它们是会话 -global,意味着在之后执行你自己的代码也会看到这些变量,这可能会产生副作用。
您不能在 PowerShell 中实现真正的单例,因为 PowerShell classes 不支持 访问修饰符;值得注意的是,你不能使构造函数私有(非public),你只能使用
hidden
关键字"hide"它,这只会使它不易被发现,同时仍然可访问。您可以使用以下技术近似一个单例,它本身模拟一个static class(PowerShell 也不支持,因为
static
关键字仅在 class members 上受支持,而不是class整体)。
一个简单的例子:
# NOT thread-safe
class AlmostAStaticClass {
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static [string] $Message # static property
static [string] DoSomething() { return ([AlmostAStaticClass]::Message + '!') }
}
[AlmostAStaticClass]::<member>
(例如,[AlmostAStaticClass]::Message = 'hi'
)现在可以在定义 AlmostAStaticClass
的范围内使用 和所有后代范围 (但它 不 全局可用,除非定义范围恰好是全局范围。
如果您需要跨模块边界class访问,您可以将其作为参数传递(作为输入文字);请注意,您仍然需要 ::
来访问(总是静态的)成员;例如,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])
实现一个线程安全准单例 - 或许可以使用
使用 ForEach-Object -Parallel
(v7+) or Start-ThreadJob
(v6+,但可安装在 v5.1 上)- 需要 更多工作:
注:
-
然后需要
方法 来获取和设置概念上的属性,因为 PowerShell 不支持代码支持的 属性 getter 和 setter自 7.0 起(添加此能力是 this GitHub feature request 的主题)。
但是你仍然需要一个底层的属性,因为PowerShell不支持字段;同样,你能做的最好的事情就是 隐藏 这个 属性,但它在技术上仍然可以访问。
以下 示例使用 System.Collections.Concurrent
命名空间中的 System.Threading.Monitor
(which C#'s lock
statement is based on) to manage thread-safe access to a value; for managing concurrent adding and removing items from collections, use the thread-safe collection types。
# Thread-safe
class AlmostAStaticClass {
static hidden [string] $_message = '' # conceptually, a *field*
static hidden [object] $_syncObj = [object]::new() # sync object for [Threading.Monitor]
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static SetMessage([string] $text) {
Write-Verbose -vb $text
# Guard against concurrent access by multiple threads.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
[AlmostAStaticClass]::_message = $text
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
}
static [string] GetMessage() {
# Guard against concurrent access by multiple threads.
# NOTE: This only works with [string] values and instances of *value types*
# or returning an *element from a collection* that is
# only subject to concurrency in terms of *adding and removing*
# elements.
# For all other (reference) types - entire (non-concurrent)
# collections or individual objects whose properties are
# themselves subject to concurrent access, the *calling* code
# must perform the locking.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
$msg = [AlmostAStaticClass]::_message
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
return $msg
}
static [string] DoSomething() { return ([AlmostAStaticClass]::GetMessage() + '!') }
}
请注意,与跨越模块边界类似,使用线程也需要将 class 作为类型对象传递给其他线程,但是使用 $using:
范围说明符更方便;一个简单的(人为的)例子:
# !! BROKEN AS OF v7.0
$class = [AlmostAStaticClass]
1..10 | ForEach-Object -Parallel { ($using:class)::SetMessage($_) }
注意:由于目前 classes,这种跨线程使用实际上 从 v7.0 开始被破坏绑定到 定义 运行空间 - 参见 this GitHub issue。看会不会给出解决方案
如您所见,PowerShell classes 的局限性使得实现此类场景变得很麻烦;将 Add-Type
与临时编译的 C# 代码一起使用是值得考虑的替代方案。
此GitHub meta issue是与PowerShell相关的各种问题的汇编classes;虽然它们最终可能会得到解决,但 PowerShell 的 classes 不太可能达到与 C# 相同的功能;毕竟,OOP 不是 PowerShell 脚本语言的重点(使用 预先存在的对象除外)。