Funkcionalni Powershell s razredi ni oksimoron, jamčim za to

Pozdrav, Habr! Predstavljam vam prevod članka "Funkcionalni PowerShell s klasami.
Obećavam da to nije oksimoron"
autora Christopher Kuech.

Objektno orijentisane i funkcionalne paradigme programiranja mogu izgledati kao da se ne slažu, ali obe su ravnomerno podržane u PowerShell-u. Gotovo svi programski jezici, funkcionalni i ne, imaju sredstva za prošireno povezivanje imena i vrednosti; Klasama, poput struct-ova i record-ova, to je samo jedan pristup. Ako ograničimo korišćenje klasa na povezivanje imena i vrednosti i izbegnemo takve "teške" objektno orijentisane programske koncepte kao što su nasleđivanje, polimorfizam ili promenljivost (mutability), možemo iskoristiti njihove prednosti bez komplikujući naš kod. Dalje, dodavanjem nepromenljivih (immutable) metoda pretvaranja tipova možemo obogatiti naš funkcionalni kod klasama.

Magija kastovanja

Kastovi su jedna od najmoćnijih funkcija u PowerShell-u. Kada izvršite kastovanje vrednosti, oslanjate se na mogućnost koju okruženje dodaje vašoj aplikaciji za implicitnu inicijalizaciju i validaciju. Na primer, jednostavno kastovanje stringa u [xml] proći će kroz kod parsera i generisati kompletnu xml strukturu. U našem kodu možemo koristiti klase u istu svrhu.

Kastovanje heš-tabela

Ako nemate konstruktor, možete nastaviti i dalje bez njega, koristeći kast heš-tabele u tip vašeg klasa. Ne zaboravite da iskoristite atribute validacije kako biste u potpunosti iskoristili ovaj obrazac. Takođe možemo koristiti tipizovana svojstva klase kako bismo pokrenuli još dublju logiku inicijalizacije i validacije.

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
}

Pored toga, kast pomaže da se dobije čist izlaz. Uporedite izlaz niza heš-tabela Cluster koje se prosleđuju u Format-Table s onim što biste dobili ako pre toga kastujete te heš-tabele u klasu. Svojstva klase se uvek nabrajuju u redosledu u kojem su definisana. Ne zaboravite da dodate ključnu reč hidden ispred svih onih svojstava koja ne bi trebala biti vidljiva u izlazu.

Funkcionalni Powershell s razredi ni oksimoron, jamčim za to

Kastovanje vrednosti

Če imate konstruktor z enim argumentom, pretvorba vrednosti v vaš razred bo prenesla vrednost v ta vaš konstruktor, v katerem lahko inicializirate instanco vašega 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"

Pretvorba v niz

Prav tako lahko prepišete metodo razreda [string] ToString(), da določite logiko nizovne predstavitve objekta, na primer z uporabo interpolacije nizov.

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 "Upravo smo ustvarili model za '$cluster'"

Pretvorba serializiranih instanc

Pretvorba omogoča varno deserializacijo. Spodnji primeri se bodo končali z napako, če podatki ne ustrezajo naši specifikaciji v Cluster.

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

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

Pretvorbe v vaši funkcijski kodi

Funkcijske programi najprej določajo podatkovne strukture, nato implementirajo program kot zaporedje transformacij nad nespremenljivimi podatkovnimi strukturami. Kljub nasprotujočim si vtisom razredi dejansko pomagajo pri pisanju funkcijske kode zaradi metod konverzije tipov.

Ali pišem funkcijski Powershell?

Mnogi ljudje, ki prihajajo iz C# ali s podobno preteklostjo, pišejo Powershell, ki spominja na C#. S tem zavračate uporabo konceptov funkcionalnega programiranja in verjetno boste imeli koristi, če se močno potopite v objektno usmerjeno programiranje v Powershell ali bolje spoznate funkcionalno programiranje.

Če se globoko zanašate na transformacijo neurejenih podatkov, z uporabo cevovodov (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object in podobno — boste imeli bolj funkcijski slog, in uporaba Powershell razredov v funkcijskem slogu vam bo pomagala.

Funkcionalna uporaba razredov

Razredi, čeprav uporabljajo alternativen sintaksis, so le preslikava med dvema domenama. V cevovodu lahko preslikate polje vrednosti s pomočjo ForEach-Object.

V spodnjem primeru se konstruktor Node izvede vsakič, ko pride do preslikave na Datum, kar nam omogoča, da ne pišemo precejšnje količine kode. Tako se naš cevovod osredotoči na deklarativno poizvedovanje po podatkih in agregacijo, medtem ko naši razredi prevzamejo odgovornost za obdelavo podatkov in validacijo.

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

Pakiranje razreda za ponovno uporabo

Ni ničesar tako dobrega, kot se zdi

Žal razredi ne morejo biti izvoženi iz modulov na enak način kot funkcije ali spremenljivke; vendar obstajajo nekatere trike. Recimo, da so vaši razredi definirani v datoteki ./my-classes.ps1

  • Lahko izvedete dot sourcing datoteke z razredi: . ./my-classes.ps1. To bo izvedlo my-classes.ps1 v vaši trenutni obsegu in definiralo vse razrede iz datoteke.

  • Lahko ustvarite Powershell modul, ki izvozi vse vaše uporabniške API-je (cmdlet-e) in nastavite spremenljivko ScriptsToProcess = "./my-classes.ps1" v manifestu vašega modula, s istim rezultatom: ./my-classes.ps1 se izvede v vašem okolju.

Ne glede na to, katero možnost izberete, ne pozabite, da sistem tipov Powershell ne more rešiti istoimenskih tipov, naloženih iz različnih krajev.
Še bolj, če ste naložili dve identični razredi z enakimi lastnostmi iz različnih mest, se lahko srečate s težavami.

Pot naprej

Najboljši način, da se izognete težavam z razreševanjem tipov, je, da nikoli ne izpostavljate svojih razredov uporabnikom. Namesto da bi pričakovali, da bo uporabnik uvozal določen tip iz razreda, izvozite iz vašega modula funkcijo, ki odpravlja potrebo po neposrednem dostopu do razreda. V zvezi s Cluster lahko izvozimo funkcijo New-Cluster, ki bo podpirala prijazne nizke parametre in vrnila 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

Što još pročitati

O razredih
Defenzivni PowerShell
Funkcionalno programiranje v PowerShellu

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster