Funkcionalni Powershell z razredi ni oksimoron, to zagotavljam

Hej Habr! Predstavljam vam prevod članka "Funkcionalna lupina PowerShell z razredi.
Obljubim, da ni oksimoron"
avtorja Christopher Kuech.

Paradigme objektno usmerjenega in funkcionalnega programiranja se morda zdijo v nasprotju, vendar sta obe enako podprti v Powershell. Skoraj vsi programski jeziki, funkcionalni ali ne, imajo možnosti za razširjeno vezavo imena in vrednosti; Razredi, tako kot strukture in zapisi, so samo en pristop. Če omejimo našo uporabo razredov na vezavo imen in vrednosti ter se izognemo težkim konceptom objektno usmerjenega programiranja, kot so dedovanje, polimorfizem ali spremenljivost, lahko izkoristimo njihove prednosti, ne da bi komplicirali kodo. Poleg tega lahko z dodajanjem metod pretvorbe nespremenljivih tipov našo funkcionalno kodo obogatimo z razredi.

Čarovnija kast

Kaste so ena najmočnejših funkcij v programu Powershell. Ko dodelite vrednost, se zanašate na implicitne zmožnosti inicializacije in preverjanja, ki jih okolje doda vaši aplikaciji. Na primer, preprosto prevajanje niza v [xml] ga bo pognalo skozi kodo razčlenjevalnika in ustvarilo celotno drevo xml. Za isti namen lahko uporabimo razrede v naši kodi.

Cast hashtables

Če nimate konstruktorja, lahko nadaljujete brez njega, tako da razpršilno tabelo preoblikujete v svoj tip razreda. Ne pozabite uporabiti atributov preverjanja, da boste v celoti izkoristili ta vzorec. Istočasno lahko uporabimo tipizirane lastnosti razreda za izvajanje še globlje inicializacijske in validacijske logike.

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
}

Poleg tega ulivanje pomaga dobiti čist rezultat. Primerjajte izhod matrike zgoščevalnih tabel Cluster, posredovane Format-Table, s tistim, kar dobite, če te zgoščevalne tabele najprej pretvorite v razred. Lastnosti razreda so vedno navedene v vrstnem redu, v katerem so tam definirane. Ne pozabite dodati skrite ključne besede pred vsemi tistimi lastnostmi, za katere ne želite, da so vidne v rezultatih.

Funkcionalni Powershell z razredi ni oksimoron, to zagotavljam

Zasedba pomenov

Če imate konstruktor z enim argumentom, bo pretvorba vrednosti v vaš tip razreda posredovala vrednost vašemu konstruktorju, kjer lahko inicializirate primerek svojega razreda

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

Metodo razreda [string] ToString() lahko tudi preglasite, da definirate logiko v ozadju predstavitve niza objekta, na primer z uporabo interpolacije niza.

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 serializirani primerki

Cast omogoča varno deserializacijo. Spodnji primeri ne bodo uspešni, če podatki ne ustrezajo našim specifikacijam v Clusterju

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

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

Kaste v vaši funkcijski kodi

Funkcionalni programi najprej definirajo podatkovne strukture, nato implementirajo program kot zaporedje transformacij nad nespremenljivimi podatkovnimi strukturami. Kljub protislovnemu vtisu vam razredi resnično pomagajo pri pisanju funkcionalne kode zahvaljujoč metodam pretvorbe tipov.

Ali Powershell, ki ga pišem, deluje?

Veliko ljudi, ki prihajajo iz C# ali podobnih okolij, pišejo Powershell, ki je podoben C#. S tem se oddaljujete od uporabe konceptov funkcionalnega programiranja in bi vam verjetno koristilo, če bi se močno poglobili v objektno usmerjeno programiranje v Powershellu ali izvedeli več o funkcionalnem programiranju.

Če se močno zanašate na preoblikovanje nespremenljivih podatkov z uporabo cevovodov (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object itd. - imate bolj funkcionalen slog in uporaba Powershell vam bo koristila razrede v funkcionalnem slogu.

Funkcionalna uporaba razredov

Kaste so, čeprav uporabljajo alternativno sintakso, le preslikava med dvema domenama. V cevovodu lahko preslikate niz vrednosti z uporabo ForEach-Object.

V spodnjem primeru se konstruktor vozlišča izvede vsakič, ko je Datum pretvorjen, kar nam daje možnost, da ne napišemo dovolj kode. Posledično se naš cevovod osredotoča na deklarativno poizvedovanje in združevanje podatkov, medtem ko naši razredi skrbijo za razčlenjevanje in preverjanje podatkov.

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

Razred embalaže za ponovno uporabo

Nič ni tako dobro, kot se zdi

Na žalost razredov ni mogoče izvoziti po modulih na enak način kot funkcij ali spremenljivk; vendar obstaja nekaj trikov. Recimo, da so vaši razredi definirani v datoteki ./my-classes.ps1

  • Lahko dotsource datoteko z razredi:. ./my-classes.ps1. To bo izvedlo my-classes.ps1 v vašem trenutnem obsegu in tam definiralo vse razrede iz datoteke.

  • Ustvarite lahko modul Powershell, ki izvozi vse vaše API-je po meri (cmdlets) in nastavite spremenljivko ScriptsToProcess = "./my-classes.ps1" v manifestu modula z enakim rezultatom: ./my-classes.ps1 se bo izvedel v tvoje okolje.

Ne glede na to, katero možnost izberete, ne pozabite, da tipski sistem Powershell ne more razrešiti tipov z istim imenom, naloženih z različnih mest.
Tudi če ste naložili dva enaka razreda z enakimi lastnostmi z različnih mest, tvegate težave.

Pot naprej

Najboljši način, da se izognete težavam z razreševanjem tipov, je, da svojih razredov nikoli ne izpostavite uporabnikom. Namesto da pričakujete, da bo uporabnik uvozil razredno definiran tip, izvozite funkcijo iz svojega modula, ki odpravlja potrebo po neposrednem dostopu do razreda. Za gručo lahko izvozimo funkcijo New-Cluster, ki bo podpirala uporabniku prijazne nize parametrov in vrnila gručo.

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

Kaj še brati

O razredih
Obrambni PowerShell
Funkcionalno programiranje v PowerShell

Vir: www.habr.com

Dodaj komentar