Funktionale PowerShell mit Klassen - kein Oxymoron, das garantiere ich

Hallo habr Ich präsentiere Ihnen die Übersetzung des Artikels "Functional PowerShell with Classes".
Ich verspreche, es ist kein Oxymoron " von Christopher Kuech.


Objektorientierte und funktionale Programmierparadigmen scheinen sich zu widersprechen, aber beide werden in Powershell gleichermaßen unterstützt. Nahezu alle funktionalen und nicht funktionalen Programmiersprachen verfügen über Mittel zur erweiterten Bindung von Namen und Werten. Klassen wie Strukturen und Datensätze sind nur ein Ansatz. Wenn wir die Verwendung von Klassen auf die Bindung von Namen und Werten beschränken und solche "schweren" objektorientierten Programmierkonzepte wie Vererbung, Polymorphismus oder Veränderbarkeit vermeiden, können wir sie nutzen, ohne unseren Code zu komplizieren. Durch Hinzufügen unveränderlicher Methoden zur Typkonvertierung können wir unseren Funktionscode mit Klassen anreichern.


Kastenzauber


Castes ist eine der mächtigsten Funktionen in Powershell. Wenn Sie einen Wert umwandeln, verlassen Sie sich auf die Fähigkeit der Umgebung, Ihrer Anwendung eine implizite Initialisierung und Validierung hinzuzufügen. Zum Beispiel wird eine einfache Umwandlung eines Strings in [xml] durch den Parser-Code ausgeführt und ein vollständiger xml-Baum generiert. Wir können Klassen in unserem Code für den gleichen Zweck verwenden.


Benutzerdefinierte Hashtabellen


Wenn Sie keinen Konstruktor haben, können Sie fortfahren, ohne eine Hash-Tabelle für den Typ Ihrer Klasse zu erstellen. Denken Sie daran, Validierungsattribute zu verwenden, um dieses Muster voll auszunutzen. Gleichzeitig können wir die typisierten Eigenschaften der Klasse verwenden, um eine noch tiefere Initialisierungs- und Validierungslogik auszulösen.


class Cluster { [ValidatePattern("^[Az]+$")] [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 die Besetzung dabei, ein sauberes Fazit zu ziehen. Vergleichen Sie die Ausgabe des an Format-Table übergebenen Cluster-Hashtabellen-Arrays mit dem, was passiert, wenn Sie diese Hashtabellen zuerst in eine Klasse umwandeln. Klasseneigenschaften werden immer in der Reihenfolge aufgelistet, in der sie dort definiert sind. Vergessen Sie nicht, das ausgeblendete Schlüsselwort vor allen Eigenschaften einzufügen, die in der Ausgabe nicht sichtbar sein sollen.


Bild

Benutzerdefinierte Werte


Wenn Sie einen Konstruktor mit einem Argument haben und die Werte in Ihren Klassentyp umwandeln, wird der Wert an diesen Konstruktor übergeben, in dem Sie die Instanz Ihrer Klasse initialisieren können


 class Cluster { [ValidatePattern("^[Az]+$")] [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" 

Benutzerdefinierte Linie


Sie können auch die Klassenmethode [string] ToString () überschreiben, um die Logik der Zeichenfolgendarstellung des Objekts zu bestimmen, z. B. mithilfe der Zeichenfolgeninterpolation.


 class Cluster { [ValidatePattern("^[Az]+$")] [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 just created a model for '$cluster'" 

Benutzerdefinierte serialisierte Instanzen


Cast ermöglicht eine sichere Deserialisierung. Die folgenden Beispiele schlagen fehl, wenn die Daten nicht unserer Spezifikation in Cluster entsprechen


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


Wirft Ihren Funktionscode ein


Funktionsprogramme definieren zuerst Datenstrukturen und implementieren dann das Programm als Folge von Transformationen über unveränderliche Datenstrukturen. Trotz des widersprüchlichen Eindrucks helfen Klassen dank Typkonvertierungsmethoden beim Schreiben von funktionalem Code.


Funktioniert die PowerShell, die ich schreibe?


Viele Leute, die aus C # oder mit einer ähnlichen Vergangenheit kommen, schreiben Powershell, ähnlich wie C #. Auf diese Weise zögern Sie, die Konzepte der funktionalen Programmierung zu verwenden, und profitieren möglicherweise davon, dass Sie stark in die objektorientierte Programmierung in Powershell eintauchen oder die funktionale Programmierung besser verstehen.


Wenn Sie sich stark darauf verlassen, unveränderliche Daten mithilfe von Pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object usw. zu transformieren, haben Sie einen funktionaleren Stil und eine bessere Verwendung Powershell-Kurse im funktionalen Stil.


Funktionale Verwendung von Klassen


Obwohl Castes eine alternative Syntax verwenden, werden sie nur zwischen zwei Domänen zugeordnet. In der Pipeline können Sie mithilfe von ForEach-Object ein Array von Werten zuordnen.


Im folgenden Beispiel wird der Node-Konstruktor jedes Mal ausgeführt, wenn eine Umwandlung in Datum erfolgt. Auf diese Weise können wir keine ausreichende Menge an Code schreiben. Infolgedessen konzentriert sich unsere Pipeline auf deklarative Datenabfragen und -aggregationen, während sich unsere Klassen um das Parsen und Validieren von Daten kümmern.


 #       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 "([az]+)(INT|PPE|PROD)([az]+)" $_, $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 


Klasse Verpackung wiederverwenden


Nichts ist so gut wie es klingt


Leider können Klassen nicht wie Funktionen oder Variablen von Modulen exportiert werden. Aber es gibt einige Tricks. Angenommen, Ihre Klassen sind in der Datei ./my-classes.ps1 definiert


  • Sie können eine Datei mit Klassen dosieren:. ./my-classes.ps1. Dadurch wird my-classes.ps1 in Ihrem aktuellen Bereich ausgeführt und dort alle Klassen aus der Datei definiert.


  • Sie können ein Powershell-Modul erstellen, das alle Ihre Benutzer-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 .



Unabhängig von der gewählten Option kann das Powershell-Typensystem nicht die Typen desselben Namens auflösen, die an verschiedenen Stellen geladen wurden.
Selbst wenn Sie zwei identische Klassen mit denselben Eigenschaften von verschiedenen Orten herunterladen, laufen Sie Gefahr, auf Probleme zu stoßen.


Weiter so


Die beste Möglichkeit, Probleme mit der Typauflösung zu vermeiden, besteht darin, Ihre Klassen niemals Benutzern auszusetzen. Anstatt zu erwarten, dass der Benutzer einen in der Klasse definierten Typ importiert, exportieren Sie aus Ihrem Modul eine Funktion, die den direkten Zugriff auf die Klasse überflüssig macht. Für Cluster können wir die Funktion New-Cluster exportieren, die benutzerfreundliche Parametersätze unterstützt und Cluster zurückgibt.


 class Cluster { [ValidatePattern("^[Az]+$")] [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 

Was gibt es noch zu lesen


Über Klassen
Defensive PowerShell
Funktionsprogrammierung in PowerShell

Source: https://habr.com/ru/post/de484300/


All Articles