Funktionales PowerShell mit Klassen ist kein Oxymoron, das garantiere ich Ihnen.

Hallo, Habr! Ich präsentiere Ihnen die Übersetzung des Artikels "Funktionales PowerShell mit Klassen.
Ich verspreche, es ist kein Oxymoron"
Autor Christopher Kuech.

Objektorientierte und funktionale Programmierparadigmen scheinen möglicherweise miteinander in Konflikt zu stehen, werden jedoch beide gleichmäßig in PowerShell unterstützt. Fast alle Programmiersprachen, sowohl funktionale als auch nicht-funktionale, bieten Möglichkeiten für eine erweiterte Bindung von Namen und Werten; Klassen, ähnlich wie Structs und Records, sind lediglich ein Ansatz. Wenn wir die Nutzung von Klassen auf die Bindung von Namen und Werten beschränken und "schwere" objektorientierte Konzepte wie Vererbung, Polymorphismus oder Veränderbarkeit (Mutability) vermeiden, können wir ihre Vorteile nutzen, ohne unseren Code zu verkomplizieren. Darüber hinaus können wir, indem wir unveränderliche (immutable) Typumwandlungs-Methoden hinzufügen, unseren funktionalen Code durch Klassen bereichern.

Die Magie der Casts

Casting ist eine der leistungsstärksten Funktionen in PowerShell. Wenn Sie einen Wert casten, verlassen Sie sich auf die von der Umgebung in Ihre Anwendung eingebrachten Möglichkeiten der impliziten Initialisierung und Validierung. Zum Beispiel wird ein einfacher String-Cast zu [xml] durch den Parser-Code geleitet und erzeugt einen vollständigen XML-Baum. In unserem Code können wir Klassen mit demselben Ziel verwenden.

Casting von Hashtable

Wenn Sie keinen Konstruktor haben, können Sie fortfahren, ohne einen zu verwenden, indem Sie eine Hashtable in den Typ Ihrer Klasse casten. Vergessen Sie nicht, Validierungsattribute zu verwenden, um das Potenzial dieses Musters voll auszuschöpfen. Gleichzeitig können wir typisierte Eigenschaften der Klasse verwenden, um noch tiefere Logiken für Initialisierung und Validierung 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
}

Zusätzlich ermöglicht das Casting eine klare Ausgabe. Vergleichen Sie die Ausgabe des Hash-Tabellen-Arrays Cluster, das an Format-Table übergeben wurde, mit dem, was entsteht, wenn Sie diese Hash-Tabellen zuvor in eine Klasse umwandeln. Die Eigenschaften der Klasse werden immer in der Reihenfolge aufgelistet, in der sie definiert sind. Vergessen Sie nicht, das Schlüsselwort hidden vor all jenen Eigenschaften hinzuzufügen, die in der Ausgabe nicht sichtbar sein sollen.

Funktionales PowerShell mit Klassen ist kein Oxymoron, das garantiere ich Ihnen.

Wert-Casting

Wenn Sie einen Konstruktor mit einem Argument haben, wird das Casting des Wertes in Ihren Klassentyp an diesen Konstruktor übergeben, in dem Sie die 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"

Casting in eine Zeichenfolge

Sie können auch die Methode [string] ToString() der Klasse überschreiben, um die Logik der Zeichenfolgenrepräsentation des Objekts zu definieren, zum Beispiel durch Verwendung von String-Interpolation.

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("OstUS", "WestUS", "NordEuropa")]
    [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        = "OstUS"
    Index         = 2
}

Write-Host "Wir haben gerade ein Modell für '$cluster' erstellt"

Casting serialisierter Instanzen

Das Casting ermöglicht eine sichere Deserialisierung. Die folgenden Beispiele führen zu einem Fehler, wenn die Daten nicht unseren Spezifikationen im Cluster entsprechen.

# Валидация сериализованных данных

[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"

Castings in Ihrer funktionalen Programmierung

Funktionale Programme definieren zunächst die Datentypen und implementieren dann die Programme als eine Abfolge von Transformationen an unveränderlichen Datentypen. Trotz des widersprüchlichen Eindrucks helfen Klassen tatsächlich, funktionalen Code zu schreiben, dank der Methoden zur Typkonvertierung.

Schreibe ich funktionales PowerShell?

Viele Menschen, die aus C# oder ähnlichen Hintergründen kommen, schreiben PowerShell, das C# ähnlich ist. Wenn Sie so verfahren, verzichten Sie darauf, Konzepte der funktionalen Programmierung zu nutzen und profitieren wahrscheinlich mehr, wenn Sie sich intensiv mit objektorientierter Programmierung in PowerShell beschäftigen oder besser funktionale Programmierung lernen.

Wenn Sie stark auf die Transformation von Immutable-Daten angewiesen sind und Pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object usw. verwenden – dann verfolgt Ihr Stil einen funktionalen Ansatz, und Sie profitieren von der Nutzung von PowerShell-Klassen im funktionalen Stil.

Funktionale Nutzung von Klassen

Castings, obwohl sie eine alternative Syntax verwenden, sind lediglich eine Zuordnung zwischen zwei Domänen. In einer Pipeline können Sie ein Array von Werten mithilfe von ForEach-Object abbilden.

Im folgenden Beispiel wird der Konstruktor Node jedes Mal ausgeführt, wenn ein Cast zu Datum stattfindet, was uns ermöglicht, eine beträchtliche Menge an Code zu vermeiden. Dadurch 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 -Unique

Wiederverwendbare Klassenverpackung

Nichts ist so gut, wie es scheint

Leider können Klassen nicht auf die gleiche Weise wie Funktionen oder Variablen von Modulen exportiert werden; aber es gibt ein paar Tricks. Angenommen, Ihre Klassen sind in der Datei ./my-classes.ps1 definiert.

  • Sie können die Datei mit den Klassen wie folgt einbinden: . ./my-classes.ps1. Dies wird my-classes.ps1 in Ihrem aktuellen Gültigkeitsbereich ausführen und alle Klassen aus der Datei definieren.

  • 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 dem gleichen Ergebnis: ./my-classes.ps1 wird in Ihrer Umgebung ausgeführt.

Egal, welche Option Sie wählen, denken Sie daran, dass das Powershell-Typsystem keine identischen Typen auflösen kann, die aus verschiedenen Quellen geladen werden.
Selbst wenn Sie zwei identische Klassen mit denselben Eigenschaften aus unterschiedlichen Quellen geladen haben, besteht die Gefahr, auf Probleme zu stoßen.

Der Weg nach vorne

Die beste Möglichkeit, Probleme mit Typauflösungen zu vermeiden, besteht darin, Ihre Klassen niemals direkt für Benutzer bereitzustellen. Anstatt zu erwarten, dass der Benutzer einen bestimmten Typ innerhalb der Klasse importiert, exportieren Sie aus Ihrem Modul eine Funktion, die es dem Benutzer ermöglicht, nicht direkt auf die Klasse zuzugreifen. Bezüglich des Clusters können wir die Funktion New-Cluster exportieren, die benutzerfreundliche Parametersets unterstützt und einen 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-Cluster

Weitere Lektüre

Über Klassen
Defensive PowerShell
Funktionale Programmierung in PowerShell

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster