Funkcionalni Powershell s klasama nije oksimoron, to jamčim

Hej Habr! Vašoj pozornosti predstavljam prijevod članka "Funkcionalni PowerShell s klasama.
Obećavam da nije oksimoron"
autora Christophera Kuecha.

Paradigme objektno orijentiranog i funkcionalnog programiranja mogu se činiti u suprotnosti jedna s drugom, ali obje su jednako podržane u Powershellu. Gotovo svi programski jezici, funkcionalni ili ne, imaju mogućnost proširenog povezivanja imena i vrijednosti; Klase, poput struktura i zapisa, samo su jedan pristup. Ako ograničimo našu upotrebu klasa na vezanje imena i vrijednosti i izbjegnemo teške koncepte objektno orijentiranog programiranja kao što su nasljeđivanje, polimorfizam ili promjenjivost, možemo iskoristiti njihove prednosti bez kompliciranja koda. Nadalje, dodavanjem nepromjenjivih metoda pretvorbe tipa, možemo obogatiti svoj funkcionalni kod klasama.

Magija kasti

Kaste su jedna od najmoćnijih značajki u Powershellu. Kada postavljate vrijednost, oslanjate se na implicitne mogućnosti inicijalizacije i provjere valjanosti koje okolina dodaje vašoj aplikaciji. Na primjer, jednostavno ubacivanje niza u [xml] provest će ga kroz kod parsera i generirati potpuno 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 hash tablicu pretvoriti u svoj tip klase. Ne zaboravite koristiti atribute provjere valjanosti kako biste u potpunosti iskoristili ovaj obrazac. U isto vrijeme, možemo koristiti tipizirana svojstva klase za pokretanje još dublje logike inicijalizacije i provjere valjanosti.

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, lijevanje pomaže u dobivanju čistog rezultata. Usporedite izlaz niza Cluster hashtable proslijeđenog Format-Tableu s onim što dobijete ako prvo ubacite ove hashtable u klasu. Svojstva klase uvijek su navedena redom kojim su tamo definirana. Ne zaboravite dodati skrivenu ključnu riječ prije svih onih svojstava za koja ne želite da budu vidljiva u rezultatima.

Funkcionalni Powershell s klasama nije oksimoron, to jamčim

Odljev značenja

Ako imate konstruktor s jednim argumentom, pretvaranje vrijednosti u vaš tip klase proslijedit će vrijednost vašem konstruktoru, gdje možete inicijalizirati instancu svoje 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 biste definirali logiku koja stoji iza reprezentacije niza objekata, kao što je korištenje interpolacije niza.

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ćuje sigurnu deserijalizaciju. Primjeri u nastavku neće uspjeti ako podaci ne zadovoljavaju našu specifikaciju u klasteru

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

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

Kaste u vašem funkcionalnom kodu

Funkcionalni programi prvo definiraju strukture podataka, zatim implementiraju program kao niz transformacija nad nepromjenjivim strukturama podataka. Unatoč kontradiktornom dojmu, klase vam stvarno pomažu u pisanju funkcionalnog koda zahvaljujući metodama pretvorbe tipa.

Je li Powershell koji pišem funkcionalan?

Mnogo ljudi koji dolaze iz C# ili sličnih pozadina pišu Powershell, koji je sličan C#. Čineći to, udaljavate se od upotrebe koncepata funkcionalnog programiranja i vjerojatno biste imali koristi od intenzivnog poniranja u objektno orijentirano programiranje u Powershellu ili učenja više o funkcionalnom programiranju.

Ako se uvelike 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 upotrebe Powershell-a nastave u funkcionalnom stilu.

Funkcionalna uporaba razreda

Kaste, iako koriste alternativnu sintaksu, samo su preslikavanje između dvije domene. U cjevovodu možete mapirati niz vrijednosti koristeći ForEach-Object.

U donjem primjeru, konstruktor čvora izvršava se svaki put kada se Datum izbaci, a to nam daje priliku da ne napišemo priličnu količinu koda. Kao rezultat toga, naš se cjevovod usredotočuje na deklarativno postavljanje upita i prikupljanje podataka, dok se naše klase brinu za raščlanjivanje i provjeru 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 pakiranja za ponovnu upotrebu

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

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

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

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

Koju god opciju odabrali, imajte na umu da Powershellov sustav tipova ne može razriješiti tipove istog imena učitane s različitih mjesta.
Čak i ako ste učitali dvije identične klase s istim svojstvima s različitih mjesta, riskirate da naiđete na probleme.

Put naprijed

Najbolji način da izbjegnete probleme s razrješenjem tipa je da nikada ne izlažete svoje klase korisnicima. Umjesto da očekujete da će korisnik uvesti tip definiran klasom, izvezite funkciju iz svog modula koja eliminira potrebu za izravnim pristupom klasi. Za Cluster možemo izvesti funkciju New-Cluster koja će podržati skupove parametara prilagođenih 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

Što još čitati

O nastavi
Obrambeni PowerShell
Funkcionalno programiranje u PowerShell-u

Izvor: www.habr.com

Dodajte komentar