Hej, Habr! Jeg præsenterer for jer oversættelsen af artiklen forfatter Christopher Kuech.
Objektorienterede og funktionen programmeringsparadigmer kan synes at være i konflikt med hinanden, men begge understøttes ligeligt i PowerShell. Næsten alle programmeringssprog, både funktionelle og ikke-funktionelle, har midler til udvidet binding af navne og værdier; Klasser, ligesom structs og records, er blot én tilgang. Hvis vi begrænser brugen af klasser til binding af navne og værdier og undgår sådanne "tunge" objektorienterede programmeringskoncepter som arv, polymorfi eller ændring (mutabilitet), kan vi udnytte deres fordele uden at komplicere vores kode. Desuden, ved at tilføje uændrerlige (immutable) metoder til typeomdannelse, kan vi berige vores funktionelle kode med klasser.
Casting-magi
Casting er en af de mest kraftfulde funktioner i PowerShell. Når du caster en værdi, stoler du på den implicitte initialisering og valideringsmulighed, som miljøet tilføjer til din applikation. For eksempel vil en simpel cast af en streng til [xml] køre den gennem parserens kode og generere et fuldt xml-træ. Vi kan bruge klasser i vores kode med samme formål.
Casting af hashtabeller
Hvis du ikke har en konstruktør, kan du fortsætte uden en, ved at caste hashtabeller til typen af din klasse. Husk at benytte valideringsattributter for at få mest muligt ud af dette mønster. Samtidig kan vi bruge typede egenskaber af klassen til at køre endnu dybere initialisering og valideringslogik.
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
}Derudover hjælper casting med at få et rent output. Sammenlign outputtet af en array af hashtabeller Cluster, der sends til Format-Table, med det, der ville blive, hvis vi caster disse hashtabeller til en klasse først. Klasseegenskaber opregnes altid i den rækkefølge, de er defineret. Husk at tilføje nøgleordet hidden foran alle de egenskaber, der ikke skal være synlige i output.

Casting af værdier
Hvis du har en konstruktor med ét argument, vil typecasting af værdien til din klasse-type overføre værdien til denne konstruktor, hvor du kan initialisere instansen af din klasse.
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"Casting til en streng
Du kan også overskrive klassens metode [string] ToString(), for at definere logikken for objektets strengrepræsentation, eksempelvis ved at bruge strenginterpolation.
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 "Vi har lige oprettet en model for '$cluster'"Casting af serialiserede instanser
Casting muliggør sikker deserialisering. Eksemplerne nedenfor vil fejle, hvis dataene ikke opfylder vores specifikation i Cluster.
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Casting i din funktionelle kode
Funktionelle programmer definerer først datakonstruktioner og implementerer derefter programmet som en sekvens af transformationer over uforanderlige datakonstruktioner. På trods af det modstridende indtryk hjælper klasser faktisk med at skrive funktionel kode, takket være metoderne til typekonvertering.
Skriver jeg funktionel PowerShell?
Mange mennesker med baggrund i C# eller lignende skriver PowerShell, der ligner C#. Ved at gøre det forlader du koncepterne for funktionel programmering og vil sandsynligvis få fordel af at dykke dybere ind i objektorienteret programmering i PowerShell eller bedre studere funktionel programmering.
Hvis du i høj grad stoler på transformation af uforanderlige data ved at bruge rørledninger (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object osv. — så har du en mere funktionel stil, og brugen af PowerShell-klasser i funktionel stil vil hjælpe dig.
Funktionel brug af klasser
Klasser, selvom de bruger en alternativ syntaks, er blot en sammenkædning mellem to domæner. I pipelinen kan man mappe et array af værdier ved at bruge ForEach-Object.
I eksemplet nedenfor udføres Node-konstruktøren hver gang der er et cast til Datum, og dette giver os mulighed for ikke at skrive en betydelig mængde kode. Som et resultat fokuserer vores pipeline på den deklarative dataanmodning og aggregering, mens vores klasser tager sig af data parsing og validering.
# Пример комбинирования классов с конвейерами для 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 -UniquePakke klasse til genbrug
Intet er så godt, som det ser ud
Desværre kan klasser ikke eksporteres af moduler på samme måde som funktioner eller variabler; men der er nogle tricks. Antag, at dine klasser er defineret i filen ./my-classes.ps1
Du kan gøre dot-sourcing af filen med klasser: . ./my-classes.ps1. Dette vil udføre my-classes.ps1 i dit nuværende scopes og definere alle klasser fra filen.
Du kan oprette et Powershell-modul, der eksporterer alle dine brugerdefinerede API'er (cmdlets) og indstille variablen ScriptsToProcess = "./my-classes.ps1" i manifestet for dit modul, med det samme resultat: ./my-classes.ps1 vil blive udført i dit miljø.
Hvilken variant du end vælger, skal du huske, at Powershell-type-systemet ikke kan håndtere navngivne typer, der er indlæst fra forskellige steder.
Selv hvis du har indlæst to identiske klasser med de samme egenskaber fra forskellige steder, risikerer du at støde på problemer.
Vejen frem
Den bedste måde at undgå problemer med typeoprettelse på er aldrig at exponere dine klasser for brugerne. I stedet for at forvente, at brugeren importerer en bestemt type i klassen, skal du eksportere en funktion fra dit modul, som gør det unødvendigt at kontakte klassen direkte. Med hensyn til Cluster kan vi eksportere funktionen New-Cluster, der vil understøtte brugervenlige sættene af parametre og returnere 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-ClusterYderligere læsning
Kilde: habr.com
