Chức năng Powershell với các lớp không phải là một phép nghịch hợp, tôi đảm bảo điều đó

Này Habr! Tôi trình bày với bạn chú ý bản dịch của bài báo "PowerShell chức năng với các lớp.
Tôi hứa đó không phải là một oxymoron"
của Christopher Kuech.

Các mô hình lập trình hướng đối tượng và lập trình chức năng có vẻ mâu thuẫn với nhau, nhưng cả hai đều được hỗ trợ như nhau trong Powershell. Hầu hết tất cả các ngôn ngữ lập trình, dù có chức năng hay không, đều có tiện ích liên kết tên-giá trị mở rộng; Các lớp, như cấu trúc và bản ghi, chỉ là một cách tiếp cận. Nếu chúng ta giới hạn việc sử dụng Lớp ở mức ràng buộc về tên và giá trị, đồng thời tránh các khái niệm nặng nề về lập trình hướng đối tượng như tính kế thừa, đa hình hoặc khả năng biến đổi, thì chúng ta có thể tận dụng lợi ích của chúng mà không làm phức tạp mã của mình. Hơn nữa, bằng cách thêm các phương thức chuyển đổi loại không thay đổi, chúng ta có thể làm phong phú mã chức năng của mình bằng Lớp.

Sự kỳ diệu của đẳng cấp

Castes là một trong những tính năng mạnh mẽ nhất trong Powershell. Khi truyền một giá trị, bạn đang dựa vào khả năng khởi tạo và xác thực tiềm ẩn mà môi trường thêm vào ứng dụng của bạn. Ví dụ: chỉ cần truyền một chuỗi vào [xml] sẽ chạy chuỗi đó thông qua mã trình phân tích cú pháp và tạo ra một cây xml hoàn chỉnh. Chúng ta có thể sử dụng Lớp trong mã của mình cho cùng một mục đích.

Truyền bảng băm

Nếu bạn không có hàm tạo, bạn có thể tiếp tục mà không cần hàm tạo bằng cách truyền một hàm băm cho loại lớp của mình. Đừng quên sử dụng các thuộc tính xác thực để tận dụng tối đa mẫu này. Đồng thời, chúng ta có thể sử dụng các thuộc tính được định kiểu của lớp để chạy logic xác thực và khởi tạo sâu hơn nữa.

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
}

Ngoài ra, việc đúc giúp có được đầu ra sạch sẽ. So sánh kết quả đầu ra của mảng bảng băm Cụm được chuyển đến Bảng định dạng với kết quả bạn nhận được nếu lần đầu tiên chuyển các bảng băm này vào một lớp. Các thuộc tính của một lớp luôn được liệt kê theo thứ tự chúng được định nghĩa ở đó. Đừng quên thêm từ khóa ẩn trước tất cả các thuộc tính mà bạn không muốn hiển thị trong kết quả.

Chức năng Powershell với các lớp không phải là một phép nghịch hợp, tôi đảm bảo điều đó

Dàn ý nghĩa

Nếu bạn có một hàm tạo với một đối số, việc truyền một giá trị cho loại lớp của bạn sẽ chuyển giá trị đó cho hàm tạo của bạn, nơi bạn có thể khởi tạo một thể hiện của lớp

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"

Truyền tới dòng

Bạn cũng có thể ghi đè phương thức lớp [string] ToString() để xác định logic đằng sau cách biểu diễn chuỗi của đối tượng, chẳng hạn như sử dụng phép nội suy chuỗi.

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

Truyền các phiên bản nối tiếp

Cast cho phép khử lưu huỳnh an toàn. Các ví dụ bên dưới sẽ thất bại nếu dữ liệu không đáp ứng đặc tả của chúng tôi trong Cụm

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

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

Diễn viên trong mã chức năng của bạn

Các chương trình chức năng trước tiên xác định cấu trúc dữ liệu, sau đó triển khai chương trình dưới dạng một chuỗi các phép biến đổi trên các cấu trúc dữ liệu bất biến. Mặc dù có ấn tượng trái ngược nhau nhưng các lớp thực sự giúp bạn viết mã chức năng nhờ các phương thức chuyển đổi kiểu.

Powershell tôi đang viết có hoạt động không?

Rất nhiều người đến từ C# hoặc có nền tảng tương tự đang viết Powershell, tương tự như C#. Bằng cách này, bạn sẽ ngừng sử dụng các khái niệm lập trình hàm và có thể sẽ được hưởng lợi từ việc đi sâu vào lập trình hướng đối tượng trong Powershell hoặc tìm hiểu thêm về lập trình hàm.

Nếu bạn phụ thuộc nhiều vào việc chuyển đổi dữ liệu bất biến bằng cách sử dụng các đường ống (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, v.v. - bạn có phong cách chức năng hơn và bạn sẽ được hưởng lợi từ việc sử dụng Powershell các lớp theo phong cách chức năng.

Chức năng sử dụng của các lớp

Các đẳng cấp, mặc dù sử dụng cú pháp thay thế, nhưng chỉ là sự ánh xạ giữa hai miền. Trong quy trình, bạn có thể ánh xạ một mảng giá trị bằng ForEach-Object.

Trong ví dụ bên dưới, hàm tạo Node được thực thi mỗi khi Datum được truyền và điều này cho chúng ta cơ hội tránh phải viết một lượng mã khá lớn. Do đó, quy trình của chúng tôi tập trung vào truy vấn và tổng hợp dữ liệu khai báo, trong khi các lớp của chúng tôi đảm nhiệm việc phân tích cú pháp và xác thực dữ liệu.

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

Lớp đóng gói để tái sử dụng

Không có gì tốt như nó có vẻ

Thật không may, các lớp không thể được xuất theo mô-đun giống như các hàm hoặc biến; nhưng có một số thủ thuật. Giả sử các lớp của bạn được xác định trong tệp ./my-classes.ps1

  • Bạn có thể chấm nguồn một tệp có các lớp:. ./my-classes.ps1. Điều này sẽ thực thi my-classes.ps1 trong phạm vi hiện tại của bạn và xác định tất cả các lớp từ tệp ở đó.

  • Bạn có thể tạo mô-đun Powershell xuất tất cả các API (lệnh ghép ngắn) tùy chỉnh của mình và đặt biến ScriptsToProcess = "./my-classes.ps1" trong bảng kê khai mô-đun của bạn, với kết quả tương tự: ./my-classes.ps1 sẽ thực thi trong môi trường của bạn.

Cho dù bạn chọn tùy chọn nào, hãy nhớ rằng hệ thống loại của Powershell không thể giải quyết các loại có cùng tên được tải từ các vị trí khác nhau.
Ngay cả khi bạn tải hai lớp giống hệt nhau có cùng thuộc tính từ những nơi khác nhau, bạn vẫn có nguy cơ gặp phải sự cố.

Con đường phía trước

Cách tốt nhất để tránh các vấn đề về phân giải kiểu là không bao giờ để lộ lớp của bạn cho người dùng. Thay vì mong đợi người dùng nhập một loại do lớp xác định, hãy xuất một hàm từ mô-đun của bạn để loại bỏ nhu cầu truy cập trực tiếp vào lớp đó. Đối với Cụm, chúng ta có thể xuất hàm New-Cluster sẽ hỗ trợ các bộ tham số thân thiện với người dùng và trả về một Cụm.

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

Còn gì để đọc

Giới thiệu về lớp học
PowerShell phòng thủ
Lập trình chức năng trong PowerShell

Nguồn: www.habr.com

Thêm một lời nhận xét