Funkcionālais Powershell ar klasēm nav oksimorons, es to garantēju

Čau Habr! Piedāvāju jūsu uzmanībai raksta tulkojumu "Funkcionāls PowerShell ar klasēm.
Es apsolu, ka tas nav oksimorons"
autors Kristofers Kuehs.

Objektorientētās un funkcionālās programmēšanas paradigmas var šķist pretrunā viena otrai, taču Powershell abas tiek vienlīdz atbalstītas. Gandrīz visās programmēšanas valodās, gan funkcionālajās, gan nefunkcionālajās, ir iespējas paplašināt nosaukumu un vērtību saistīšanu; Klases, piemēram, struktūras un ieraksti, ir tikai viena pieeja. Ja mēs aprobežojamies ar klašu izmantošanu līdz nosaukumu un vērtību saistīšanai un izvairāmies no smagiem objektorientētas programmēšanas jēdzieniem, piemēram, mantojuma, polimorfisma vai mainīguma, mēs varam izmantot to priekšrocības, neapgrūtinot mūsu kodu. Turklāt, pievienojot nemainīgas tipa konvertēšanas metodes, mēs varam bagātināt savu funkcionālo kodu ar klasēm.

Kastu maģija

Kastes ir viena no spēcīgākajām Powershell funkcijām. Nododot vērtību, jūs paļaujaties uz netiešajām inicializācijas un validācijas iespējām, ko vide pievieno jūsu lietojumprogrammai. Piemēram, vienkārši izraidot virkni [xml], tā tiks palaists caur parsētāja kodu un ģenerēs pilnu xml koku. Tam pašam mērķim mēs varam izmantot klases savā kodā.

Cast hashtables

Ja jums nav konstruktora, varat turpināt bez tā, atlaižot hashtable savam klases veidam. Neaizmirstiet izmantot validācijas atribūtus, lai pilnībā izmantotu šī modeļa priekšrocības. Tajā pašā laikā mēs varam izmantot klases drukātos rekvizītus, lai palaistu vēl dziļāku inicializācijas un validācijas loģiku.

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
}

Turklāt liešana palīdz iegūt tīru rezultātu. Salīdziniet klastera hashtable masīva izvadi, kas nodota Format-Table, ar to, ko iegūstat, ja pirmo reizi šīs jaucējtabulas ievietojat klasē. Klases rekvizīti vienmēr tiek uzskaitīti tādā secībā, kādā tie tur definēti. Neaizmirstiet pievienot slēpto atslēgvārdu pirms visiem tiem rekvizītiem, kurus nevēlaties redzēt rezultātos.

Funkcionālais Powershell ar klasēm nav oksimorons, es to garantēju

Nozīmju liešana

Ja jums ir konstruktors ar vienu argumentu, vērtības atrašana klases tipam nodos vērtību jūsu konstruktoram, kur varēsit inicializēt savas klases gadījumu.

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 uz līniju

Varat arī ignorēt [string] ToString() klases metodi, lai noteiktu loģiku aiz objekta virknes attēlojuma, piemēram, izmantojot virknes interpolāciju.

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

Apraidiet sērijveida gadījumus

Cast nodrošina drošu deserializāciju. Tālāk sniegtie piemēri neizdosies, ja dati neatbilst mūsu specifikācijai klasterī

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

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

Kastes jūsu funkcionālajā kodā

Funkcionālās programmas vispirms definē datu struktūras, pēc tam ievieš programmu kā transformāciju secību, izmantojot nemainīgas datu struktūras. Neskatoties uz pretrunīgo iespaidu, klases patiešām palīdz rakstīt funkcionālo kodu, pateicoties tipa konvertēšanas metodēm.

Vai Powershell, ko es rakstu, darbojas?

Daudzi cilvēki no C# vai līdzīgas pieredzes raksta Powershell, kas ir līdzīga C#. Šādi rīkojoties, jūs pārtraucat izmantot funkcionālās programmēšanas koncepcijas un, visticamāk, gūtu labumu no lielas ienirt objektorientētās programmēšanas programmā Powershell vai uzzināt vairāk par funkcionālo programmēšanu.

Ja jūs lielā mērā paļaujaties uz nemainīgu datu pārveidošanu, izmantojot konveijerus (|), Kur-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object utt., jums ir funkcionālāks stils un jūs iegūsit no Powershell izmantošanas. nodarbības funkcionālā stilā.

Nodarbību funkcionāla izmantošana

Kastas, lai gan tās izmanto alternatīvu sintaksi, ir tikai divu domēnu kartēšana. Gatavošanas procesā varat kartēt vērtību masīvu, izmantojot ForEach-Object.

Tālāk esošajā piemērā mezgla konstruktors tiek izpildīts katru reizi, kad tiek nodots Datum, un tas dod mums iespēju izvairīties no liela koda daudzuma rakstīšanas. Rezultātā mūsu konveijera uzmanība tiek pievērsta deklaratīvai datu vaicāšanai un apkopošanai, savukārt mūsu klases rūpējas par datu parsēšanu un validāciju.

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

Iepakojuma klase atkārtotai izmantošanai

Nekas nav tik labi, kā šķiet

Diemžēl klases nevar eksportēt pa moduļiem tāpat kā funkcijas vai mainīgos; bet ir daži triki. Pieņemsim, ka jūsu klases ir definētas failā ./my-classes.ps1

  • Varat dot avotu failam ar klasēm:. ./my-classes.ps1. Tas izpildīs my-classes.ps1 jūsu pašreizējā tvērumā un definēs visas klases no tur esošā faila.

  • Varat izveidot Powershell moduli, kas eksportē visas jūsu pielāgotās API (cmdlet) un moduļa manifestā iestatīt mainīgo ScriptsToProcess = "./my-classes.ps1", ar tādu pašu rezultātu: ./my-classes.ps1 tiks izpildīts jūsu vide.

Neatkarīgi no tā, kuru opciju izvēlaties, ņemiet vērā, ka Powershell tipa sistēma nevar atrisināt viena nosaukuma tipus, kas ielādēti no dažādām vietām.
Pat ja ielādējāt divas identiskas klases ar vienādām īpašībām no dažādām vietām, jūs riskējat ar problēmām.

Ceļš uz priekšu

Labākais veids, kā izvairīties no veida izšķirtspējas problēmām, ir nekad nepakļaut lietotājiem savas nodarbības. Tā vietā, lai gaidītu, ka lietotājs importēs klases definētu tipu, eksportējiet no moduļa funkciju, kas novērš nepieciešamību tieši piekļūt klasei. Klasterim mēs varam eksportēt funkciju New-Cluster, kas atbalstīs lietotājam draudzīgas parametru kopas un atgriezīs klasteru.

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

Ko vēl lasīt

Par klasēm
Aizsardzības PowerShell
Funkcionālā programmēšana programmā PowerShell

Avots: www.habr.com

Pievieno komentāru