Powershell funksional me klasa nuk është një oksimoron, e garantoj

Përshëndetje, Habr! Unë paraqes në vëmendjen tuaj një përkthim të artikullit "PowerShell funksional me klasa.
Unë premtoj se nuk është një oksimoron"
nga Christopher Kuech.

Paradigmat e programimit të orientuara nga objekti dhe ato funksionale mund të duken në kundërshtim me njëra-tjetrën, por të dyja mbështeten në mënyrë të barabartë në Powershell. Pothuajse të gjitha gjuhët e programimit, funksionale ose jo, kanë lehtësi për lidhjen e zgjeruar të emrit-vlerës; Klasat, si strukturat dhe regjistrimet, janë vetëm një qasje. Nëse e kufizojmë përdorimin tonë të Klasave në lidhjen e emrave dhe vlerave dhe shmangim konceptet e rënda të programimit të orientuar drejt objekteve si trashëgimia, polimorfizmi ose ndryshueshmëria, ne mund të përfitojmë nga përfitimet e tyre pa e komplikuar kodin tonë. Më tej, duke shtuar metoda të konvertimit të tipit të pandryshueshëm, ne mund të pasurojmë kodin tonë funksional me klasa.

Magjia e kastave

Kastat janë një nga tiparet më të fuqishme në Powershell. Kur jepni një vlerë, ju jeni duke u mbështetur në aftësitë e nënkuptuara të inicializimit dhe vlefshmërisë që mjedisi i shton aplikacionit tuaj. Për shembull, thjesht hedhja e një vargu në [xml] do ta kalojë atë përmes kodit analizues dhe do të gjenerojë një pemë të plotë xml. Ne mund të përdorim Klasat në kodin tonë për të njëjtin qëllim.

Heshtables të hedhura

Nëse nuk keni një konstruktor, mund të vazhdoni pa një të tillë duke hedhur një hashtable në llojin tuaj të klasës. Mos harroni të përdorni atributet e vërtetimit për të përfituar plotësisht nga ky model. Në të njëjtën kohë, ne mund të përdorim vetitë e shtypura të klasës për të ekzekutuar logjikën e inicializimit dhe vërtetimit edhe më të thellë.

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
}

Përveç kësaj, derdhja ndihmon për të marrë një prodhim të pastër. Krahasoni daljen e grupit hashtable Cluster të kaluar në Format-Table me atë që merrni nëse së pari i hidhni këto hashtable në një klasë. Vetitë e një klase renditen gjithmonë sipas radhës në të cilën ato përcaktohen aty. Mos harroni të shtoni fjalën kyçe të fshehur përpara të gjitha atyre veçorive që nuk dëshironi të jenë të dukshme në rezultate.

Powershell funksional me klasa nuk është një oksimoron, e garantoj

Cast kuptimesh

Nëse keni një konstruktor me një argument, vendosja e një vlere në llojin e klasës suaj do t'ia kalojë vlerën konstruktorit tuaj, ku mund të inicializoni një shembull të klasës tuaj

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"

Transmetoni në rresht

Ju gjithashtu mund të anashkaloni metodën e klasës [string] ToString() për të përcaktuar logjikën pas paraqitjes së vargut të objektit, si p.sh. përdorimi i interpolimit të vargut.

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 raste të serializuara

Cast lejon deserializimin e sigurt. Shembujt e mëposhtëm do të dështojnë nëse të dhënat nuk plotësojnë specifikimet tona në Cluster

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

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

Vendos në kodin tuaj funksional

Programet funksionale fillimisht përcaktojnë strukturat e të dhënave, më pas implementojnë programin si një sekuencë transformimesh mbi strukturat e pandryshueshme të të dhënave. Pavarësisht përshtypjes kontradiktore, klasat ju ndihmojnë vërtet të shkruani kodin funksional falë metodave të konvertimit të tipit.

A është funksional Powershell që po shkruaj?

Shumë njerëz që vijnë nga C# ose prejardhje të ngjashme po shkruajnë Powershell, i cili është i ngjashëm me C#. Duke bërë këtë, ju po largoheni nga përdorimi i koncepteve të programimit funksional dhe ka të ngjarë të përfitoni nga zhytja shumë në programimin e orientuar nga objekti në Powershell ose të mësoni më shumë rreth programimit funksional.

Nëse mbështeteni shumë në transformimin e të dhënave të pandryshueshme duke përdorur tubacionet (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etj. - ju keni një stil më funksional dhe do të përfitoni nga përdorimi i Powershell klasa në një stil funksional.

Përdorimi funksional i klasave

Kastat, megjithëse përdorin një sintaksë alternative, janë vetëm një hartë midis dy fushave. Në tubacion, ju mund të hartoni një grup vlerash duke përdorur ForEach-Object.

Në shembullin e mëposhtëm, konstruktori Node ekzekutohet sa herë që hidhet një Data dhe kjo na jep mundësinë të shmangim shkrimin e një sasie të mjaftueshme kodi. Si rezultat, tubacioni ynë fokusohet në kërkimin dhe grumbullimin e të dhënave deklarative, ndërsa klasat tona kujdesen për analizimin dhe vlefshmërinë e të dhënave.

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

Klasa e paketimit për ripërdorim

Asgjë nuk është aq e mirë sa duket

Fatkeqësisht, klasat nuk mund të eksportohen nga modulet në të njëjtën mënyrë si funksionet ose variablat; por ka disa truke. Le të themi se klasat tuaja janë të përcaktuara në skedarin ./my-classes.ps1

  • Ju mund t'i jepni dotsource një skedari me klasa:. ./my-classes.ps1. Kjo do të ekzekutojë my-classes.ps1 në fushëveprimin tuaj aktual dhe do të përcaktojë të gjitha klasat nga skedari atje.

  • Mund të krijoni një modul Powershell që eksporton të gjitha API-të tuaja të personalizuara (cmdlet) dhe të vendosni variablin ScriptsToProcess = "./my-classes.ps1" në manifestin e modulit tuaj, me të njëjtin rezultat: ./my-classes.ps1 do të ekzekutohet në mjedisin tuaj.

Cilido opsion që zgjidhni, mbani në mend se sistemi i tipit Powershell nuk mund të zgjidhë llojet me të njëjtin emër të ngarkuar nga vende të ndryshme.
Edhe nëse keni ngarkuar dy klasa identike me të njëjtat veti nga vende të ndryshme, rrezikoni të përballeni me probleme.

Rruga përpara

Mënyra më e mirë për të shmangur problemet e zgjidhjes së tipit është të mos ekspozoni kurrë klasat tuaja ndaj përdoruesve. Në vend që të prisni që përdoruesi të importojë një lloj të përcaktuar nga klasa, eksportoni një funksion nga moduli juaj që eliminon nevojën për të hyrë drejtpërdrejt në klasë. Për Cluster, ne mund të eksportojmë një funksion New-Cluster që do të mbështesë grupe parametrash miqësore për përdoruesit dhe do të kthejë një 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

Çfarë tjetër për të lexuar

Rreth klasave
PowerShell mbrojtës
Programimi funksional në PowerShell

Burimi: www.habr.com

Shto një koment