Funksionele Powershell met klasse is nie 'n oksimoron nie, ek waarborg dit

Haai Habr! Ek bied die vertaling van die artikel aan u aandag "Funksionele PowerShell met klasse.
Ek belowe dit is nie 'n oksimoron nie"
deur Christopher Kuech.

Objekgeoriënteerde en funksionele programmeringsparadigmas lyk dalk in stryd met mekaar, maar albei word ewe ondersteun in Powershell. Byna alle programmeertale, funksioneel of nie, het fasiliteite vir uitgebreide naam-waarde-binding; Klasse, soos strukture en rekords, is net een benadering. As ons ons gebruik van Klasse beperk tot die binding van name en waardes, en swaar objekgeoriënteerde programmeringskonsepte soos oorerwing, polimorfisme of veranderlikheid vermy, kan ons voordeel trek uit hul voordele sonder om ons kode te bemoeilik. Verder, deur onveranderlike tipe omskakelingsmetodes by te voeg, kan ons ons funksionele kode met Klasse verryk.

Die magie van kaste

Kastes is een van die kragtigste kenmerke in Powershell. Wanneer jy 'n waarde werp, maak jy staat op die implisiete inisialisering- en valideringsvermoëns wat die omgewing by jou toepassing voeg. Byvoorbeeld, om bloot 'n string in [xml] te gooi, sal dit deur die ontlederkode laat loop en 'n volledige xml-boom genereer. Ons kan Klasse in ons kode vir dieselfde doel gebruik.

Gooi hash-tabelle uit

As jy nie 'n konstruktor het nie, kan jy voortgaan sonder een deur 'n hash-tabel na jou klastipe te gooi. Moenie vergeet om die valideringskenmerke te gebruik om hierdie patroon ten volle te benut nie. Terselfdertyd kan ons die klas se getikte eienskappe gebruik om nog dieper inisialisering en valideringslogika uit te voer.

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
}

Daarbenewens help gietwerk om 'n skoon uitset te kry. Vergelyk die uitvoer van die Cluster hashable-skikking wat na Format-Table oorgedra is met wat jy kry as jy eers hierdie hashables in 'n klas gooi. Die eienskappe van 'n klas word altyd gelys in die volgorde waarin hulle daar gedefinieer is. Moenie vergeet om die verborge sleutelwoord by te voeg voor al daardie eienskappe wat jy nie in die resultate wil sien nie.

Funksionele Powershell met klasse is nie 'n oksimoron nie, ek waarborg dit

Giet van betekenisse

As jy 'n konstruktor met een argument het, sal die oordrag van 'n waarde na jou klastipe die waarde na jou konstruktor oorgedra word, waar jy 'n instansie van jou klas kan inisialiseer

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"

Gooi na lyn

Jy kan ook die [string] ToString() klasmetode ignoreer om die logika agter die voorwerp se stringvoorstelling te definieer, soos om stringinterpolasie te gebruik.

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

Gooi geserialiseerde gevalle uit

Cast laat veilige deserialisering toe. Die voorbeelde hieronder sal misluk as die data nie aan ons spesifikasie in Cluster voldoen nie

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

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

Kastes in jou funksionele kode

Funksionele programme definieer eers datastrukture, implementeer dan die program as 'n reeks transformasies oor onveranderlike datastrukture. Ten spyte van die teenstrydige indruk, help klasse jou regtig om funksionele kode te skryf danksy tipe omskakelingsmetodes.

Is die Powershell wat ek skryf funksioneel?

Baie mense wat uit C# of soortgelyke agtergronde kom, skryf Powershell, wat soortgelyk is aan C#. Deur dit te doen, beweeg jy weg van die gebruik van funksionele programmeringskonsepte en sal jy waarskynlik daarby baat vind om baie na objekgeoriënteerde programmering in Powershell te duik of meer oor funksionele programmering te leer.

As jy baie staatmaak op die transformasie van onveranderlike data deur gebruik te maak van pyplyne (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, ens. - jy het 'n meer funksionele styl en jy sal baat by die gebruik van Powershell klasse in 'n funksionele styl.

Funksionele gebruik van klasse

Kastes, hoewel hulle 'n alternatiewe sintaksis gebruik, is net 'n kartering tussen twee domeine. In die pyplyn kan u 'n verskeidenheid waardes karteer deur ForEach-Object te gebruik.

In die voorbeeld hieronder word die Node-konstruktor uitgevoer elke keer as 'n Datum gegiet word, en dit gee ons die geleentheid om nie 'n redelike hoeveelheid kode te skryf nie. Gevolglik fokus ons pyplyn op verklarende data-navrae en samevoeging, terwyl ons klasse sorg vir data-ontleding en -validering.

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

Verpakkingsklas vir hergebruik

Niks is so goed soos dit lyk nie

Ongelukkig kan klasse nie deur modules uitgevoer word op dieselfde manier as funksies of veranderlikes nie; maar daar is 'n paar truuks. Kom ons sê jou klasse word in die lêer ./my-classes.ps1 gedefinieer

  • Jy kan 'n lêer met klasse dotsource:. ./my-klasse.ps1. Dit sal my-classes.ps1 in jou huidige omvang uitvoer en al die klasse uit die lêer daar definieer.

  • Jy kan 'n Powershell-module skep wat al jou pasgemaakte API's (cmdlets) uitvoer en die ScriptsToProcess = "./my-classes.ps1" veranderlike in jou modulemanifes stel, met dieselfde resultaat: ./my-classes.ps1 sal uitgevoer word in jou omgewing.

Watter opsie jy ook al kies, hou in gedagte dat Powershell se tipe stelsel nie tipes met dieselfde naam kan oplos wat van verskillende plekke gelaai is nie.
Selfs as jy twee identiese klasse met dieselfde eienskappe van verskillende plekke gelaai het, loop jy die risiko om probleme te ondervind.

Die pad vorentoe

Die beste manier om tipe resolusie probleme te vermy, is om nooit jou klasse aan gebruikers bloot te stel nie. In plaas daarvan om van die gebruiker te verwag om 'n klasgedefinieerde tipe in te voer, voer 'n funksie uit jou module uit wat die behoefte om direk toegang tot die klas uit te skakel. Vir Cluster kan ons 'n New-Cluster-funksie uitvoer wat gebruikersvriendelike parameterstelle sal ondersteun en 'n Cluster sal terugstuur.

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

Wat anders om te lees

Oor Klasse
Verdedigende PowerShell
Funksionele programmering in PowerShell

Bron: will.com

Voeg 'n opmerking