He leído en varios lugares que las variables globales son, en el mejor de los casos, un olor a código, y es mejor evitarlas. Por el momento, estoy trabajando en la refactorización de un gran script PS basado en funciones para las clases, y pensé en usar un Singleton. El caso de uso es una estructura de datos grande que deberá ser referenciada desde muchas clases y módulos diferentes. Luego encontré this, lo que parece sugerir que Singletons es malo idea también.

Entonces, ¿cuál ES la forma correcta (en PS 5.1) de crear una estructura de datos única que deba ser referenciada por muchas clases y modificada por algunas de ellas? Probablemente pertinente es el hecho de que NO necesito que esto sea seguro para subprocesos. Por definición, la cola se procesará de manera muy lineal.

FWIW, llegué al enlace referenciado en busca de información sobre singletons y herencia, ya que mi singleton es simplemente una de una serie de clases con un comportamiento muy similar, donde empiezo con el singleton que contiene colecciones de la siguiente clase, que contienen colecciones de la siguiente clase, para crear una cola jerárquica. Quería tener una clase base que manejara toda la gestión de colas común y luego extenderla para las diferentes funciones de cada clase. Lo que funciona muy bien además de que esa primera clase extendida sea un singleton. Eso parece ser imposible, ¿correcto?

EDITAR: Alternativamente, ¿es posible con estas clases anidadas en un enfoque de propiedad de lista genérica poder identificar al padre desde dentro de un niño? Así es como manejé esta es la versión basada en funciones. Una variable global [XML] formó la estructura de datos, y pude pasar por esa estructura, usando .SelectNode() para llenar una variable para pasar a la siguiente función hacia abajo, y usando .Parent para obtener información de más arriba, y especialmente desde la raíz de la estructura de datos.

EDITAR: dado que parece que no puedo pegar código aquí en este momento, tengo algo de código en GitHub. El ejemplo aquí de dónde entra el Singleton es en la línea 121, donde necesito verificar si hay otros ejemplos de la misma tarea que aún no se han completado, por lo que puedo omitir todos menos la última instancia. Esta es una prueba de concepto para eliminar componentes comunes de varios software de Autodesk, que se administra de manera muy ad hoc. Por lo tanto, quiero poder instalar cualquier combinación de programas (paquetes) y desinstalarlos en cualquier horario, y asegurarme de que el último paquete que tiene una desinstalación de componentes compartidos sea el que lo desinstala. Para no interrumpir otros programas dependientes antes de que ocurra la última desinstalación. Esperemos que tenga sentido. Las instalaciones de Autodesk son un montón de miseria. Si no tiene que lidiar con ellos, considérese afortunado. :)

2
Gordon 26 may. 2020 a las 11:54

2 respuestas

Para complementar la respuesta útil de Mathias R. Jessen, que bien podría ser la mejor solución a su problema, con una respuesta a su original pregunta:

Entonces, ¿cuál ES la forma correcta (en PS 5.1) de crear una estructura de datos única que deba ser referenciada por muchas clases y modificada por algunas de ellas [sin preocuparse por la seguridad del hilo]?

  • La razón principal por la que se deben evitar las variables globales es que son sesión -global, lo que significa que el código que ejecuta después de las suyas ve esas variables también, que puede tener efectos secundarios.

  • No puede implementar un verdadero singleton en PowerShell, porque las clases de PowerShell no admiten modificadores de acceso ; en particular, no puede hacer que un constructor sea privado (no público), solo puede "ocultarlo" con la palabra clave hidden, lo que simplemente lo hace menos reconocible sin dejar de ser accesible .

  • Puede aproximar un singleton con la siguiente técnica, que emula a sí misma una clase estática (que PowerShell tampoco admite, porque la palabra clave static solo es compatible en la clase miembros , no en la clase en su conjunto).

Un simple ejemplo:

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> (por ejemplo, [AlmostAStaticClass]::Message = 'hi') ahora se puede utilizar en el ámbito en el que AlmostAStaticClass se definió y todos los ámbitos descendientes (pero es no disponible globalmente, a menos que el alcance definitorio sea el global).

Si necesita acceso a la clase a través de los límites del módulo , puede pasarla como un parámetro (como un tipo literal); tenga en cuenta que aún necesita :: para acceder a los miembros (invariablemente estáticos); por ejemplo,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])

1
mklement0 26 may. 2020 a las 14:23

Como se menciona en los comentarios, nada en el código al que se vinculó requiere un singleton.

Si desea mantener una relación padre-hijo entre su ProcessQueue y la instancia relacionada Task, eso puede resolverse estructuralmente.

Simplemente requiera la inyección de una instancia ProcessQueue en el constructor Task:

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
  }
}

Al crear instancias de la jerarquía de objetos:

$myQueue = [ProcessQueue]::new()
$myQueue.Add([Task]@{ Id = "id"; Parent = $myQueue})

... o refactorizar ProcessQueue.Add() para encargarse de construir la tarea:

class ProcessQueue
{
  [Task] Add([string]$Id){
    $newTask = [Task]::new($Id,$this)
    $Queue.Add($newTask)
    return $newTask
  }
}

En ese momento, solo usa ProcessQueue.Add() como proxy para el constructor [Task]:

$newTask = $myQueue.Add($id)
$newTask.DisplayName = "Display name goes here"

La próxima vez que necesite buscar tareas relacionadas desde una sola instancia Task, simplemente haga lo siguiente:

$relatedTasks = $task.Parent.Find($whatever)
0
Mathias R. Jessen 26 may. 2020 a las 12:50