帶有類別的功能性 Powershell 並不是一個矛盾的說法,我保證這一點

嘿哈布爾! 我提請您注意這篇文章的翻譯 「帶有類別的功能性 PowerShell。
我保證這不是矛盾”
作者:克里斯多福‧庫奇。

物件導向和函數式程式設計範式看似相互矛盾,但兩者在 Powershell 中都受到同等支援。 幾乎所有的程式語言,無論是否是函數式的,都具有擴展名稱-值綁定的功能; 類,如結構和記錄,只是一種方法。 如果我們將類別的使用限制為名稱和值的綁定,並避免大量的物件導向程式設計概念,例如繼承、多態性或可變性,那麼我們就可以利用它們的優勢,而不會使我們的程式碼複雜化。 此外,透過新增不可變型別轉換方法,我們可以用類別豐富我們的功能程式碼。

種姓的魔力

種姓是 Powershell 中最強大的功能之一。 當您轉換值時,您將依賴環境新增至應用程式中的隱式初始化和驗證功能。 例如,簡單地在 [xml] 中轉換字串將透過解析器程式碼運行它並產生完整的 xml 樹。 我們可以在程式碼中使用類別來達到相同的目的。

投射哈希表

如果您沒有建構函數,則可以透過將雜湊表轉換為類別類型來繼續沒有建構函數。 不要忘記使用驗證屬性來充分利用此模式。 同時,我們可以使用類別的類型化屬性來運行更深層的初始化和驗證邏輯。

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
}

此外,鑄造有助於獲得乾淨的輸出。 將傳遞給 Format-Table 的 Cluster 雜湊表數組的輸出與首先將這些雜湊表轉換為類別時所得到的結果進行比較。 類別的屬性始終按照它們在其中定義的順序列出。 不要忘記在您不希望在結果中可見的所有屬性之前添加隱藏關鍵字。

帶有類別的功能性 Powershell 並不是一個矛盾的說法,我保證這一點

意義的轉換

如果您有一個帶有一個參數的建構函數,則將值轉換為類別類型會將值傳遞給建構函數,您可以在建構函數中初始化類別的實例

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"

投射到線路

您也可以重寫 [string] ToString() 類別方法來定義物件字串表示背後的邏輯,例如使用字串插值。

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

強制轉換序列化實例

強制轉換允許安全的反序列化。 如果資料不符合我們在 Cluster 中的規範,下面的範例將會失敗

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

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

在你的函數程式碼中進行強制型別轉換

函數式程式首先定義資料結構,然後將程式實作為對不可變資料結構的一系列轉換。 儘管給人的印像是矛盾的,但由於類型轉換方法,類別確實可以幫助您編寫功能程式碼。

我正在編寫的 Powershell 可以運作嗎?

許多來自 C# 或類似背景的人都在寫 Powershell,它與 C# 類似。 透過這樣做,您將不再使用函數式程式設計概念,並且可能會從深入研究 Powershell 中的物件導向程式設計或了解有關函數式程式設計的更多資訊中受益。

如果您嚴重依賴使用管道(|)、Where-Object、ForEach-Object、Select-Object、Group-Object、Sort-Object 等來轉換不可變資料- 您將擁有更實用的風格,並且將從使用Powershell 中受益函數式風格的類別。

類別的功能使用

種姓雖然使用替代語法,但只是兩個域之間的映射。 在管道中,您可以使用 ForEach-Object 來對應值陣列。

在下面的範例中,每次轉換 Datum 時都會執行 Node 建構函數,這使我們有機會不必編寫大量程式碼。 因此,我們的管道專注於聲明性資料查詢和聚合,而我們的類別則負責資料解析和驗證。

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

重複使用的包裝等級

一切都不像看起來那麼美好

不幸的是,類別不能像函數或變數一樣被模組導出; 但也有一些技巧。 假設您的類別在檔案 ./my-classes.ps1 中定義

  • 您可以使用類別來點原始檔:。 ./我的班級.ps1。 這將在目前範圍內執行 my-classes.ps1 並定義該檔案中的所有類別。

  • 您可以建立一個匯出所有自訂 API (cmdlet) 的 Powershell 模組,並在模組清單中設定 ScriptsToProcess = "./my-classes.ps1" 變量,得到相同的結果: ./my-classes.ps1 將在你的環境。

無論您選擇哪個選項,請記住,Powershell 的類型系統無法解析從不同位置載入的同名類型。
即使您從不同的地方加載了兩個具有相同屬性的相同類,您也可能會遇到問題。

前進的道路

避免類型解析問題的最佳方法是永遠不要向使用者公開您的類別。 不要期望使用者匯入類別定義的類型,而是從模組中匯出函數,這樣就無需直接存取該類別。 對於 Cluster,我們可以匯出一個 New-Cluster 函數,該函數將支援使用者友善的參數集並傳回一個 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

還有什麼要讀的

關於課程
防禦性PowerShell
PowerShell 中的函數式編程

來源: www.habr.com

添加評論