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

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

Objektorientētas un funkcionālas programmēŔanas paradigmas var Ŕķist pretrunÄ«gas viena otrai, taču PowerShell vienlÄ«dzÄ«gi atbalsta abas. GandrÄ«z visas programmēŔanas valodas, funkcionālas vai nē, piedāvā plaÅ”u nosaukuma-vērtÄ«bas saistīŔanu; klases, piemēram, struktÅ«ras un ieraksti, ir tikai viena pieeja. Ierobežojot klaÅ”u izmantoÅ”anu ar nosaukuma-vērtÄ«bas saistīŔanu un izvairoties no sarežģītiem objektorientētas programmēŔanas jēdzieniem, piemēram, mantoÅ”anas, polimorfisma vai maināmÄ«bas, mēs varam izmantot to priekÅ”rocÄ«bas, nesarežģījot savu kodu. Turklāt, pievienojot nemainÄ«gas tipu konvertēŔanas metodes, mēs varam bagātināt savu funkcionālo kodu ar klasēm.

Kastu maģija

Transformācijas ir viena no jaudÄ«gākajām PowerShell funkcijām. Kad jÅ«s transformējat 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 transfērējot virkni uz [xml], tā tiks palaista caur parsētāja kodu un Ä£enerēts pilns XML koks. Mēs varam izmantot klases savā kodā tam paÅ”am mērÄ·im.

Apraides heŔtabulas

Ja jums nav konstruktora, varat turpināt bez tā, pārveidojot heÅ”tabulu par savas klases tipu. Neaizmirstiet izmantot validācijas atribÅ«tus, lai pilnÄ«bā izmantotu Å”o modeli. Varam arÄ« izmantot tipizētas klases Ä«paŔības, lai ieviestu 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 pārveidoÅ”ana palÄ«dz iegÅ«t tÄ«rāku izvadi. SalÄ«dziniet Cluster masÄ«va heÅ”tabulu izvadi, kas nodota Format-Table, ar to, ko iegÅ«stat, ja vispirms pārveidojat Ŕīs heÅ”tabulas klasē. Klases Ä«paŔības vienmēr ir uzskaitÄ«tas tādā secÄ«bā, kādā tās ir definētas. Neaizmirstiet pievienot atslēgvārdu "hidden" pirms jebkuras Ä«paŔības, kuru nevēlaties redzēt izvadē.

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

Vērtību kasta

Ja jums ir konstruktors ar vienu argumentu, vērtÄ«bas pieŔķirÅ”ana klases tipam nodos vērtÄ«bu jÅ«su konstruktoram, kurā varat inicializēt savas klases instanci.

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"

Izliet līdz līnijai

Varat arÄ« ignorēt [string] klases ToString() metodi, lai definētu objekta attēloÅ”anas loÄ£iku virknē, piemēram, izmantojot virkņu 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'"

Serializētu instanču lieÅ”ana

Apstrādes metode ļauj droÅ”i veikt deserializāciju. Tālāk sniegtie piemēri neizdosies, ja dati neatbilst mÅ«su klastera specifikācijai.

# Š’Š°Š»ŠøŠ“Š°Ń†ŠøŃ сериализованных Ганных

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

Jūsu funkcionālā koda pārveidoŔana

Funkcionālās programmas vispirms definē datu struktÅ«ras un pēc tam ievieÅ” programmu kā transformāciju secÄ«bu nemainÄ«gās datu struktÅ«rās. Neskatoties uz neloÄ£iski Ŕķietamo iespaidu, klases faktiski palÄ«dz rakstÄ«t funkcionālu kodu, pateicoties to tipu konvertēŔanas metodēm.

Vai es rakstu funkcionālu Powershell?

Daudzi cilvēki ar C# vai lÄ«dzÄ«gu pieredzi raksta PowerShell, kas ir lÄ«dzÄ«gs C#. Tā rÄ«kojoties, jÅ«s atsakāties no funkcionālās programmēŔanas koncepcijām un, visticamāk, gÅ«tu labumu no dziļākas iedziļināŔanās objektorientētā programmēŔanā PowerShell vai vairāku zināŔanu apgūŔanas par funkcionālo programmēŔanu.

Ja jÅ«s lielā mērā paļaujaties uz nemaināmu datu pārveidoÅ”anu, izmantojot vertikālās lÄ«nijas (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object utt., jums ir funkcionālāks stils, un jÅ«s iegÅ«siet no funkcionālā stila Powershell klaÅ”u izmantoÅ”anas.

Klases funkcionālā izmantoŔana

Lai gan pārsÅ«tÄ«jumi izmanto alternatÄ«vu sintaksi, tie ir vienkārÅ”i kartējums starp diviem domēniem. Cauruļvadā var kartēt vērtÄ«bu masÄ«vu, izmantojot ForEach-Object.

Zemāk redzamajā piemērā Node konstruktors tiek izpildÄ«ts katru reizi, kad tiek pārveidots Datum, tādējādi samazinot ievērojamu koda apjomu. Tā rezultātā mÅ«su cauruļvads koncentrējas uz deklaratÄ«vu datu vaicāŔanu un apkopoÅ”anu, savukārt mÅ«su klases apstrādā 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

Pārstrādājams iepakojums

Nekas nav tik labs, kā Ŕķiet

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

  • Varat dotsource tipa klases failu: ./my-classes.ps1. Tas izpildÄ«s my-classes.ps1 jÅ«su paÅ”reizējā darbÄ«bas jomā un definēs visas klases no Ŕī faila.

  • Varat izveidot Powershell moduli, kas eksportē visus jÅ«su pielāgotos API (cmdlet), un moduļa manifestā iestatÄ«t mainÄ«go ScriptsToProcess = "./my-classes.ps1", iegÅ«stot tādu paÅ”u rezultātu: jÅ«su vidē tiks izpildÄ«ts ./my-classes.ps1.

NeatkarÄ«gi no izvēlētās opcijas atcerieties, ka PowerShell tipu sistēma nevar atŔķirt tipus ar vienādu nosaukumu, kas ielādēti no dažādām vietām.
Pat ja ielādējat divas identiskas klases ar vienādām Ä«paŔībām no dažādām vietām, jÅ«s riskējat saskarties ar problēmām.

CeļŔ uz priekŔu

Labākais veids, kā izvairÄ«ties no tipu atpazīŔanas problēmām, ir nekad neizpaust savas klases lietotājiem. Tā vietā, lai gaidÄ«tu, ka lietotāji importēs klasē definētu tipu, eksportējiet funkciju no sava moduļa, kas novērÅ” nepiecieÅ”amÄ«bu tieÅ”i piekļūt klasei. Klastera gadÄ«jumā mēs varam eksportēt New-Cluster funkciju, kas atbalsta lietotājam draudzÄ«gas parametru kopas un atgriež klastera vērtÄ«bu.

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 nodarbībām
Aizsardzības PowerShell
Funkcionālā programmēŔana programmā PowerShell

Avots: www.habr.com

Pievieno komentāru