Olá, Habr! Apresento a sua atenção uma tradução do artigo
Eu prometo que não é um oxímoro"
Os paradigmas de programação funcional e orientado a objetos podem parecer conflitantes, mas ambos são igualmente suportados no Powershell. Quase todas as linguagens de programação, funcionais ou não, possuem recursos para ligação estendida de nome-valor; Classes, como estruturas e registros, são apenas uma abordagem. Se limitarmos nosso uso de Classes à ligação de nomes e valores e evitarmos conceitos pesados de programação orientada a objetos, como herança, polimorfismo ou mutabilidade, poderemos tirar vantagem deles sem complicar nosso código. Além disso, ao adicionar métodos de conversão de tipo imutáveis, podemos enriquecer nosso código funcional com Classes.
A magia das castas
Castas são um dos recursos mais poderosos do Powershell. Ao converter um valor, você depende dos recursos implícitos de inicialização e validação que o ambiente adiciona ao seu aplicativo. Por exemplo, simplesmente converter uma string em [xml] irá executá-la através do código do analisador e gerar uma árvore xml completa. Podemos usar Classes em nosso código para a mesma finalidade.
Transmitir hashtables
Se você não tiver um construtor, poderá continuar sem ele convertendo uma tabela hash para o seu tipo de classe. Não se esqueça de usar os atributos de validação para aproveitar ao máximo esse padrão. Ao mesmo tempo, podemos usar as propriedades digitadas da classe para executar uma inicialização e uma lógica de validação ainda mais profundas.
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
}
Além disso, a transmissão ajuda a obter uma saída limpa. Compare a saída da matriz hashtable Cluster passada para Format-Table com o que você obtém se primeiro converter essas tabelas hash em uma classe. As propriedades de uma classe são sempre listadas na ordem em que são definidas. Não se esqueça de adicionar a palavra-chave oculta antes de todas as propriedades que você não deseja que fiquem visíveis nos resultados.
Elenco de significados
Se você tiver um construtor com um argumento, converter um valor para o seu tipo de classe passará o valor para o seu construtor, onde você poderá inicializar uma instância da sua classe
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"
Transmitir para linha
Você também pode substituir o método de classe [string] ToString() para definir a lógica por trás da representação de string do objeto, como usar interpolação de string.
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'"
Transmitir instâncias serializadas
Cast permite desserialização segura. Os exemplos abaixo falharão se os dados não atenderem à nossa especificação no Cluster
# Валидация сериализованных данных
[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"
Castas em seu código funcional
Os programas funcionais primeiro definem estruturas de dados e depois implementam o programa como uma sequência de transformações em estruturas de dados imutáveis. Apesar da impressão contraditória, as classes realmente ajudam você a escrever código funcional graças aos métodos de conversão de tipo.
O Powershell que estou escrevendo está funcional?
Muitas pessoas que vêm de C# ou com formação semelhante estão escrevendo Powershell, que é semelhante ao C#. Ao fazer isso, você está deixando de usar conceitos de programação funcional e provavelmente se beneficiaria se se aprofundasse na programação orientada a objetos no Powershell ou aprendesse mais sobre programação funcional.
Se você depende muito da transformação de dados imutáveis usando pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc. - você tem um estilo mais funcional e se beneficiará com o uso do Powershell aulas em um estilo funcional.
Uso funcional de classes
As castas, embora utilizem uma sintaxe alternativa, são apenas um mapeamento entre dois domínios. No pipeline, você pode mapear uma matriz de valores usando ForEach-Object.
No exemplo abaixo, o construtor Node é executado toda vez que um Datum é lançado, e isso nos dá a oportunidade de não escrever uma quantidade razoável de código. Como resultado, nosso pipeline se concentra na consulta e agregação de dados declarativos, enquanto nossas classes cuidam da análise e validação dos dados.
# Пример комбинирования классов с конвейерами для 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
Classe de embalagem para reutilização
Nada é tão bom quanto parece
Infelizmente, as classes não podem ser exportadas por módulos da mesma forma que funções ou variáveis; mas existem alguns truques. Digamos que suas classes estejam definidas no arquivo ./my-classes.ps1
-
Você pode dotsource um arquivo com classes:. ./minhas-classes.ps1. Isso executará my-classes.ps1 em seu escopo atual e definirá todas as classes do arquivo lá.
-
Você pode criar um módulo Powershell que exporta todas as suas APIs personalizadas (cmdlets) e definir a variável ScriptsToProcess = "./my-classes.ps1" no manifesto do seu módulo, com o mesmo resultado: ./my-classes.ps1 será executado em seu ambiente.
Qualquer que seja a opção escolhida, lembre-se de que o sistema de tipos do Powershell não pode resolver tipos com o mesmo nome carregados de locais diferentes.
Mesmo se você carregar duas classes idênticas com as mesmas propriedades de locais diferentes, você corre o risco de ter problemas.
Caminho a seguir
A melhor maneira de evitar problemas de resolução de tipos é nunca expor suas classes aos usuários. Em vez de esperar que o usuário importe um tipo definido pela classe, exporte uma função do seu módulo que elimine a necessidade de acessar a classe diretamente. Para Cluster, podemos exportar uma função New-Cluster que oferecerá suporte a conjuntos de parâmetros fáceis de usar e retornará um 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
O que mais ler
Fonte: habr.com