Hallo, Habr! Ich präsentiere Ihnen eine Übersetzung des Artikels. Autor Christopher Kuech.
Objektorientierte und funktionale Programmierparadigmen mögen im Widerspruch zueinander stehen, aber beide werden in Powershell gleichermaßen unterstützt. Fast alle Programmiersprachen, ob funktional oder nicht, verfügen über Mechanismen zur erweiterten Bindung von Namen und Werten; Klassen sind, ähnlich wie structs und records, nur ein Ansatz. Wenn wir die Verwendung von Klassen auf die Bindung von Namen und Werten beschränken und versuchen, "schwere" objektorientierte Programmierkonzepte wie Vererbung, Polymorphismus oder Mutabilität zu vermeiden, können wir ihre Vorteile nutzen, ohne unseren Code zu komplizieren. Darüber hinaus können wir, indem wir unveränderliche (immutable) Typkonvertierungsmethoden hinzufügen, unseren funktionalen Code durch Klassen bereichern.
Die Magie von Casts
Casts sind eine der mächtigsten Funktionen in PowerShell. Wenn Sie einen Wert casten, verlassen Sie sich auf die von der Umgebung in Ihre Anwendung eingefügte Fähigkeit zur impliziten Initialisierung und Validierung. Zum Beispiel wird ein einfacher Cast einer Zeichenfolge in [xml] durch den Parser-Code laufen und einen vollständigen XML-Baum generieren. Wir können in unserem Code Klassen mit dem gleichen Ziel verwenden.
Cast von Hashtable
Wenn Sie keinen Konstruktor haben, können Sie auch ohne ihn weiterarbeiten, indem Sie die Hashtable in den Typ Ihrer Klasse casten. Vergessen Sie nicht, die Validierungsattribute zu nutzen, um dieses Muster vollständig auszuschöpfen. Gleichzeitig können wir die typisierten Eigenschaften der Klasse verwenden, um noch tiefere Initialisierungs- und Validierungslogik zu aktivieren.
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
}Darüber hinaus hilft der Cast, eine saubere Ausgabe zu erzielen. Vergleichen Sie die Ausgabe des Array von Hashtables Cluster, die an Format-Table übergeben wurde, mit dem, was herauskommt, wenn diese Hashtables zuvor in eine Klasse gecastet werden. Die Eigenschaften der Klasse werden immer in der Reihenfolge aufgelistet, in der sie definiert sind. Vergessen Sie nicht, das Schlüsselwort hidden vor allen Eigenschaften hinzuzufügen, die in der Ausgabe nicht sichtbar sein sollten.

Cast von Werten
Wenn Sie einen Konstruktor mit einem Argument haben, wird der Typ des Wertes in Ihre Klassenart umgewandelt, und der Wert wird an diesen Konstruktor übergeben, in dem Sie eine Instanz Ihrer Klasse initialisieren können.
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"Typumwandlung in einen String
Sie können auch die Klassenmethode [string] ToString() überschreiben, um die Logik der String-Darstellung des Objekts zu definieren, beispielsweise durch Verwendung von String-Interpolation.
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 "Wir haben gerade ein Modell für '$cluster' erstellt"Typumwandlung von serialisierten Instanzen
Die Typumwandlung ermöglicht eine sichere Deserialisierung. Die folgenden Beispiele führen zu einem Fehler, wenn die Daten nicht unserem Spezifikationen im Cluster entsprechen.
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Typumwandlungen in Ihrem funktionalen Code
Funktionale Programme definieren zuerst Datenstrukturen und implementieren dann das Programm als eine Reihe von Transformationen auf unveränderlichen Datenstrukturen. Trotz des gegensätzlichen Eindrucks helfen Klassen tatsächlich dabei, funktionalen Code zu schreiben, dank der Methoden zur Typkonvertierung.
Schreibe ich funktionales PowerShell?
Viele Menschen, die aus C# oder einem ähnlichen Hintergrund kommen, schreiben PowerShell, die ähnlich wie C# aussieht. Wenn Sie dies tun, verzichten Sie darauf, Konzepte der funktionalen Programmierung zu nutzen, und profitieren wahrscheinlich davon, sich intensiv mit objektorientierter Programmierung in PowerShell oder besser mit funktionaler Programmierung auseinanderzusetzen.
Wenn Sie stark auf die Transformation von immutable Daten bauen, indem Sie Pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object usw. verwenden, haben Sie einen funktionaleren Stil, und die Verwendung von PowerShell-Klassen im funktionalen Stil wird Ihnen helfen.
Funktionale Nutzung von Klassen
Kapseln verwenden zwar alternative Syntax, sind aber lediglich ein Mapping zwischen zwei Domänen. Im Pipeline können Sie ein Array von Werten abbilden, indem Sie ForEach-Object verwenden.
Im folgenden Beispiel wird der Node-Konstruktor jedes Mal ausgeführt, wenn ein Cast zu Datum erfolgt, was uns die Möglichkeit gibt, eine beträchtliche Menge an Code zu vermeiden. In der Folge konzentriert sich unsere Pipeline auf die deklarative Abfrage von Daten und Aggregation, während unsere Klassen das Parsen von Daten und die Validierung übernehmen.
# Пример комбинирования классов с конвейерами для 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 -UniqueKlassenverpackung zur Wiederverwendung
Nichts ist so gut, wie es scheint
Leider können Klassen nicht von Modulen auf die gleiche Weise exportiert werden wie Funktionen oder Variablen; es gibt jedoch einige Tricks. Angenommen, Ihre Klassen sind in der Datei ./my-classes.ps1 definiert.
Sie können die Datei mit den Klassen so einfügen: . ./my-classes.ps1. Dies führt my-classes.ps1 in Ihrem aktuellen Geltungsbereich aus und definiert dort alle Klassen aus der Datei.
Sie können ein PowerShell-Modul erstellen, das alle Ihre benutzerdefinierten APIs (Cmdlets) exportiert, und die Variable ScriptsToProcess = "./my-classes.ps1" im Manifest Ihres Moduls festlegen, mit demselben Ergebnis: ./my-classes.ps1 wird in Ihrer Umgebung ausgeführt.
Egal für welche Variante Sie sich entscheiden, denken Sie daran, dass das Powershell-Typsystem keine gleichnamigen Typen verwalten kann, die aus verschiedenen Quellen geladen werden.
Selbst wenn Sie identische Klassen mit denselben Eigenschaften aus verschiedenen Quellen geladen haben, laufen Sie Gefahr, auf Probleme zu stoßen.
Der Weg nach vorne
Der beste Weg, um Probleme mit der Typauflösung zu vermeiden, besteht darin, Ihre Klassen niemals für Benutzer freizugeben. Anstatt zu erwarten, dass der Benutzer einen bestimmten im Klassen Typ importiert, exportieren Sie eine Funktion aus Ihrem Modul, die die Notwendigkeit beseitigt, direkt auf die Klasse zuzugreifen. In Bezug auf Cluster können wir die Funktion New-Cluster exportieren, die benutzerfreundliche Parametergruppen unterstützt und Cluster zurückgibt.
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-ClusterWas man sonst noch lesen sollte
Quelle: habr.com
