Прывітанне, Хабр! Уяўляю вашай увазе пераклад артыкула
I promise it's не an oxymoron"
Аб'ектна-арыентаваная і функцыянальная парадыгмы праграмавання могуць здавацца не ў ладах сябар з сябрам, але абедзве ў роўнай меры падтрымліваюцца ў Powershell. Практычна ўсе праграмныя мовы, функцыянальныя і не, маюць сродкі пашыранага звязвання імён і значэнняў; Класы, падобна struct-ам і record-ам, гэта ўсяго толькі адзін падыход. Калі мы абмяжуем выкарыстанне Класаў звязваннем імён і значэнняў і станем пазбягаць такіх "цяжкіх" аб'ектна-арыентаваных праграмных канцэпцый, як атрыманне ў спадчыну, палімарфізм, або змяняльнасць (mutability), мы зможам выкарыстоўваць іх перавагі, не ўскладняючы наш код. Далей, дадаючы нязменныя (immutable) метады пераўтварэнні тыпаў, мы можам узбагаціць Класамі наш функцыянальны код.
Магія кастаў
Касты адна з самых магутных фіч у 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
}
Акрамя таго, каст дапамагае атрымаць чыстую выснову. Параўнайце выснову масіва хэштабліц Cluster перададзены ў Format-Table c тым, што атрымаецца, калі перш каставаць гэтыя хэштабліцы ў клас. Уласцівасці класа заўсёды пералічваюцца ў тым парадку, у якім яны тамака вызначаныя. Не забудзьцеся дадаць ключавое слова hidden перад усімі тымі ўласцівасцямі, якія не павінны быць бачныя ў выдачы.
Каст значэнняў
Калі ў вас ёсць канструктар з адным аргументам, каст значэння да вашага тыпу класа перадасць значэнне гэтаму вашаму канструктару, у якім вы можаце ініцыялізаваць інстанс вашага класа
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, які падобны на З#. Паступаючы так, вы адмаўляецеся ад выкарыстання канцэпцый функцыянальнага праграмавання і, верагодна, атрымаеце карысць узмоцнена пагрузіўшыся ў аб'ектна-арыентаванае праграмаванне ў Powershell ці лепш вывучыўшы функцыянальнае праграмаванне.
Калі вы глыбока належыце на трансфармацыю імутабельных дадзеных, выкарыстоўваючы канвееры (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object і т. д. - у вас больш функцыянальны стыль, і вам дапаможа выкарыстанне класаў Powershell у функцыянальным стылі.
Функцыянальнае выкарыстанне класаў
Касты, хоць і выкарыстоўваюць альтэрнатыўны сінтаксіс, усяго толькі мапінг паміж двума даменамі. У канвееры можна змапіць масіў значэнняў, выкарыстоўваючы ForEach-Object.
У прыкладзе ніжэй канструктар Node выконваецца кожны раз калі адбываецца касты да Datum, і гэта дае нам магчымасць не пісаць ладную колькасць кода. У выніку наш канвеер факусуюць на дэкларатыўным запыце дадзеных і агрэгацыі, у той час як нашы класы бяруць на сябе парсінг дадзеных і валідацыю.
# Пример комбинирования классов с конвейерами для 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
-
Вы можаце зрабіць датсорсінг файла з класамі:. ./my-classes.ps1. Гэта выканае my-classes.ps1 у вашай бягучай вобласці бачнасці і вызначыць тамака ўсе класы з файла.
-
Вы можаце стварыць модуль Powershell, які экспартуе ўсе вашы карыстацкія API (камандлеты) і ўсталяваць зменную 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
Што яшчэ пачытаць
Крыніца: habr.com