Hej Habro! Zwracam uwagę na tłumaczenie artykułu
Zapewniam, że to nie oksymoron”
Paradygmaty programowania obiektowego i funkcjonalnego mogą wydawać się ze sobą sprzeczne, ale oba są w równym stopniu obsługiwane w programie Powershell. Prawie wszystkie języki programowania, funkcjonalne lub nie, mają możliwości rozszerzonego wiązania nazw i wartości; Klasy, podobnie jak struktury i rekordy, to tylko jedno podejście. Jeśli ograniczymy użycie klas do wiązania nazw i wartości i unikniemy ciężkich koncepcji programowania obiektowego, takich jak dziedziczenie, polimorfizm lub zmienność, możemy skorzystać z ich zalet bez komplikowania naszego kodu. Co więcej, dodając niezmienne metody konwersji typów, możemy wzbogacić nasz kod funkcjonalny o klasy.
Magia kast
Kasty to jedna z najpotężniejszych funkcji programu Powershell. Rzucając wartość, polegasz na niejawnych możliwościach inicjowania i sprawdzania poprawności, które środowisko dodaje do Twojej aplikacji. Na przykład proste rzutowanie ciągu znaków do pliku [xml] spowoduje uruchomienie go przez kod parsera i wygenerowanie kompletnego drzewa XML. W tym samym celu możemy używać klas w naszym kodzie.
Przesyłaj tablice mieszające
Jeśli nie masz konstruktora, możesz kontynuować bez niego, rzutując tablicę mieszającą na typ swojej klasy. Nie zapomnij użyć atrybutów walidacji, aby w pełni wykorzystać ten wzorzec. Jednocześnie możemy użyć typowanych właściwości klasy, aby uruchomić jeszcze głębszą logikę inicjalizacji i sprawdzania poprawności.
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 odlewanie pomaga uzyskać czysty wydruk. Porównaj wynik tablicy mieszającej Cluster przekazanej do tabeli Format z tym, co otrzymasz, jeśli najpierw rzucisz te tablice mieszające na klasę. Właściwości klasy są zawsze wymienione w kolejności, w jakiej są tam zdefiniowane. Nie zapomnij dodać słowa kluczowego ukrytego przed wszystkimi właściwościami, których nie chcesz, aby były widoczne w wynikach.
Obsada znaczeń
Jeśli masz konstruktor z jednym argumentem, rzutowanie wartości na typ klasy spowoduje przekazanie wartości do konstruktora, gdzie 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"
Rzuć do linii
Można także przesłonić metodę klasy [string] ToString(), aby zdefiniować logikę stojącą za reprezentacją obiektu w postaci ciągu znaków, na przykład za pomocą 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 "We just created a model for '$cluster'"
Przesyłaj serializowane instancje
Obsada umożliwia bezpieczną deserializację. Poniższe przykłady nie powiodą się, jeśli dane nie spełniają naszej specyfikacji w klastrze
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"
Castuje w kodzie funkcjonalnym
Programy funkcjonalne najpierw definiują struktury danych, a następnie implementują program jako sekwencję transformacji na niezmiennych strukturach danych. Pomimo sprzecznego wrażenia, klasy naprawdę pomagają w pisaniu kodu funkcjonalnego dzięki metodom konwersji typów.
Czy PowerShell, który piszę, działa?
Wiele osób mających doświadczenie w języku C# lub podobnym, pisze Powershell, który jest podobny do C#. Robiąc to, odchodzisz od koncepcji programowania funkcjonalnego i prawdopodobnie odniesiesz korzyść, jeśli zagłębisz się w programowanie obiektowe w Powershell lub dowiesz się więcej o programowaniu funkcjonalnym.
Jeśli w dużym stopniu polegasz na przekształcaniu niezmiennych danych za pomocą potoków (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object itp. - masz bardziej funkcjonalny styl i odniesiesz korzyści z używania Powershell zajęcia w stylu funkcjonalnym.
Funkcjonalne wykorzystanie klas
Kasty, choć używają alternatywnej składni, są jedynie odwzorowaniem pomiędzy dwiema domenami. W potoku możesz zmapować tablicę wartości za pomocą ForEach-Object.
W poniższym przykładzie konstruktor Node jest wykonywany za każdym razem, gdy rzucane jest Datum, co daje nam możliwość napisania niewielkiej ilości kodu. W rezultacie nasz potok koncentruje się na deklaratywnym wykonywaniu zapytań i agregacji danych, podczas gdy nasze klasy zajmują się analizowaniem i walidacją danych.
# Пример комбинирования классов с конвейерами для 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 opakowania do ponownego użycia
Nic nie jest tak dobre, jak się wydaje
Niestety klas nie można eksportować przez moduły w taki sam sposób, jak funkcje lub zmienne; ale jest kilka sztuczek. Załóżmy, że Twoje klasy są zdefiniowane w pliku ./my-classes.ps1
-
Możesz pobrać plik z klasami:. ./moje-klasy.ps1. Spowoduje to wykonanie my-classes.ps1 w bieżącym zakresie i zdefiniowanie wszystkich klas z znajdującego się tam pliku.
-
Możesz utworzyć moduł Powershell, który eksportuje wszystkie niestandardowe interfejsy API (cmdlet) i ustawić zmienną ScriptsToProcess = "./my-classes.ps1" w manifeście modułu, z tym samym skutkiem: ./my-classes.ps1 zostanie wykonane w Twoje środowisko.
Niezależnie od tego, którą opcję wybierzesz, pamiętaj, że system typów Powershell nie może rozpoznawać typów o tej samej nazwie ładowanych z różnych miejsc.
Nawet jeśli załadujesz dwie identyczne klasy o tych samych właściwościach z różnych miejsc, ryzykujesz wystąpieniem problemów.
Droga naprzód
Najlepszym sposobem na uniknięcie problemów z rozpoznawaniem typów jest nigdy nie udostępnianie klas użytkownikom. Zamiast oczekiwać, że użytkownik zaimportuje typ zdefiniowany przez klasę, wyeksportuj funkcję ze swojego modułu, która eliminuje potrzebę bezpośredniego dostępu do klasy. W przypadku klastra możemy wyeksportować funkcję New-Cluster, która będzie obsługiwać przyjazne dla użytkownika zestawy parametrów i zwracać klaster.
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
Co jeszcze przeczytać
Źródło: www.habr.com