Funkcionalni Powershell sa klasama nije oksimoron, garantujem

Hej Habr! Predstavljam Vašoj pažnji prevod članka "Funkcionalni PowerShell sa klasama.
Obećavam da to nije oksimoron"
od Christophera Kuecha.

Objektno orijentisana i funkcionalna programska paradigma mogu izgledati u suprotnosti jedna s drugom, ali obje su podjednako podržane u Powershell-u. Gotovo svi programski jezici, funkcionalni ili ne, imaju mogućnosti za prošireno vezivanje ime-vrijednost; Klase, kao što su strukture i zapisi, su samo jedan pristup. Ako ograničimo našu upotrebu klasa na vezivanje imena i vrijednosti i izbjegnemo teške koncepte objektno orijentisanog programiranja kao što su nasljeđivanje, polimorfizam ili promjenjivost, možemo ih iskoristiti bez kompliciranja našeg koda. Nadalje, dodavanjem nepromjenjivih metoda konverzije tipa, možemo obogatiti naš funkcionalni kod klasama.

Magija kasti

Kaste su jedna od najmoćnijih karakteristika u Powershell-u. Kada dodijelite vrijednost, oslanjate se na implicitne mogućnosti inicijalizacije i validacije koje okruženje dodaje vašoj aplikaciji. Na primjer, jednostavno bacanje stringa u [xml] će ga pokrenuti kroz kod parsera i generirati kompletno xml stablo. U istu svrhu možemo koristiti klase u našem kodu.

Cast hashtables

Ako nemate konstruktor, možete nastaviti bez njega tako što ćete ubaciti hashtable na svoj tip klase. Ne zaboravite da koristite atribute validacije da biste u potpunosti iskoristili ovaj obrazac. U isto vrijeme, možemo koristiti upisana svojstva klase za pokretanje još dublje logike 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
}

Osim toga, livenje pomaže da se dobije čist rezultat. Uporedite izlaz niza hashtable Cluster proslijeđenog u Format-Table sa onim što dobijete ako prvo bacite ove hashtable u klasu. Svojstva klase su uvijek navedena onim redoslijedom kojim su tamo definirana. Nemojte zaboraviti dodati skrivenu ključnu riječ ispred svih onih svojstava koja ne želite da budu vidljiva u rezultatima.

Funkcionalni Powershell sa klasama nije oksimoron, garantujem

Cast značenja

Ako imate konstruktor s jednim argumentom, prebacivanje vrijednosti na tip vaše klase će proslijediti vrijednost vašem konstruktoru, gdje možete inicijalizirati instancu vaše klase

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"

Baci na liniju

Također možete nadjačati metodu klase [string] ToString() da definirate logiku iza string reprezentacije objekta, kao što je korištenje interpolacije stringova.

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 serijalizirane instance

Cast omogućava sigurnu deserializaciju. Primjeri u nastavku neće uspjeti ako podaci ne zadovoljavaju našu specifikaciju u Clusteru

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

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

Uvodi u vaš funkcionalni kod

Funkcionalni programi prvo definiraju strukture podataka, a zatim implementiraju program kao niz transformacija nad nepromjenjivim strukturama podataka. Uprkos kontradiktornom utisku, klase vam zaista pomažu u pisanju funkcionalnog koda zahvaljujući metodama konverzije tipova.

Je li Powershell koji pišem funkcionalan?

Mnogi ljudi koji dolaze iz C# ili slične pozadine pišu Powershell, što je slično C#. Čineći ovo, udaljavate se od korištenja koncepta funkcionalnog programiranja i vjerojatno biste imali koristi od urona u objektno orijentirano programiranje u Powershell-u ili učenja više o funkcionalnom programiranju.

Ako se u velikoj mjeri oslanjate na transformaciju nepromjenjivih podataka pomoću cjevovoda (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, itd. - imate funkcionalniji stil i imat ćete koristi od korištenja Powershell-a nastava u funkcionalnom stilu.

Funkcionalna upotreba časova

Kaste, iako koriste alternativnu sintaksu, samo su mapiranje između dva domena. U cjevovodu možete mapirati niz vrijednosti pomoću ForEach-Object.

U primjeru ispod, konstruktor čvora se izvršava svaki put kada se datum izbacuje, a to nam daje priliku da ne napišemo priličnu količinu koda. Kao rezultat toga, naš cevovod se fokusira na deklarativno ispitivanje podataka i agregaciju, dok se naše klase brinu za raščlanjivanje i validaciju podataka.

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

Ništa nije tako dobro kao što se čini

Nažalost, moduli ne mogu eksportovati klase na isti način kao funkcije ili varijable; ali postoje neki trikovi. Recimo da su vaše klase definisane u datoteci ./my-classes.ps1

  • Možete dotsource fajl sa klasama:. ./my-classes.ps1. Ovo će izvršiti my-classes.ps1 u vašem trenutnom opsegu i definirati sve klase iz datoteke tamo.

  • Možete kreirati Powershell modul koji izvozi sve vaše prilagođene API-je (cmdlete) i postaviti varijablu ScriptsToProcess = "./my-classes.ps1" u manifestu vašeg modula, sa istim rezultatom: ./my-classes.ps1 će se izvršiti u vaše okruženje.

Koju god opciju da odaberete, imajte na umu da Powershell-ov sistem tipova ne može riješiti tipove istog imena učitane s različitih mjesta.
Čak i ako ste učitali dvije identične klase sa istim svojstvima sa različitih mjesta, rizikujete da naiđete na probleme.

Put naprijed

Najbolji način da izbjegnete probleme s rezolucijom tipova je da nikada ne izlažete svoje klase korisnicima. Umjesto da očekujete da korisnik uveze tip definiran klasom, izvezite funkciju iz vašeg modula koja eliminira potrebu za direktnim pristupom klasi. Za Cluster, možemo izvesti funkciju New-Cluster koja će podržati skupove parametara prilagođene korisniku i vratiti 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

Šta još čitati

About Classes
Defensive PowerShell
Funkcionalno programiranje u PowerShell-u

izvor: www.habr.com

Dodajte komentar