Hola Habr! Les presento la traducción del artículo "PowerShell funcional con clases".
Prometo que no es un oxímoron " de Christopher Kuech.
Los paradigmas de programación orientados a objetos y funcionales pueden parecer opuestos entre sí, pero ambos son igualmente compatibles con Powershell. Casi todos los lenguajes de programación, funcionales y no, tienen medios de enlace extendido de nombres y valores; Las clases, como las estructuras y los registros, son solo un enfoque. Si restringimos el uso de Clases a nombres y valores y evitamos conceptos de programación orientados a objetos "pesados" como herencia, polimorfismo o mutabilidad, podemos aprovecharlos sin complicar nuestro código. Además, agregando métodos inmutables de conversión de tipos, podemos enriquecer nuestro código funcional con clases.
Magia de casta
Castes es una de las características más poderosas de Powershell. Cuando emite un valor, confía en la capacidad del entorno para agregar inicialización y validación implícitas a su aplicación. Por ejemplo, una simple conversión de una cadena en [xml] lo ejecutará a través del código del analizador y generará un árbol xml completo. Podemos usar las clases en nuestro código para el mismo propósito.
Tablas hash personalizadas
Si no tiene un constructor, puede continuar sin él utilizando una tabla hash para el tipo de su clase. Recuerde utilizar los atributos de validación para aprovechar al máximo este patrón. Al mismo tiempo, podemos usar las propiedades escritas de la clase para activar una lógica de inicialización y validación aún más profunda.
class Cluster { [ValidatePattern("^[Az]+$")] [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 }
Además, el elenco ayuda a llegar a una conclusión limpia. Compare el resultado de la matriz de tabla hash Cluster pasada a Format-Table con lo que sucede si primero convierte estas tablas hash en una clase. Las propiedades de clase siempre se enumeran en el orden en que se definen allí. No olvide agregar la palabra clave oculta frente a todas aquellas propiedades que no deberían estar visibles en la salida.

Valores personalizados
Si tiene un constructor con un argumento, emitir un valor a su tipo de clase pasará el valor a este constructor en el que puede inicializar la instancia de su clase
class Cluster { [ValidatePattern("^[Az]+$")] [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"
Línea personalizada
También puede anular el método de clase [string] ToString () para determinar la lógica de la representación de cadena del objeto, por ejemplo, utilizando la interpolación de cadena.
class Cluster { [ValidatePattern("^[Az]+$")] [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'"
Instancias serializadas personalizadas
El molde permite una deserialización segura. Los siguientes ejemplos fallarán si los datos no cumplen con nuestra especificación en Cluster
Castas en su código de función
Los programas funcionales primero definen estructuras de datos, luego implementan el programa como una secuencia de transformaciones sobre estructuras de datos inmutables. A pesar de la impresión conflictiva, las clases realmente ayudan a escribir código funcional gracias a los métodos de conversión de tipos.
¿Powershell es funcional?
Muchas personas que vienen de C # o con un pasado similar escriben Powershell, que es similar a C #. Al hacerlo, es reacio a utilizar los conceptos de programación funcional y es probable que se beneficie de estar muy inmerso en la programación orientada a objetos en Powershell, o mejor aprendiendo programación funcional.
Si confía en gran medida en la transformación de datos inmutables usando tuberías (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc., tiene un estilo y uso más funcional Clases de Powershell en estilo funcional.
Uso funcional de clases.
Las castas, aunque usan una sintaxis alternativa, son solo mapeos entre dos dominios. En la tubería, puede asignar una matriz de valores utilizando ForEach-Object.
En el siguiente ejemplo, el constructor de Nodo se ejecuta cada vez que se produce una conversión a Datum, y esto nos permite no escribir una buena cantidad de código. Como resultado, nuestra cartera se centra en la consulta y agregación de datos declarativos, mientras que nuestras clases se encargan del análisis y validación de datos.
Reutilizar envases de clase
Nada es tan bueno como parece
Desafortunadamente, las clases no pueden exportarse por módulos de la misma manera que las funciones o variables; Pero hay algunos trucos. Digamos que sus clases están definidas en el archivo ./my-classes.ps1
Puede dosificar un archivo con clases:. ./my-classes.ps1. Esto ejecutará my-classes.ps1 en su alcance actual y definirá allí todas las clases del archivo.
Puede crear un módulo Powershell que exporta todas sus API de usuario (cmdlets) y establecer la variable ScriptsToProcess = "./my-classes.ps1" en el manifiesto de su módulo, con el mismo resultado: ./my-classes.ps1 se ejecutará en su entorno .
Cualquiera que sea la opción que elija, recuerde que el sistema de tipos Powershell no puede resolver los tipos del mismo nombre cargados desde diferentes lugares.
Incluso si descarga dos clases idénticas con las mismas propiedades desde diferentes lugares, corre el riesgo de tener problemas.
Camino a seguir
La mejor manera de evitar problemas de resolución de tipo es nunca exponer sus clases a los usuarios. En lugar de esperar que el usuario importe un tipo definido en la clase, exporte desde su módulo una función que elimine la necesidad de acceder directamente a la clase. Para Cluster, podemos exportar la función New-Cluster, que admitirá conjuntos de parámetros fáciles de usar y devolver Cluster.
class Cluster { [ValidatePattern("^[Az]+$")] [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
Que mas leer
Sobre clases
PowerShell defensivo
Programación funcional en PowerShell