Hallo, Habr! Ik presenteer u de vertaling van het artikel auteur Christopher Kuech.
Objectgeoriënteerde en functionele programmeerparadigma's kunnen elkaar als tegenstrijdig lijken, maar beide worden gelijkwaardig ondersteund in PowerShell. Bijna alle programmeertalen, zowel functioneel als niet-functioneel, hebben middelen voor geavanceerde naam- en waardekoppeling; Klassen, net als structuren en records, zijn slechts één benadering. Als we het gebruik van Klassen beperken tot naam- en waardekoppeling en zware objectgeoriënteerde programmeerconcepten zoals overerving, polymorfisme of mutabiliteit vermijden, kunnen we hun voordelen benutten zonder onze code te compliceren. Verder, door onveranderlijke (immutable) typeconversiemethoden toe te voegen, kunnen we onze functionele code verrijken met Klassen.
De magie van casts
Casts zijn een van de krachtigste functies in PowerShell. Wanneer je een waarde cast, vertrouw je op de door de omgeving aan je applicatie toegevoegde mogelijkheid van impliciete initialisatie en validatie. Bijvoorbeeld, een eenvoudige cast van een string naar [xml] zal het door de parsercode leiden en een volledige xml-boom genereren. We kunnen Klassen in onze code gebruiken voor hetzelfde doel.
Cast van hashtabellen
Als je geen constructor hebt, kun je verder gaan zonder deze, door hashtabellen naar het type van je klasse te casten. Vergeet niet validatie-attributen te gebruiken om ten volle van dit patroon te profiteren. Tegelijkertijd kunnen we getypte eigenschappen van de klasse gebruiken om nog diepere initialisatie- en validatielogica te activeren.
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
}Bovendien helpt casting om een nette output te verkrijgen. Vergelijk de output van de hashtabellenarray van Cluster die door Format-Table is doorgegeven met wat er ontstaat als deze hashtabellen eerst naar een klasse worden gecast. De eigenschappen van de klasse worden altijd in de volgorde vermeld waarin ze zijn gedefinieerd. Vergeet niet het sleutelwoord hidden toe te voegen voor alle eigenschappen die niet in de output zichtbaar mogen zijn.

Cast van waarden
Als je een constructor met één argument hebt, zal de casting van de waarde naar je klassetype deze waarde aan die constructor doorgeven, waarin je de instantie van je klasse kunt initialiseren.
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 naar een string
Je kunt ook de klasse-methode [string] ToString() overschrijven om de logica van de stringrepresentatie van het object te definiëren, bijvoorbeeld door gebruik te maken van stringinterpolatie.
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 "We hebben zojuist een model voor '$cluster' gemaakt"Casting van geserialiseerde instanties
Casting maakt veilige deserialisatie mogelijk. Hieronder weergegeven voorbeelden eindigen met een fout als de gegevens niet aan onze specificatie in Cluster voldoen.
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Castings in je functionele code
Functionele programma's definiëren eerst gegevensstructuren en implementeren vervolgens het programma als een reeks transformaties op onveranderlijke gegevensstructuren. Ondanks de tegenstrijdige indruk helpen klassen echt bij het schrijven van functionele code, dankzij de typeconversiemethoden.
Schrijf ik functioneel Powershell?
Veel mensen die uit C# komen of een soortgelijke achtergrond hebben, schrijven Powershell die op C# lijkt. Door dit te doen, geef je de concepten van functioneel programmeren op en kun je waarschijnlijk profiteren door je meer te verdiepen in objectgeoriënteerd programmeren in Powershell of beter functioneel programmeren te leren.
Als je sterk vertrouwt op de transformatie van immutabele gegevens, gebruik makend van pijplijnen (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, enz. — dan heb je een meer functionele stijl en kan het gebruik van Powershell-klassen in een functionele stijl je helpen.
Functioneel gebruik van klassen
Het gebruik van casts, hoewel ze een alternatieve syntaxis gebruiken, is slechts een mapping tussen twee domeinen. In de pipeline is het mogelijk om een array van waarden te mappen met behulp van ForEach-Object.
In het onderstaande voorbeeld wordt de Node constructor elke keer uitgevoerd wanneer er een cast naar Datum plaatsvindt, en dit geeft ons de mogelijkheid om een aanzienlijke hoeveelheid code te vermijden. Als gevolg hiervan richt onze pipeline zich op het declaratieve verzoek om gegevens en aggregatie, terwijl onze klassen de parsing van gegevens en validatie op zich nemen.
# Пример комбинирования классов с конвейерами для 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 -UniqueVerpakking van de klasse voor hergebruik
Niets is zo goed als het lijkt
Helaas kunnen klassen niet op dezelfde manier als functies of variabelen door modules worden geëxporteerd; maar er zijn enkele trucs. Stel dat uw klassen zijn gedefinieerd in het bestand .\/my-classes.ps1
U kunt het bestand met klassen 'dot-source'en: . .\/my-classes.ps1. Dit voert my-classes.ps1 uit in uw huidige scope en definieert daar alle klassen uit het bestand.
U kunt een Powershell-module maken die al uw aangepaste API's (cmdlets) exporteert en de variabele ScriptsToProcess instellen = ".\/my-classes.ps1" in de manifest van uw module, met hetzelfde resultaat: .\/my-classes.ps1 wordt uitgevoerd in uw omgeving.
Welke optie u ook kiest, vergeet niet dat het Powershell-type systeem niet in staat is om identieke types, geladen vanuit verschillende locaties, correct af te handelen.
Zelfs als u identieke klassen met dezelfde eigenschappen vanuit verschillende locaties heeft geladen, loopt u het risico problemen te ondervinden.
De weg vooruit
De beste manier om problemen met type-resolutie te vermijden, is door uw klassen nooit aan gebruikers bloot te stellen. In plaats van te verwachten dat de gebruiker een specifiek type uit de klasse importeert, exporteert u een functie uit uw module die het mogelijk maakt om niet rechtstreeks naar de klasse te verwijzen. Voor Cluster kunnen we de functie New-Cluster exporteren, die gebruiksvriendelijke parameter sets ondersteunt en Cluster retourneert.
class Cluster {
[ValidatePattern("^[A-z]+$")]
[string] $Service
[ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
[string] $FlightingRing
[ValidateSet("OostVS", "WestVS", "NoordEuropa")]
[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-ClusterWat nog meer te lezen
Bron: habr.com
