Funkční Powershell s třídami není oxymoron, za to ručím

Čau Habr! Předkládám vaší pozornosti překlad článku "Funkční PowerShell s třídami.
Slibuji, že to není oxymoron"
od Christophera Kuecha.

Objektově orientované a funkční programovací paradigmata se mohou zdát ve vzájemném rozporu, ale obě jsou v Powershell stejně podporovány. Téměř všechny programovací jazyky, funkční nebo ne, mají prostředky pro rozšířenou vazbu název-hodnota; Třídy, stejně jako struktury a záznamy, jsou jen jedním přístupem. Pokud omezíme naše použití tříd na vazbu názvů a hodnot a vyvarujeme se náročných objektově orientovaných programovacích konceptů, jako je dědičnost, polymorfismus nebo mutabilita, můžeme využít jejich výhod, aniž bychom komplikovali náš kód. Dále přidáním neměnných metod konverze typů můžeme obohatit náš funkční kód o třídy.

Kouzlo kast

Castes jsou jednou z nejvýkonnějších funkcí v Powershell. Když přetypujete hodnotu, spoléháte na implicitní možnosti inicializace a ověřování, které prostředí přidává do vaší aplikace. Například jednoduché přetypování řetězce do [xml] jej projde kódem analyzátoru a vygeneruje úplný xml strom. Pro stejný účel můžeme použít třídy v našem kódu.

Cast hashtables

Pokud konstruktor nemáte, můžete pokračovat bez něj přetypováním hashtable do vašeho typu třídy. Nezapomeňte použít ověřovací atributy, abyste mohli plně využít tento vzor. Zároveň můžeme použít typizované vlastnosti třídy ke spuštění ještě hlubší inicializační a validační logiky.

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
}

Odlévání navíc pomáhá získat čistý výstup. Porovnejte výstup pole hashtable Cluster předaný do Format-Table s tím, co získáte, když tyto hashtables nejprve přenesete do třídy. Vlastnosti třídy jsou vždy uvedeny v pořadí, v jakém jsou tam definovány. Nezapomeňte přidat klíčové slovo skryté před všechny vlastnosti, které nechcete, aby byly ve výsledcích viditelné.

Funkční Powershell s třídami není oxymoron, za to ručím

Obsazení významů

Pokud máte konstruktor s jedním argumentem, přetypování hodnoty do vašeho typu třídy předá hodnotu vašemu konstruktoru, kde můžete inicializovat instanci vaší třídy

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"

Cast to line

Můžete také přepsat metodu třídy [string] ToString() a definovat logiku za reprezentací řetězce objektu, například pomocí interpolace řetězců.

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'"

Cast serializované instance

Odlitek umožňuje bezpečnou deserializaci. Níže uvedené příklady selžou, pokud data nesplňují naši specifikaci v Clusteru

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

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

Casty ve vašem funkčním kódu

Funkční programy nejprve definují datové struktury, poté implementují program jako sekvenci transformací nad neměnnými datovými strukturami. Navzdory rozporuplnému dojmu vám třídy opravdu pomáhají psát funkční kód díky metodám převodu typů.

Je Powershell, který píšu, funkční?

Mnoho lidí pocházejících z C# nebo podobného prostředí píše Powershell, který je podobný C#. Tím se odkloníte od používání konceptů funkcionálního programování a pravděpodobně by vám prospělo ponořit se intenzivně do objektově orientovaného programování v Powershell nebo se dozvědět více o funkcionálním programování.

Pokud hodně spoléháte na transformaci neměnných dat pomocí kanálů (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object atd. – máte funkčnější styl a budete mít prospěch z používání Powershell třídy ve funkčním stylu.

Funkční využití tříd

Kasty, i když používají alternativní syntaxi, jsou pouze mapováním mezi dvěma doménami. V kanálu můžete mapovat pole hodnot pomocí ForEach-Object.

V níže uvedeném příkladu je konstruktor Node spuštěn pokaždé, když je přetypován Datum, a to nám dává příležitost nenapsat velké množství kódu. V důsledku toho se náš kanál zaměřuje na deklarativní dotazování a agregaci dat, zatímco naše třídy se starají o analýzu a ověřování dat.

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

Třída balení pro opětovné použití

Nic není tak dobré, jak se zdá

Bohužel třídy nelze exportovat pomocí modulů stejným způsobem jako funkce nebo proměnné; ale jsou tam nějaké triky. Řekněme, že vaše třídy jsou definovány v souboru ./my-classes.ps1

  • Můžete dotsource soubor s třídami:. ./moje-třídy.ps1. Tím se spustí my-classes.ps1 ve vašem aktuálním rozsahu a nadefinují se všechny třídy ze souboru tam.

  • Můžete vytvořit modul Powershell, který exportuje všechna vaše vlastní rozhraní API (cmdlets) a nastavit proměnnou ScriptsToProcess = "./my-classes.ps1" v manifestu modulu se stejným výsledkem: ./my-classes.ps1 se spustí v vaše prostředí.

Ať už zvolíte kteroukoli možnost, mějte na paměti, že systém typů Powershell nedokáže rozlišit typy se stejným názvem načtené z různých míst.
I když načtete dvě stejné třídy se stejnými vlastnostmi z různých míst, riskujete, že narazíte na problémy.

Cesta vpřed

Nejlepším způsobem, jak se vyhnout problémům s rozlišením typů, je nikdy nevystavovat své třídy uživatelům. Namísto očekávání, že uživatel importuje typ definovaný třídou, exportujte funkci z vašeho modulu, která eliminuje potřebu přímého přístupu ke třídě. Pro Cluster můžeme exportovat funkci New-Cluster, která bude podporovat uživatelsky přívětivé sady parametrů a vrátí Cluster.

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

Co jiného číst

O třídách
Defenzivní PowerShell
Funkční programování v PowerShellu

Zdroj: www.habr.com

Přidat komentář