Ciao, Habr! Vi presento la traduzione dell'articolo autore Christopher Kuech.
Le paradigmi della programmazione orientata agli oggetti e funzionale possono sembrare in contraddizione tra loro, ma entrambe sono supportate in modo equo in PowerShell. Quasi tutti i linguaggi di programmazione, siano essi funzionali o meno, dispongono di strumenti per il binding avanzato di nomi e valori; Le classi, simili a struct e record, sono solo un approccio. Se limitiamo l'uso delle classi al binding di nomi e valori ed evitiamo concetti di programmazione orientata agli oggetti "pesanti", come ereditarietà, polimorfismo o mutabilità, possiamo sfruttarne i vantaggi senza complicare il nostro codice. Inoltre, aggiungendo metodi di trasformazione di tipo immutabili, possiamo arricchire il nostro codice funzionale con classi.
La magia dei cast
I cast sono una delle funzionalità più potenti di PowerShell. Quando sottoponi un valore a un cast, ti stai affidando alla capacità di inizializzazione e validazione implicita fornita dall'ambiente nella tua applicazione. Ad esempio, un semplice cast di una stringa in [xml] la passerà attraverso il codice del parser e genererà un albero XML completo. Possiamo usare le classi nel nostro codice con lo stesso scopo.
Cast delle hash table
Se non hai un costruttore, puoi continuare senza di esso, utilizzando il cast delle hash table nel tipo della tua classe. Non dimenticare di sfruttare gli attributi di validazione per trarre pieno vantaggio da questo modello. Inoltre, possiamo utilizzare le proprietà tipizzate della classe per attivare una logica di inizializzazione e validazione ancora più profonda.
class Cluster {
[ValidatePattern("^[A-z]+$")]
[string] $Service
[ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
[string] $FlightingRing
[ValidateSet("EastUS", "WestUS", "NorthEurope")]
[string] $Region
[ValidateRange(0, 255)]
[int] $Index
}
[Cluster]@{
Service = "MyService"
FlightingRing = "PROD"
Region = "EastUS"
Index = 2
}Inoltre, il cast aiuta a ottenere un'uscita pulita. Confronta l'uscita di un array di hash table Cluster passato a Format-Table con quella che otterresti se prima castassi queste hash table in una classe. Le proprietà della classe vengono sempre elencate nell'ordine in cui sono state definite. Non dimenticare di aggiungere la parola chiave hidden davanti a tutte quelle proprietà che non devono essere visibili nell'output.

Cast dei valori
Se hai un costruttore con un argomento, il cast del valore al tuo tipo di classe passerà il valore a questo costruttore, in cui potrai inizializzare l'istanza della tua classe.
class Cluster {
[ValidatePattern("^[A-z]+$")]
[string] $Service
[ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
[string] $FlightingRing
[ValidateSet("EastUS", "WestUS", "NorthEurope")]
[string] $Region
[ValidateRange(0, 255)]
[int] $Index
Cluster([string] $id) {
$this.Service, $this.FlightingRing, $this.Region, $this.Index = $id -split "-"
}
}
[Cluster]"MyService-PROD-EastUS-2"Cast a stringa
Puoi anche sovrascrivere il metodo della classe [string] ToString() per definire la logica della rappresentazione in forma di stringa dell'oggetto, ad esempio utilizzando l'interpolazione delle stringhe.
class Cluster {
[ValidatePattern("^[A-z]+$")]
[string] $Service
[ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
[string] $FlightingRing
[ValidateSet("EastUS", "WestUS", "NorthEurope")]
[string] $Region
[ValidateRange(0, 255)]
[int] $Index
[string] ToString() {
return $this.Service, $this.FlightingRing, $this.Region, $this.Index -join "-"
}
}
$cluster = [Cluster]@{
Service = "MyService"
FlightingRing = "PROD"
Region = "EastUS"
Index = 2
}
Write-Host "Abbiamo appena creato un modello per '$cluster'"Cast di istanze serializzate
Il cast consente una deserializzazione sicura. Gli esempi di seguito genereranno un errore se i dati non rispettano la nostra specifica in Cluster.
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Cast nel tuo codice funzionale
I programmi funzionali prima definiscono le strutture dei dati, poi implementano il programma come una sequenza di trasformazioni su strutture di dati immutabili. Nonostante una certa impressione contraddittoria, le classi aiutano effettivamente a scrivere codice funzionale grazie ai metodi di conversione dei tipi.
Sto scrivendo un Powershell funzionale?
Molte persone provenienti da C# o con un background simile scrivono Powershell che assomiglia a C#. Facendo così, rinunciano all'uso dei concetti di programmazione funzionale e probabilmente trarranno beneficio immergendosi maggiormente nella programmazione orientata agli oggetti in Powershell o studiando meglio la programmazione funzionale.
Se fai molto affidamento sulla trasformazione di dati immutabili utilizzando pipeline (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, ecc. — hai uno stile più funzionale, e ti sarà utile usare le classi Powershell in uno stile funzionale.
Uso funzionale delle classi
Le classi, sebbene utilizzino una sintassi alternativa, sono solo un mappaggio tra due domini. Nel pipeline si può mappare un array di valori utilizzando ForEach-Object.
Nell'esempio seguente il costruttore Node viene eseguito ogni volta che si verifica un cast a Datum, e questo ci consente di non scrivere un numero eccessivo di righe di codice. Di conseguenza, il nostro pipeline si concentra sulla richiesta dichiarativa di dati e sull'aggregazione, mentre le nostre classi si occupano del parsing e della validazione dei dati.
# Пример комбинирования классов с конвейерами для separation of concerns в конвейерах
class Node {
[ValidateLength(3, 7)]
[string] $Name
[ValidateSet("INT", "PPE", "PROD")]
[string] $FlightingRing
[ValidateSet("EastUS", "WestUS", "NorthEurope", "WestEurope")]
[string] $Region
Node([string] $Name) {
$Name -match "([a-z]+)(INT|PPE|PROD)([a-z]+)"
$_, $this.Service, $this.FlightingRing, $this.Region = $Matches
$this.Name = $Name
}
}
class Datum {
[string] $Name
[int] $Value
[Node] $Computer
[int] Severity() {
$this.Name -match "[0-9]+$"
return $Matches[0]
}
}
Write-Host "Urgent Security Audit Issues:"
Import-Csv "./audit-results.csv" `
| ForEach-Object {[Datum]$_} `
| Where-Object Value -gt 0 `
| Group-Object {$_.Severity()} `
| Where-Object Name -lt 2 `
| ForEach-Object Group `
| ForEach-Object Computer `
| Where-Object FlightingRing -eq "PROD" `
| Sort-Object Name, Region -UniqueImballaggio della classe per il riutilizzo
Niente è così buono come sembra
Sfortunatamente, le classi non possono essere esportate dai moduli nello stesso modo delle funzioni o delle variabili; ma ci sono alcune astuzie. Supponiamo che le tue classi siano definite nel file ./my-classes.ps1
Puoi fare il dot-sourcing del file delle classi: . ./my-classes.ps1. Questo eseguirà my-classes.ps1 nel tuo attuale ambito e definirà tutte le classi del file.
Puoi creare un modulo Powershell che esporta tutte le tue API personalizzate (cmdlet) e impostare la variabile ScriptsToProcess = "./my-classes.ps1" nel manifesto del tuo modulo, con lo stesso risultato: ./my-classes.ps1 verrà eseguito nel tuo ambiente.
Qualunque opzione tu scelga, non dimenticare che il sistema di tipi Powershell non può risolvere i tipi omonimi caricati da luoghi diversi.
Anche se hai caricato da luoghi diversi due classi identiche con le stesse proprietà, rischi di incorrere in problemi.
La strada da percorrere
Il modo migliore per evitare problemi con la risoluzione dei tipi è non esporre mai le tue classi agli utenti. Invece di aspettarti che l'utente importi un tipo definito nella classe, esporta dalla tua funzione di modulo che elimina la necessità di accedere direttamente alla classe. In relazione a Cluster, possiamo esportare la funzione New-Cluster, che supporterà insiemi di parametri amichevoli per l'utente e restituirà Cluster.
class Cluster {
[ValidatePattern("^[A-z]+$")]
[string] $Service
[ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
[string] $FlightingRing
[ValidateSet("EastUS", "WestUS", "NorthEurope")]
[string] $Region
[ValidateRange(0, 255)]
[int] $Index
}
function New-Cluster {
[OutputType([Cluster])]
Param(
[Parameter(Mandatory, ParameterSetName = "Id", Position = 0)]
[ValidateNotNullOrEmpty()]
[string] $Id,
[Parameter(Mandatory, ParameterSetName = "Components")]
[string] $Service,
[Parameter(Mandatory, ParameterSetName = "Components")]
[string] $FlightingRing,
[Parameter(Mandatory, ParameterSetName = "Components")]
[string] $Region,
[Parameter(Mandatory, ParameterSetName = "Components")]
[int] $Index
)
if ($Id) {
$Service, $FlightingRing, $Region, $Index = $Id -split "-"
}
[Cluster]@{
Service = $Service
FlightingRing = $FlightingRing
Region = $Region
Index = $Index
}
}
Export-ModuleMember New-ClusterCosa leggere ancora
Fonte: habr.com
