Salut, Habr! Vă prezint traducerea articolului autorul Christopher Kuech.
Paradigmele de programare obiectuală și funcțională pot părea în contradicție, dar ambele sunt susținute în mod egal în PowerShell. Practic, toate limbajele de programare, atât funcționale cât și non-funcționale, dispun de metode de legare avansată a numelui și valorilor; Clasele, asemeni structurilor și înregistrărilor, sunt doar o abordare. Dacă limităm utilizarea claselor la legarea numelui și valorilor și evităm concepte de programare obiectuală "grele" precum moștenirea, polimorfismul sau mutabilitatea, putem beneficia de avantajele lor fără a complica codul nostru. În plus, adăugând metode de conversie imutabilă, putem îmbogăți codul nostru funcțional cu clase.
Magia conversiilor
Conversiile sunt unele dintre cele mai puternice caracteristici din PowerShell. Când supui o valoare unei conversii, te bazezi pe capacitatea adăugată de mediu în aplicația ta de inițializare și validare implicită. De exemplu, o simplă conversie a unui șir în [xml] va trece prin codul parserului și va genera un arbore XML complet. Putem folosi clasele în codul nostru cu același scop.
Conversia hash table-urilor
Dacă nu ai un constructor, poți continua fără el, utilizând conversia hash table-ului în tipul clasei tale. Nu uita să folosești atributele de validare pentru a profita la maximum de acest model. De asemenea, putem utiliza proprietăți tipizate ale clasei pentru a activa 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, conversia ajută la obținerea unei ieșiri curate. Compară ieșirea unui array de hash table-uri Cluster transmis în Format-Table cu ceea ce vei obține dacă convertești mai întâi aceste hash table-uri în clase. Proprietățile clasei sunt întotdeauna listate în ordinea în care sunt definite. Nu uita să adaugi cuvântul cheie hidden înaintea tuturor proprietăților care nu ar trebui să fie vizibile în ieșire.

Conversia valorilor
Dacă aveți un constructor cu un singur argument, conversia valorii la tipul dvs. de clasă va transmite valoarea acestui constructor, în care puteți inițializa 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"Conversia la șir
De asemenea, puteți suprascrie metoda clasei [string] ToString() pentru a defini logica de reprezentare în șir a obiectului, de exemplu, folosind interpolarea ș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 "Am creat un model pentru '$cluster'"Conversia instanțelor serializate
Conversia permite deserializarea sigură. Exemplele de mai jos vor genera erori dacă datele nu respectă specificațiile noastre din Cluster.
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Conversiile în codul dvs. funcțional
Programele funcționale definesc mai întâi structuri de date, apoi implementează programul ca o succesiune de transformări asupra structurilor de date nemodificabile. În ciuda impresiei contradictorii, clasele ajută la scrierea codului funcțional, datorită metodelor de conversie a tipurilor.
Scriu un Powershell funcțional?
Multe persoane venind din C# sau cu un trecut similar scriu Powershell care seamănă cu C#. Procedând astfel, renunțați la utilizarea conceptelor de programare funcțională și, probabil, veți beneficia de aprofundarea programării orientate spre obiect în Powershell sau de o mai bună învățare a programării funcționale.
Dacă depindeți profund de transformarea datelor imutabile, utilizând pipe-uri (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object etc. — aveți un stil mai funcțional și utilizarea claselor Powershell într-un stil funcțional vă va ajuta.
Utilizarea funcțională a claselor
Castele, deși folosesc o sintaxă alternativă, sunt doar un mapping între două domenii. În pipeline, se poate mapa un array de valori folosind ForEach-Object.
În exemplul de mai jos, constructorul Node este executat de fiecare dată când se face un cast la Datum, oferindu-ne posibilitatea de a nu scrie o cantitate considerabilă de cod. Astfel, pipeline-ul nostru se concentrează pe interogarea și agregarea declarativă a datelor, în timp ce clasele noastre preiau parsing-ul ș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Împachetarea clasei pentru reutilizare
Nimic nu este chiar 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 într-un fișier .\/my-classes.ps1.
Poți să faci dot sourcing pe fișierul cu clase:. .\/my-classes.ps1. Acest lucru va executa my-classes.ps1 în aria ta curentă de vizibilitate și va defini toate clasele din fișier.
Poți crea un modul PowerShell care exportă toate API-urile tale personalizate (cmdlet-uri) și să stabilești variabila ScriptsToProcess = ".\/my-classes.ps1" în manifestul modulului tău, cu același rezultat: .\/my-classes.ps1 va fi executat în mediul tău.
Indiferent de opțiunea pe care o alegi, nu uita că sistemul de tipuri PowerShell nu poate gestiona tipuri cu același nume încărcate din locuri diferite.
Chiar dacă ai încărcat din locuri diferite două clase identice cu proprietăți identice, riști să te confrunți cu probleme.
Calea înainte
Cel mai bun mod de a evita problemele de rezolvare a tipurilor este să nu expui clasele tale utilizatorilor. În loc să aștepți ca utilizatorul să importe un tip definit în clasă, exportează din modulul tău o funcție care reduce necesitatea de a apela direct la clasă. Referitor la Cluster, putem exporta funcția New-Cluster, care va susține seturi de parametri prietenoși utilizatorului și va returna 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-ClusterCe altceva să citim
Sursa: habr.com
