A funkcionális Powershell osztályokkal nem oximoron, ezt garantálom

Szia Habr! Figyelmébe ajánlom a cikk fordítását "Funkcionális PowerShell osztályokkal.
Ígérem, ez nem oximoron"
írta: Christopher Kuech.

Az objektumorientált és a funkcionális programozási paradigmák ellentétesnek tűnhetnek egymással, de mindkettőt egyformán támogatja a Powershell. Szinte minden programozási nyelv, akár funkcionális, akár nem, rendelkezik kiterjesztett név-érték kötés lehetőségével; Az osztályok, mint a struktúrák és a rekordok, csak egy megközelítést jelentenek. Ha az osztályok használatát a nevek és értékek összekapcsolására korlátozzuk, és elkerüljük az olyan nehéz objektum-orientált programozási fogalmakat, mint az öröklődés, a polimorfizmus vagy a mutabilitás, akkor a kódunk bonyolítása nélkül kihasználhatjuk ezeket. Továbbá megváltoztathatatlan típuskonverziós módszerek hozzáadásával a funkcionális kódunkat osztályokkal gazdagíthatjuk.

A kasztok varázsa

A kasztok a Powershell egyik legerősebb funkciója. Amikor értéket ad meg, akkor a környezet által az alkalmazáshoz hozzáadott implicit inicializálási és érvényesítési képességekre támaszkodik. Például egy karakterlánc egyszerű öntése [xml]-ben keresztülfut az elemző kódon, és egy teljes xml-fát generál. Ugyanerre a célra használhatjuk a kódunkban található osztályokat.

Cast hashtables

Ha nincs konstruktor, akkor anélkül folytathatja, hogy egy hashtablet önt az osztálytípusra. Ne felejtse el használni az érvényesítési attribútumokat a minta teljes kihasználásához. Ugyanakkor az osztály beírt tulajdonságaival még mélyebb inicializálási és érvényesítési logikát futtathatunk.

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
}

Ezenkívül az öntés segít a tiszta kimenet elérésében. Hasonlítsa össze a Format-Table-nek átadott fürt hashtable tömb kimenetét azzal, amit akkor kap, ha ezeket a hashtable-okat először egy osztályba önti. Egy osztály tulajdonságai mindig abban a sorrendben jelennek meg, ahogy ott meghatározásra kerültek. Ne felejtse el hozzáadni a rejtett kulcsszót mindazon tulajdonságok elé, amelyeket nem szeretne látni az eredmények között.

A funkcionális Powershell osztályokkal nem oximoron, ezt garantálom

Jelentések leadása

Ha van egy konstruktorod egy argumentummal, akkor az osztálytípusod értékének megadása átadja az értéket a konstruktorodnak, ahol inicializálhatod az osztályod példányát.

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"

Sorra öntve

A [string] ToString() osztálymetódus felülbírálásával is meghatározhatja az objektum karakterlánc-reprezentációja mögötti logikát, például karakterlánc-interpolációt.

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 just created a model for '$cluster'"

Sorozatos példányok leadása

A Cast biztonságos deszerializálást tesz lehetővé. Az alábbi példák sikertelenek lesznek, ha az adatok nem felelnek meg a fürtben megadott specifikációnknak

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

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

Castok a funkcionális kódban

A funkcionális programok először adatstruktúrákat definiálnak, majd megváltoztathatatlan adatstruktúrákon át történő átalakítások sorozataként valósítják meg a programot. Az ellentmondásos benyomás ellenére az osztályok valóban segítenek a funkcionális kód írásában a típuskonverziós módszereknek köszönhetően.

Működik a Powershell, amit írok?

Sokan C# vagy hasonló háttérrel rendelkezők írnak Powershell-t, ami hasonló a C#-hoz. Ezzel eltávolodik a funkcionális programozási koncepciók használatától, és valószínűleg előnyös lenne, ha alaposan belemerülne a Powershell objektum-orientált programozásába, vagy többet tanulna a funkcionális programozásról.

Ha nagymértékben támaszkodik a megváltoztathatatlan adatok feldolgozási folyamatokkal (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object stb. osztályok funkcionális stílusban.

Az osztályok funkcionális használata

A kasztok, bár alternatív szintaxist használnak, csak egy leképezést jelentenek két tartomány között. A folyamatban a ForEach-Object segítségével leképezhet egy értékek tömbjét.

Az alábbi példában a Node konstruktor minden Datum leadásakor lefut, és ez lehetőséget ad arra, hogy ne írjunk megfelelő mennyiségű kódot. Ennek eredményeként a folyamatunk a deklaratív adatlekérdezésre és -összesítésre összpontosít, míg osztályaink az adatok elemzéséről és érvényesítéséről gondoskodnak.

# Пример комбинирования классов с конвейерами для 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

Csomagolási osztály újrafelhasználásra

Semmi sem olyan jó, mint amilyennek látszik

Sajnos az osztályokat nem lehet modulonként ugyanúgy exportálni, mint függvényeket vagy változókat; de van néhány trükk. Tegyük fel, hogy az osztályai a ./my-classes.ps1 fájlban vannak meghatározva

  • Dotsource-ozhat egy fájlt osztályokkal:. ./my-classes.ps1. Ez végrehajtja a my-classes.ps1 fájlt a jelenlegi hatókörben, és meghatározza az összes osztályt a fájlból.

  • Létrehozhat egy Powershell-modult, amely exportálja az összes egyéni API-t (parancsmagot), és beállíthatja a ScriptsToProcess = "./my-classes.ps1" változót a modul jegyzékében, ugyanazzal az eredménnyel: a ./my-classes.ps1 végrehajtása a környezeted.

Bármelyik opciót is választja, ne feledje, hogy a Powershell típusrendszere nem tudja feloldani a különböző helyekről betöltött azonos nevű típusokat.
Még ha különböző helyekről töltött be két azonos, azonos tulajdonságokkal rendelkező osztályt, fennáll a veszélye, hogy problémákba ütközik.

A továbbjutás

A típusfeloldási problémák elkerülésének legjobb módja, ha soha nem teszik közzé az osztályokat a felhasználók számára. Ahelyett, hogy azt várná a felhasználótól, hogy egy osztály által meghatározott típust importáljon, exportáljon egy függvényt a modulból, amely szükségtelenné teszi az osztály közvetlen elérését. A Cluster esetében exportálhatunk egy New-Cluster függvényt, amely támogatja a felhasználóbarát paraméterkészleteket, és visszaad egy fürtöt.

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

Mit kell még olvasni

Az osztályokról
Védekező PowerShell
Funkcionális programozás a PowerShellben

Forrás: will.com

Hozzászólás