Powershell funcțional cu clase nu este un oximoron, vă garantez

Bună, Habr! Vă prezint atenției o traducere a articolului „PowerShell funcțional cu clase.
Promit că nu este un oximoron"
de Christopher Kuech.

Paradigmele de programare orientate pe obiecte și funcționale pot părea în dezacord unele cu altele, dar ambele sunt acceptate în mod egal în Powershell. Aproape toate limbajele de programare, funcționale sau nu, au facilități pentru legarea nume-valoare extinsă; Clasele, precum structurile și înregistrările, sunt doar o abordare. Dacă ne limităm utilizarea Claselor la legarea de nume și valori și evităm concepte grele de programare orientată pe obiecte, cum ar fi moștenirea, polimorfismul sau mutabilitatea, putem profita de avantajele acestora fără a ne complica codul. În plus, adăugând metode de conversie de tip imuabil, ne putem îmbogăți codul funcțional cu clase.

Magia castelor

Castele sunt una dintre cele mai puternice caracteristici din Powershell. Când aruncați o valoare, vă bazați pe capacitățile implicite de inițializare și validare pe care mediul le adaugă aplicației dvs. De exemplu, pur și simplu aruncarea unui șir în [xml] îl va rula prin codul parserului și va genera un arbore xml complet. Putem folosi Clasele în codul nostru în același scop.

Cast hashtables

Dacă nu aveți un constructor, puteți continua fără unul prin turnarea unui hashtable în tipul clasei dvs. Nu uitați să utilizați atributele de validare pentru a profita din plin de acest model. În același timp, putem folosi proprietățile tipizate ale clasei pentru a rula o logică de inițializare și validare și mai profundă.

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
}

În plus, turnarea ajută la obținerea unui rezultat curat. Comparați rezultatul tabloului hashtable Cluster transmis către Format-Table cu ceea ce obțineți dacă aruncați mai întâi aceste hashtable într-o clasă. Proprietățile unei clase sunt întotdeauna listate în ordinea în care sunt definite acolo. Nu uitați să adăugați cuvântul cheie ascuns înaintea tuturor acelor proprietăți pe care nu doriți să fie vizibile în rezultate.

Powershell funcțional cu clase nu este un oximoron, vă garantez

Distribuție de semnificații

Dacă aveți un constructor cu un singur argument, turnarea unei valori în tipul clasei dvs. va transmite valoarea constructorului dvs., unde puteți inițializa o instanță a clasei dvs.

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"

Distribuie pe linie

De asemenea, puteți suprascrie metoda clasei [șir] ToString() pentru a defini logica din spatele reprezentării șirurilor de caractere a obiectului, cum ar fi utilizarea interpolării șirurilor.

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

Distribuiți instanțe serializate

Cast permite deserializarea în siguranță. Exemplele de mai jos vor eșua dacă datele nu îndeplinesc specificațiile noastre din Cluster

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

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

Caste în codul tău funcțional

Programele funcționale definesc mai întâi structurile de date, apoi implementează programul ca o secvență de transformări peste structuri de date imuabile. În ciuda impresiei contradictorii, clasele te ajută cu adevărat să scrii cod funcțional datorită metodelor de conversie a tipurilor.

Powershell-ul pe care îl scriu este funcțional?

Mulți oameni care provin din C# sau medii similare scriu Powershell, care este similar cu C#. Făcând acest lucru, te îndepărtezi de la utilizarea conceptelor de programare funcțională și probabil că ai beneficia de a te scufunda puternic în programarea orientată pe obiecte în Powershell sau de a afla mai multe despre programarea funcțională.

Dacă te bazezi foarte mult pe transformarea datelor imuabile folosind conducte (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object etc. - ai un stil mai funcțional și vei beneficia de utilizarea Powershell cursuri într-un stil funcțional.

Utilizarea funcțională a claselor

Castele, deși folosesc o sintaxă alternativă, sunt doar o mapare între două domenii. În conductă, puteți mapa o matrice de valori folosind ForEach-Object.

În exemplul de mai jos, constructorul Node este executat de fiecare dată când este turnat un Datum, iar acest lucru ne oferă posibilitatea de a nu scrie o cantitate suficientă de cod. Ca rezultat, pipeline-ul nostru se concentrează pe interogarea și agregarea declarativă a datelor, în timp ce clasele noastre se ocupă de analizarea și validarea datelor.

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

Clasa de ambalare pentru reutilizare

Nimic nu este atât de bun pe cât pare

Din păcate, clasele nu pot fi exportate de module în același mod ca funcțiile sau variabilele; dar există câteva trucuri. Să presupunem că clasele tale sunt definite în fișierul ./my-classes.ps1

  • Puteți dotsource un fișier cu clase:. ./clasele-mea.ps1. Acest lucru va executa my-classes.ps1 în domeniul dvs. actual și va defini toate clasele din fișierul de acolo.

  • Puteți crea un modul Powershell care să exporte toate API-urile personalizate (cmdleturi) și să setați variabila ScriptsToProcess = "./my-classes.ps1" în manifestul modulului, cu același rezultat: ./my-classes.ps1 se va executa în mediul tău.

Indiferent de opțiunea pe care o alegeți, rețineți că sistemul de tip Powershell nu poate rezolva tipurile cu același nume încărcate din locuri diferite.
Chiar dacă ați încărcat două clase identice cu aceleași proprietăți din locuri diferite, riscați să întâmpinați probleme.

Calea de urmat

Cel mai bun mod de a evita problemele de rezoluție de tip este să nu expui niciodată clasele utilizatorilor. În loc să vă așteptați ca utilizatorul să importe un tip definit de clasă, exportați o funcție din modulul dvs. care elimină necesitatea de a accesa clasa direct. Pentru Cluster, putem exporta o funcție New-Cluster care va accepta seturi de parametri ușor de utilizat și va returna un 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

Ce altceva de citit

Despre cursuri
PowerShell defensiv
Programare funcțională în PowerShell

Sursa: www.habr.com

Adauga un comentariu