Альтернатива глобальной переменной или одиночному элементу - PullRequest
2 голосов
/ 26 мая 2020

Я читал в разных местах, что глобальные переменные в лучшем случае являются запахом кода, и их лучше избегать. В настоящий момент я работаю над рефакторингом большого функционального скрипта PS для классов и подумываю использовать Singleton. Пример использования - это большая структура данных, на которую нужно будет ссылаться из множества различных классов и модулей. Затем я нашел это , что, кажется, наводит на мысль, что синглтоны тоже плохая идея.

Итак, каков правильный способ (в PS 5.1) создать единую структуру данных, которая должна быть ссылаться на множество классов и изменять некоторые из них? Вероятно, уместным является тот факт, что мне НЕ нужно, чтобы это было потокобезопасным. По определению очередь будет обрабатываться очень линейно.

FWIW, я перешел по указанной ссылке в поисках информации о синглтонах и наследовании, поскольку мой синглтон - это просто один из нескольких классов с очень похожим поведением, где я начинаю с синглтона, который содержит коллекции следующих class, каждый из которых содержит коллекции следующего класса, для создания иерархической очереди. Я хотел иметь базовый класс, который обрабатывал бы все общее управление очередью, а затем расширить его для различных функциональных возможностей каждого класса. Что отлично работает, за исключением того, что этот первый расширенный класс является синглтоном. Это кажется невозможным, правильно?

РЕДАКТИРОВАТЬ: В качестве альтернативы, возможно ли с помощью этих вложенных классов в общем подходе свойств списка c иметь возможность идентифицировать родителя изнутри дочернего элемента? Вот как я справился с этой версией на основе функций. Глобальная переменная [XML] сформировала структуру данных, и я мог пройти через эту структуру, используя .SelectNode(), чтобы заполнить переменную для перехода к следующей функции вниз, и используя .Parent, чтобы получить информацию с более высоких уровней, и особенно от root структуры данных.

РЕДАКТИРОВАТЬ: Поскольку я, похоже, не могу вставить сюда код прямо сейчас, у меня есть код на GitHub . Пример синглтона здесь находится в строке 121, где мне нужно проверить, есть ли другие примеры той же задачи, которые еще не были выполнены, поэтому я могу пропустить все, кроме последнего экземпляра. Это доказательство концепции удаления общих компонентов различного программного обеспечения Autodesk, которое управляется очень специальным образом c. Поэтому я хочу иметь возможность устанавливать любое сочетание программ (пакетов) и удалять по любому расписанию, а также убедиться, что последний пакет с удаленным общим компонентом удаляет его. Чтобы не нарушить работу других зависимых программ до последнего удаления. Надеюсь, это имеет смысл. Установка Autodesk - это скопище несчастья. Если вам не приходится с ними сталкиваться, считайте, что вам повезло. :)

Ответы [ 2 ]

2 голосов
/ 26 мая 2020

Чтобы дополнить полезный ответ Матиаса Р. Джессена - который вполне может быть лучшим решением вашей проблемы - ответом на ваш исходный вопрос:

Итак, что ЕСТЬ правильный способ (в PS 5.1) для создания единой структуры данных, на которую нужно ссылаться множеством классов и изменять некоторые из них [без заботы о безопасности потоков]?

  • Основная причина, по которой следует избегать глобальных переменных , заключается в том, что они являются сеансом -глобальным , что означает, что код, который выполняется после ваш собственный тоже видит эти переменные, которые могут иметь побочные эффекты.

  • Вы не можете реализовать настоящий синглтон в PowerShell , потому что классы PowerShell не поддерживают модификаторы доступа ; в частности, вы не можете сделать конструктор private (не publi c), вы можете только «скрыть» его с помощью ключевого слова hidden, что просто делает его менее обнаруживаемым , в то время как по-прежнему доступен .

  • Вы можете приблизить синглтон с помощью следующей техники , которая сама имитирует stati c class (который PowerShell также не поддерживает, поскольку ключевое слово static поддерживается только для членов 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 и все потомки scopes (но это не доступно глобально, если только определяющая область не является глобальной).

Если вам нужен доступ к классу через границы модуля , вы можете передать его как параметр (как литерал типа); обратите внимание, что вам по-прежнему требуется :: для доступа к членам (всегда stati c); например,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])


Реализация потокобезопасного квази-одноэлементного - возможно, для использования с ForEach-Object -Parallel (v7 +) или Start-ThreadJob (v6 +, но устанавливается на v5.1) - требуется дополнительная работа :

Примечание:

  • Затем требуются методы для получения и установки того, что является концептуально свойствами, поскольку PowerShell не поддерживает методы получения и установки свойств с кодовой поддержкой, начиная с версии 7.0 (добавление этой возможности является предметом этот запрос функции GitHub ).

  • Однако вам все еще нужно базовое свойство , поскольку PowerShell не поддерживает поля ; опять же, лучшее, что вы можете сделать, это скрыть это свойство, но оно технически все еще доступно.

В следующем примере используется System.Threading.Monitor (на котором основан оператор C# lock) для управления поточно-безопасным доступом к значению; для управления одновременным добавлением и удалением элементов из коллекций используйте потокобезопасные типы коллекций из пространства имен System.Collections.Concurrent.

# 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() + '!') }

}

Обратите внимание, что, как и при пересечении границ модуля, использование потоков также требует передачи класса в качестве объекта типа другим потокам, что, однако, более удобно сделать с помощью спецификатора $using: области видимости; простой (надуманный) пример:

# !! BROKEN AS OF v7.0
$class = [AlmostAStaticClass]
1..10 | ForEach-Object -Parallel { ($using:class)::SetMessage($_) }

Примечание : это перекрестное использование потоков фактически не работает с v7.0 из-за того, что классы в настоящее время связаны в , определяющее пространство выполнения - см. этот выпуск GitHub . Посмотрим, будет ли предоставлено решение.


Как видите, ограничения классов PowerShell делают реализацию такого сценария ios громоздкой; использование Add-Type с ad ho c -компилированным кодом C# заслуживает рассмотрения в качестве альтернативы.

Эта мета-проблема GitHub представляет собой компиляцию различных проблем, связанных с классами PowerShell ; хотя в конечном итоге они могут быть разрешены, маловероятно, что классы PowerShell когда-либо достигнут паритета функций с C#; в конце концов, OOP не является фокусом языка сценариев PowerShell (кроме , использующего уже существующие объекты).

1 голос
/ 26 мая 2020

Как упоминалось в комментариях, ничто в коде, на который вы ссылаетесь, не требует синглтона.

Если вы хотите сохранить отношения родитель-потомок между вашим ProcessQueue и связанным Task экземпляром, это может решаться структурно.

Просто требуется внедрение экземпляра ProcessQueue в конструктор 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
  }
}

При создании экземпляра иерархии объектов:

$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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...