Функционални Поверсхелл са класама није оксиморон, гарантујем

Здраво, Хабр! Представљам вашој пажњи превод чланка „Функционални ПоверСхелл са класама.
Обећавам да то није оксиморон"
од Цхристопхер Куецх.

Објектно оријентисана и функционална програмска парадигма могу изгледати у супротности једна са другом, али су обе подједнако подржане у Поверсхелл-у. Скоро сви програмски језици, функционални или не, имају могућности за проширено везивање име-вредност; Класе, као што су структуре и записи, су само један приступ. Ако ограничимо нашу употребу класа на везивање имена и вредности и избегнемо тешке концепте објектно оријентисаног програмирања као што су наслеђивање, полиморфизам или променљивост, можемо их искористити без компликовања нашег кода. Даље, додавањем непроменљивих метода конверзије типа, можемо обогатити наш функционални код са класама.

Магија касти

Касте су једна од најмоћнијих карактеристика у Поверсхелл-у. Када баците вредност, ослањате се на имплицитне могућности иницијализације и валидације које окружење додаје вашој апликацији. На пример, једноставно пребацивање стринга у [кмл] ће га покренути кроз код парсера и генерисати комплетно кмл стабло. У исту сврху можемо користити класе у нашем коду.

Цаст хасхтаблес

Ако немате конструктор, можете да наставите без њега тако што ћете убацити хеш-табелу на свој тип класе. Не заборавите да користите атрибуте валидације да бисте у потпуности искористили овај образац. У исто време, можемо да користимо откуцана својства класе да бисмо покренули још дубљу логику иницијализације и валидације.

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
}

Поред тога, ливење помаже да се добије чист резултат. Упоредите излаз низа хасхтабле Цлустер прослеђених у Формат-Табле са оним што добијете ако прво баците ове хеш табеле у класу. Својства класе су увек наведена оним редоследом којим су тамо дефинисана. Не заборавите да додате скривену кључну реч испред свих оних својстава која не желите да буду видљива у резултатима.

Функционални Поверсхелл са класама није оксиморон, гарантујем

Цаст оф значења

Ако имате конструктор са једним аргументом, пребацивање вредности у ваш тип класе ће проследити вредност вашем конструктору, где можете иницијализовати инстанцу ваше класе

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"

Пребаците на линију

Такође можете заменити метод класе [стринг] ТоСтринг() да бисте дефинисали логику која стоји иза стринг репрезентације објекта, као што је коришћење интерполације стрингова.

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 = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"

Касте у вашем функционалном коду

Функционални програми прво дефинишу структуре података, а затим имплементирају програм као низ трансформација над непроменљивим структурама података. Упркос контрадикторном утиску, класе вам заиста помажу у писању функционалног кода захваљујући методама конверзије типова.

Да ли је Поверсхелл који пишем функционалан?

Многи људи који долазе из Ц# или сличне позадине пишу Поверсхелл, што је слично Ц#. Чинећи ово, удаљавате се од коришћења концепта функционалног програмирања и вероватно бисте имали користи од зарона у објектно оријентисано програмирање у Поверсхелл-у или учења више о функционалном програмирању.

Ако се у великој мери ослањате на трансформацију непроменљивих података помоћу цевовода (|), Вхере-Објецт, ФорЕацх-Објецт, Селецт-Објецт, Гроуп-Објецт, Сорт-Објецт, итд. - имате функционалнији стил и имаћете користи од коришћења Поверсхелл-а наставу у функционалном стилу.

Функционална употреба часова

Касте, иако користе алтернативну синтаксу, само су мапирање између два домена. У цевоводу, можете мапирати низ вредности помоћу ФорЕацх-Објецт.

У примеру испод, конструктор чвора се извршава сваки пут када се датум избацује, а то нам даје прилику да избегнемо писање велике количине кода. Као резултат тога, наш цевовод се фокусира на декларативно испитивање података и агрегацију, док се наше класе брину за рашчлањивање и валидацију података.

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

Класа паковања за поновну употребу

Ништа није тако добро као што изгледа

Нажалост, модули не могу експортовати класе на исти начин као функције или променљиве; али постоје неки трикови. Рецимо да су ваше класе дефинисане у датотеци ./ми-цлассес.пс1

  • Можете дотсоурце фајл са класама:. ./ми-цлассес.пс1. Ово ће извршити ми-цлассес.пс1 у вашем тренутном опсегу и дефинисати све класе из датотеке тамо.

  • Можете креирати Поверсхелл модул који извози све ваше прилагођене АПИ-је (цмдлете) и поставити променљиву СцриптсТоПроцесс = "./ми-цлассес.пс1" у манифесту вашег модула, са истим резултатом: ./ми-цлассес.пс1 ће се извршити у ваше окружење.

Коју год опцију да одаберете, имајте на уму да Поверсхелл-ов систем типова не може да разреши типове истог имена учитане са различитих места.
Чак и ако сте учитали две идентичне класе са истим својствима са различитих места, ризикујете да наиђете на проблеме.

Пут напред

Најбољи начин да избегнете проблеме са резолуцијом типова је да никада не излажете своје класе корисницима. Уместо да очекујете да корисник увезе тип дефинисан класом, извезите функцију из вашег модула која елиминише потребу за директним приступом класи. За Цлустер, можемо да извеземо функцију Нев-Цлустер која ће подржати скупове параметара прилагођене кориснику и вратити Цлустер.

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

Шта још читати

Абоут Цлассес
Дефенсиве ПоверСхелл
Функционално програмирање у ПоверСхелл-у

Извор: ввв.хабр.цом

Додај коментар