Cześć, Habr! Przedstawiam Wam tłumaczenie artykułu autorstwa Christophera Kuecha.
Obiektowo-orientowane i funkcjonalne paradygmaty programowania mogą wydawać się niezgodne, ale oba są równorzędnie wspierane w PowerShell. Praktycznie wszystkie języki programowania, zarówno funkcjonalne, jak i nie, dysponują środkami zaawansowanego powiązania nazw i wartości; Klasy, podobne do struktur i rekordów, to tylko jeden z podejść. Jeśli ograniczymy użycie klas do powiązania nazw i wartości oraz unikniemy „ciężkich” obiektowo-orientowanych koncepcji, takich jak dziedziczenie, polimorfizm czy zmienność (mutability), możemy skorzystać z ich zalet, nie komplikując naszego kodu. Następnie, dodając niezmienne (immutable) metody konwersji typów, możemy wzbogacić nasz funkcjonalny kod o klasy.
Magia rzutów
Rzutowanie to jedna z najpotężniejszych funkcji w PowerShell. Gdy dokonujesz rzutowania wartości, polegasz na funkcjach automatycznej inicjalizacji i walidacji wprowadzonych przez środowisko do twojej aplikacji. Na przykład prosty rzutowanie ciągu w [xml] przeprowadzi go przez kod parsera i wygeneruje pełne drzewo xml. Możemy w naszym kodzie wykorzystać klasy w tym samym celu.
Rzutowanie tablic haszujących
Jeżeli nie masz konstruktora, możesz kontynuować bez niego, używając rzutowania tablicy haszującej na typ twojej klasy. Nie zapomnij skorzystać z atrybutów walidacji, aby w pełni wykorzystać ten wzorzec. Przy okazji możemy użyć typowanych właściwości klasy, aby uruchomić jeszcze głębszą logikę inicjalizacji i walidacji.
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
}Ponadto, rzutowanie pomaga uzyskać czysty wynik. Porównaj wynik tablicy haszującej klastra przekazany do Format-Table z tym, co otrzymasz, jeśli najpierw rzutujesz te tablice haszujące na klasę. Właściwości klasy są zawsze wymieniane w kolejności, w jakiej zostały zdefiniowane. Nie zapomnij dodać słowa kluczowego hidden przed wszystkimi właściwościami, które nie powinny być widoczne w wynikach.

Rzutowanie wartości
Jeśli masz konstruktor z jednym argumentem, rzutowanie wartości na Twój typ klasy przekaże wartość do tego konstruktor, w którym możesz zainicjować instancję swojej klasy.
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"Rzutowanie na ciąg
Możesz również przesłonić metodę klasy [string] ToString(), aby określić logikę reprezentacji tekstowej obiektu, na przykład korzystając z interpolacji ciągów.
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 "Właśnie utworzyliśmy model dla '$cluster'"Typ zserializowanych instancji
Typ pozwala na bezpieczną deserializację. Poniższe przykłady zakończą się błędem, jeśli dane nie będą zgodne z naszą specyfikacją w Cluster
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"Typy w Twoim kodzie funkcyjnym
Programy funkcyjne najpierw definiują struktury danych, a następnie implementują program jako sekwencję transformacji na niemodyfikowalnych strukturach danych. Mimo kontrowersyjnych wrażeń, klasy faktycznie pomagają w pisaniu kodu funkcyjnego dzięki metodom konwersji typów.
Czy piszę w funkcyjnym Powershellu?
Wielu ludzi z doświadczeniem w C# lub podobnych językach pisze w PowerShell, który przypomina C#. Decydując się na to, rezygnujesz z koncepcji programowania funkcyjnego i prawdopodobnie skorzystasz na深入进去 obiektowym programowaniu w PowerShell lub lepszym zrozumieniu programowania funkcyjnego.
Jeśli mocno polegasz na transformacji danych niemutowalnych przy użyciu potoków (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object itp. — twój styl jest bardziej funkcyjny, a klasy PowerShell w stylu funkcyjnym będą przydatne.
Funkcyjne wykorzystanie klas
Przekazy, mimo że wykorzystują alternatywną składnię, to jedynie mapowanie między dwoma domenami. W potoku można zmapować tablicę wartości za pomocą ForEach-Object.
W poniższym przykładzie konstruktor Node jest wywoływany za każdym razem, gdy następuje rzutowanie na Datum, co pozwala nam uniknąć pisania dużej ilości kodu. W rezultacie nasz potok koncentruje się na deklaratywnym zapytaniu danych i agregacji, podczas gdy nasze klasy zajmują się parsowaniem danych i walidacją.
# Пример комбинирования классов с конвейерами для 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 -UniqueKlasa opakowania do ponownego wykorzystania
Nic nie jest tak dobre, jak się wydaje
Niestety, klasy nie mogą być eksportowane przez moduły w taki sam sposób jak funkcje czy zmienne; jednak są pewne sztuczki. Załóżmy, że Twoje klasy są zdefiniowane w pliku ./my-classes.ps1
Możesz doładować plik z klasami: ./my-classes.ps1. To wykona my-classes.ps1 w Twoim bieżącym zakresie i zdefiniuje wszystkie klasy z pliku.
Możesz stworzyć moduł Powershell, który eksportuje wszystkie Twoje własne API (cmdlety) i ustawić zmienną ScriptsToProcess = "./my-classes.ps1" w manifeście swojego modułu, osiągając ten sam rezultat: ./my-classes.ps1 wykona się w Twoim środowisku.
Bez względu na wybraną opcję, pamiętaj, że system typów Powershell nie potrafi obsługiwać typów o tej samej nazwie ładowanych z różnych miejsc.
Nawet jeśli załadujesz z różnych miejsc dwie identyczne klasy z tymi samymi właściwościami, możesz napotkać problemy.
Droga naprzód
Najlepszym sposobem na uniknięcie problemów z typami to nigdy nie wystawiać swoich klas użytkownikom. Zamiast oczekiwać, że użytkownik zaimportuje określony typ z klasy, wyeksportuj z modułu funkcję, która zwolni z konieczności bezpośredniego odwoływania się do klasy. W odniesieniu do Cluster możemy wyeksportować funkcję New-Cluster, która obsłuży przyjazne dla użytkownika zestawy parametrów i zwróci 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-ClusterCo jeszcze przeczytać
Źródło: habr.com
