Funktionell PowerShell med klasser – jag garanterar att det inte Ă€r ett oxymoron

Hej, Habr! Jag presenterar översÀttningen av artikeln "Funktionell PowerShell med Klasser."
Jag lovar att det inte Àr en oxymoron"
författaren Christopher Kuech.

Objektorienterade och funktionella programmeringsparadigm kan verka oförenliga, men bÄda stöds i lika hög grad i PowerShell. NÀstan alla programmeringssprÄk, bÄde funktionella och icke-funktionella, har medel för avancerad bindning av namn och vÀrden; Klasser, likt structar och records, Àr bara en metod. Om vi begrÀnsar anvÀndningen av Klasser till bindning av namn och vÀrden och undviker "tunga" objektorienterade programmeringskoncept som arv, polymorfism eller mutabilitet, kan vi utnyttja deras fördelar utan att komplicera vÄr kod. Genom att dessutom lÀgga till oförÀnderliga metoder för typkonvertering kan vi berika vÄr funktionella kod med Klasser.

Kraften av casts

Casts Àr en av de mest kraftfulla funktionerna i PowerShell. NÀr du tillÀmpar en cast pÄ ett vÀrde, förlitar du dig pÄ de implicit initierings- och valideringsmöjligheter som lÀggs till av miljön till din applikation. Till exempel, en enkel cast av en strÀng till [xml] kommer att köra den genom parserkoden och generera ett komplett xml-trÀd. Vi kan anvÀnda Klasser i vÄr kod med samma syfte.

Cast av hash-tabeller

Om du inte har en konstruktor kan du fortsÀtta utan en, genom att anvÀnda cast av hash-tabeller till din klass typ. Glöm inte att anvÀnda valideringsattribut för att fullt ut dra nytta av detta mönster. Samtidigt kan vi anvÀnda typade klass-egenskaper för att utföra Ànnu mer djupgÄende logik för initiering och validering.

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
}

Dessutom hjÀlper casting till att fÄ ett rent utdata. JÀmför utdata av arrayen av hash-tabeller Cluster som skickas till Format-Table med vad som skulle hÀnda om dessa hash-tabeller först casts till klass. Klassens egenskaper listas alltid i den ordning de definieras. Glöm inte att lÀgga till nyckelordet hidden före alla egenskaper som inte ska synas i utdata.

Funktionell PowerShell med klasser – jag garanterar att det inte Ă€r ett oxymoron

Casting av vÀrden

Om du har en konstruktor med ett argument, kommer typomvandlingen av vÀrdet till din klass att överföra vÀrdet till din konstruktor, dÀr du kan initiera instansen av din klass.

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"

Typomvandling till strÀng

Du kan ocksÄ skriva över metoden [string] ToString() för att definiera logiken för strÀngrepresentationen av objektet, till exempel genom att anvÀnda strÀnginterpolering.

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 just skapat en modell för '$cluster'"

Typomvandling av serialiserade instanser

Typomvandling möjliggör sÀker deserialisering. Exemplen nedan kommer att avslutas med ett fel om data inte uppfyller vÄr specifikation i Cluster.

# Đ’Đ°Đ»ĐžĐŽĐ°Ń†ĐžŃ ŃĐ”Ń€ĐžĐ°Đ»ĐžĐ·ĐŸĐČĐ°ĐœĐœŃ‹Ń… ĐŽĐ°ĐœĐœŃ‹Ń…

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

Typomvandlingar i din funktionella kod

Funktionella program definierar först datatyper, och implementerar sedan programmet som en sekvens av transformationer över oförÀnderliga datatyper. Trots det motstridiga intrycket hjÀlper klasser faktiskt till att skriva funktionell kod, tack vare metoder för typkonvertering.

Skriver jag funktionell Powershell?

MÄnga som kommer frÄn C# eller liknande bakgrund skriver Powershell som liknar C#. Genom att göra det avstÄr du frÄn att anvÀnda koncept frÄn funktionell programmering och fÄr troligtvis mest nytta av att fördjupa dig i objektorienterad programmering i Powershell, eller bÀttre förstÄ funktionell programmering.

Om du starkt litar pĂ„ transformation av oförĂ€nderliga data, genom att anvĂ€nda pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object osv. — har du en mer funktionell stil, och klasser i Powershell kan hjĂ€lpa till i funktionell stil.

Funktionell anvÀndning av klasser

Klassar, Àven om de anvÀnder alternativ syntax, Àr bara en mappning mellan tvÄ domÀner. I röret kan du mappa en array av vÀrden med hjÀlp av ForEach-Object.

I exemplet nedan körs konstruktorn Node varje gÄng en kastning till Datum sker, och detta ger oss möjlighet att inte skriva en hel del kod. Som ett resultat fokuserar vÄrt rör pÄ att deklarativt frÄga data och aggregatera, medan vÄra klasser tar hand om dataparsering och 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 -Unique

Paketera en klass för ÄteranvÀndning

Ingenting Àr sÄ bra som det verkar

TyvÀrr kan klasser inte exporteras av moduler pÄ samma sÀtt som funktioner eller variabler; men det finns vissa knep. Anta att dina klasser Àr definierade i filen ./my-classes.ps1

  • Du kan göra en dot-sourcing av filen med klasser: . ./my-classes.ps1. Detta kommer att köra my-classes.ps1 i ditt aktuella sammanhang och definiera alla klasser frĂ„n filen dĂ€r.

  • Du kan skapa en Powershell-modul som exporterar alla dina anvĂ€ndardefinierade API:er (kommandon) och sĂ€tta variabeln ScriptsToProcess = "./my-classes.ps1" i manifestet för din modul, med samma resultat: ./my-classes.ps1 kommer att köras i din miljö.

Vilken variant du Àn vÀljer, glöm inte att PowerShells typ system inte kan hantera identiska typer som laddas frÄn olika platser.
Även om du har laddat tvĂ„ identiska klasser med samma egenskaper frĂ„n olika platser, riskerar du att stöta pĂ„ problem.

VÀgen framÄt

Det bÀsta sÀttet att undvika problem med typupplösning Àr att aldrig exponera dina klasser för anvÀndare. IstÀllet för att förvÀnta dig att anvÀndaren importerar en viss typ definierad i klassen, exportera en funktion frÄn din modul som frigör behovet av att direkt referera till klassen. I relation till Cluster kan vi exportera funktionen New-Cluster, som stöder anvÀndarvÀnliga uppsÀttningar av parametrar och returnerar Cluster.

klass Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index
}

funktion 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

Vad mer att lÀsa

Om klasser
Defensiv PowerShell
Funktionell programmering i PowerShell

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster