Encapsulación Usada En Programación Como Patrón De Diseño

encapsulation

Antes de nada vamos hablar sobre la definición de la encapsulación aplicada a la programación, si echamos un vistazo en algún medio conocido por todos como Wikipedia dice lo siguiente:

En programación modular, y más específicamente en programación orientada a objetos, se denomina Encapsulamiento al ocultamiento del estado, es decir, de los datos miembro de un objeto de manera que sólo se pueda cambiar mediante las operaciones definidas para ese objeto.

Cada objeto está aislado del exterior, es un módulo natural, y la aplicación entera se reduce a un agregado o rompecabezas de objetos. El aislamiento protege a los datos asociados de un objeto contra su modificación por quien no tenga derecho a acceder a ellos, eliminando efectos secundarios e interacciones.

De esta forma el usuario de la clase puede obviar la implementación de los métodos y propiedades para concentrarse sólo en cómo usarlos. Por otro lado se evita que el usuario pueda cambiar su estado de maneras imprevistas e incontroladas.

Por tanto, después de esta definición entendemos que deberíamos aplicar la encapsulación para componentes reusables pudiéndolo llamar Framework, Toolkit… generalmente se usa para proyectos cerrados que tienen una serie de objetivos concretos, por ejemplo, todos los Nuget Packages que podemos encontrarnos en el repositorio oficial aplican este principio y también en cualquier desarrollo móvil donde conectamos normalmente con una API que necesitamos sea segura y fácil de trabajar con ella.

Ejemplo de código sin encapsulación

A continuación, vamos a mostrar un claro ejemplo donde no se ha aplicado la encapsulación, con esta base transformaremos un código inicial mal codificado en una clase encapsulada:

encapsulation-1

Al ver este ejemplo claramente vemos que los métodos que se incluyen en una clase no son entendibles por sí solos, no sabemos por qué devuelven datos de un tipo String o por qué se ha creado un método o una propiedad. Por lo tanto, este es el motivo del por qué de la encapsulación, hay que programar para principiantes, es decir, especificar cada elemento para alguien que no tiene ningún tipo de información sobre las especificaciones de tu implementación.

¿Cómo aplicariamos la encapsulación y para qué?

“Long term productivity” es un concepto que popularmente se conoce como el tiempo que gastamos a la hora de programar, y dentro de este intervalo de tiempo se sabe que gastamos hasta 10-20 veces más leyendo código que escribiéndolo. Esto resulta bastante impactante si se lo presentamos así a nuestro jefe de proyecto, pero de esta forma seguro que conseguiremos que recapacite y estime un sobrecoste por cada funcionalidad / requerimiento que nos pida el cliente con mótivo del encapsulamiento en la programación.

En conclusión, volvemos al punto anterior de que el código que implementemos debe ser entendido por cualquier programador, independientemente del nivel que tenga, por tanto podemos concluir con que

El código debe ser entendido por sí solo, sin necesidad de realizar un soporte para documentación especialmente extenso.

Definiciones Aplicadas a la Programación Orientada a Objetos (OO)

Punto de vista clásico

Dentro de esta definición podemos identificar 2 elementos imprescindibles de abordar, por un lado tenemos:

  • Information hiding: Nos referimos con este término a la información o implementación del código que mostramos al cliente con el fin de aclarar dudas sobre su uso parámetros a pasar a esa caja negra llama “Encapsulamiento”. Es decir, qué propiedades u objetos son públicos los cuáles están a la vista de todo el mundo, y sobre todo cuáles de ellos no incumben al cliente tratándolos como privados.
  • Protection of invariants: Consiste básicamente en comprobar las pre-condiciones a la hora de realizar una llamada a la API (podemos tomarlo como la implementación encapsulada) que garantizan un buen resultado cumpliendo por lo tanto las directrices de las post-condiciones. Tanto las pre como las post-condiciones (conocidas como “assertions” según Bertrand Meyer) son reglas que identifican los estados válidos e inválidos del objeto facilitado, cuya finalidad es la de garantizar que los estados inválidos son imposibles de darse a cabo.

Punto de vista alternativo

Aquí nos encontramos con un par de principios en OO más concretos y accionables, que hacen de la encapsulación algo mucho más tangible desde el punto de vista del programador:

Command Query Separation (CQS – Bertrand)

Este principio según Bertrand, dice que las operaciones identificadas en nuestra clase deberían ser comandos (Commands) o consultas (Queries), pero nunca ambas. Para diferenciar ambos conceptos vamos a entrar un poco en detalle mostrando un ejemplo muy claro y conciso:

  • Commands: Para identificar qué métodos son de este tipo debemos fijarnos en un par de cosas, por un lado nunca devuelven ningún tipo de dato (void) y por otro siempre causan efectos colaterales en su ejecución, es decir, implícitamente se realizan otras acciones que afectan al entorno del propio objeto que alberga el método llamado.
  • Queries: En cambio, en este caso sucede exactamente lo contrario, por un lado siempre devolvemos datos ya que estamos pidiendo a través de la consulta ciertos resultados y por otro existe una base que es la siguiente: “Da igual el número de veces que ejecutes una query, siempre deberá devolverte resultados idénticos válidos” lo cual podemos definir con los términos “Idempotence” y “Safe to invoke”.

Como podemos ver a continuación, hemos refactorizado parte del código mostrado anteriormente, convirtiendo en Commands y Queries cada método, lo cuál hace que sea más fácil de entender para un programador las pre-condiciones y los resultados esperados en cada caso:

encapsulation-2

 

Por supuesto, algo muy importante que tratar es la comunicación con el equipo de desarrollo, siempre debemos llegar a un acuerdo sobre (en el caso de refactorizaciones) qué módulos / funcionalidades queremos migrar a CQS, y en caso afirmativo necesitamos acordar de qué forma exactamente vamos a trabajar conjuntamente.

Postel’s Law (Principio de robustez)

Con la siguiente frase podemos resumir en consiste este principio:

“Sé muy conservador en lo que tú mandas, pero deberías ser muy flexible en lo que aceptas o consideras cómo válido”

Es decir, los inputs que aceptas deben estar muy restringidos pero los datos que recibes deben ser lo más abiertos posible. Si lo vemos en términos de desarrollo, nosotros cuando acordamos algo con el cliente debemos ceñirnos a esto (WCF contract / firma del método) para obtener los datos que esperamos.

Input (pre-condiciones)

Aquí hablamos de la posibilidad de hacer fallar al sistema si los inputs tienen un tipo / contenido erróneo que permite la compilación del mismo pero un error inevitable en la ejecución del sistema. Con lo cuál se habla de pre-condiciones para que no se genere un estado inválido de la instancia, algunos ejemplos pueden ser:

  1. Evitar un incorrecto estado de inicialización de clases
  2. Obligar a rellenar esos datos requeridos en el constructor con parámetros válidos
  3. Colocar el SET de las propiedades como “private” para evitar rellenarlas directamente, y así no poder evitar la instancia de la propia clase (constructor)
  4. Creación de trazas de error (throw) para detectar excepciones y evitar procesar datos que generen estados inválidos.

Todavía tenemos un problema a este nivel, son los tipos de dato Nullables lo cuál provoca excepciones del tipo ArgumentNullException.

Para ello existe el principio de Fail Fast,  que es la forma de validar pre-condiciones para que el parámetro se considere válido, es decir,  cuando tenemos que verificar otras posibles excepciones como que el dato que me pasas es correcto en cuanto a tipología pero su contenido es incorrecto, lo cuál genera excepción y por ello debemos controlarla con ArgumentException pasando un mensaje lo suficientemente explicativo para otros desarrolladores explicando lo que sucede, así se podrá corregir ese error. Tal y como vemos a continuación con la propiedad SourcePath:

encapsulation-3

Output (post-condiciones)

El diseño de nuestras queries… porque CUANDO HAY OUTPUT es query… debe ser siempre 100% fiable sobre el tipo que devuelven si siempre será ese, es decir, un caso práctico es que nunca sería viable utilizar NULL como dato devuelto válido ya que no es el tipo que dijimos y podemos hacer que no seamos fiables para el cliente que consume nuestra librería. Por lo tanto, existen 3 técnicas eficientes para resolver este problema:

  • Tester/Doer: Consiste en crear un método query que devuelva un valor booleano y así asegurarnos de que los valores serán consistentes y nunca tendremos NULL como resultado, el problema que tenemos aquí es que cuando están actuando diferentes threads a la vez puede que ese resultado ya quede obsoleto y no sea válido.
encapsulation-4
Creamos un método que verifique si nuestro input es válido
encapsulation-5
Llamamos a este método antes de realizar la operación de lectura
  •  TryRead: Aquí hacemos algo parecido, donde en lugar de devolver el tipo tal cual de la query, lo convertimos en una query que devuelva booleano y así hacer la función atómica para evitar el problema explicado anteriormente, devolviendo el resultado de la query real como un parámetro OUT. Aquí el problema que tenemos es que nos salimos de la convención a la hora de implementar queries, devolver resultados a través de parámetros de entrada hace que sigamos el “interface fluent”… lo cual no es recomendable para un diseño OO de una API.

 

encapsulation-6
Creamos un método con parámetro de entrada/salida y devolvemos booleano

 

encapsulation-7
Aquí devolvemos siempre un valor válido en message cuando exists es True
  • Maybe (la mejor estrategia para saber si el output es legal o no – EVITAR NULL como return value): Es la creación de una clase llamada Maybe donde hereda de IEnumerable<string> para asegurar que nuestro resultado tendrá 0 o 1 elementos, en función de sí contiene o no un resultado válido. Esta función es muy de F# programación funcional.
Creamos la clase Maybe que hereda de IEnumerable
Creamos la clase Maybe que hereda de IEnumerable
Utilizamos dicha clase para utilizarlo como dato de salida (0-1 elementos)
Utilizamos dicha clase para utilizarlo como dato de salida (0-1 elementos)

CONCLUSIÓN

La encapsulación es una forma de programar que hace la vida de los desarrolladores más fácil, no solo la de los demás sino la tuya propia, ya que en un futuro cuando tengas que volver a esa funcionalidad encapsulada podrás ver exactamente que es lo que implementa. Mantenibilidad y Productividad son conceptos que deben tenerse en cuenta en cualquier desarrollo de SW (Fácil compresión a la hora de leer si escribes para desarrolladores principiantes).

Como apunte, decir que es muy importante no llevar la encapsulación hasta su punto más extremo, basicamente porque los tiempos de desarrollo se irían totalmente en la planificación del proyecto.

 

C# 5.0: Nuevas características

Esta “nueva” versión (lanzada en Agosto del 2012) de C# incluido en .NET Framework 4.5, incluye algunas novedades que voy a mencionar a continuación, pero que se basa fundamentalmente en la implementación del “asincronismo” de una forma mucho más sencilla que con sus predecesores, ya que a la hora de trabajar con Multithreading no deberemos tirar muchas más líneas de código con respecto a la programación síncrona/secuencial de toda la vida.

Evolución de C#

“Async” o asincronismo más sencillo

Como he comentado anteriormente, esta característica representa el groso de la nueva versión que Microsoft publicó con respecto a su lenguaje de programación estrella, C# 5.0.

La API para programar asíncronamente es sencilla de entender, ya que todos aquellos que hayan estudiado un poco sobre el tema enseguida le sacarán parecido a otras implementaciones de lenguajes de programación como JAVA, C, NodeJS…

En el apartado de programación asíncrona, vamos a manejar un ejemplo sencillo con la clase WebClient, que nos permitirá descargar un documento de manera asíncrona dándole al usuario la posibilidad de poder realizar otro tipo de acciones mientras esta tarea se resuelve, además me parece muy interesante porque no solo se puede aplicar a aplicaciones de escritorio (WinForms), sino que también es aplicable a Web (ya sea WebForms o MVC).

Esto para las aplicaciones de escritorio es un problema muy común, ya que al tardar demasiado tiempo Windows se impacienta y notifica que la aplicación “no responde”, por tanto se bloquea y damos una imagen al usuario bastante pobre de nuestro nivel como programadores.

API Síncrono: WebClient.DownloadString

API Asíncrono: WebClient.DownloadStringAsync

Con el uso de este método solventamos el problema de bloqueo de procesos, de forma transparente para el programador y dotar así al usuario de una mayor flexibilidad a la hora de interaccionar con nuestra aplicación.

Muy bien, ya tenemos una solución a nuestro problema de bloqueo de procesos, pero ahora nos surge otro problema, no sabemos cuando se finaliza ese proceso al que estamos esperando, y por tanto ejecutarlo en el hilo/proceso correspondiente para evitar posibles excepciones que nos pueda indicar nuestra pila con un mensaje esclarecedor de que estamos en una ejecución fuera de contexto.

WebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(WebClient_DownloadStringCallback);
public void WebClient_DownloadStringCallback (object sender, DownloadStringCompletedEventArgs e)
{
   this.txtResult.Text = e.Result;
}

Para solucionar el problema explicado anteriormente, tendremos un manejador de eventos dedicado especialmente para rescatar el resultado de la llamada asíncrona que hemos realizado, así de una forma muy sencilla podremos acceder al hilo que quedó a la espera de la descarga del documento que solicitamos previamente. Más adelante os mostraré los hilos de ejecución que tenemos disponibles y así hacernos una idea por donde transcurre el programa.

Una vez entendemos cómo realizar una llamada asíncrona, vamos a ver como se hacía antes y como lo haríamos ahora en .NET. En el caso del código de servidor existe una clase llamada SynchronizationContext que soluciona este problema en versiones anteriores (nos ayuda a manejar las requests” y las “responses” pertenecientes a un mismo contexto/hilo) funciona con ASP.NET, WPF, Win Forms…

Antes de C# 5.0

A continuación veremos con un ejemplo sencillo, cómo obtener de forma asíncrona los headers presentes actualmente en una web HttpWebRequest, si queremos obtener solamente los headers incluimos en:  HttpWebRequest.Method = “HEAD”  y a continuación implementamos el siguiente código:

HttpWebRequest.BeginGetResponse(
      asyncResult => {
            var resp = (HttpWebResponse) HttpWebRequest.EndGetResponse(asyncResult);
            string headersText = FormatHeaders (resp.Headers);
            this.txtResult.Text = headersText;
      },
      null);

Como vemos, para la llamada BeginGetResponse debemos llamar a un delegado que será el encargado de continuar con la ejecución una vez se ha completado la “request”, en este caso la obtención de los headers y almacenarlos en una caja de texto. También cabe mencionar que como último parámetro es posible incluir cierta información arbitraria, dicha información podría servirnos como añadido a nuestra llamada asíncrona y utilizarla para tal efecto.

En este primer ejemplo, se verá como obtenemos una excepción ya que intentamos asignar los headers de la respuesta a la caja de texto en un hilo distinto al que deberíamos, Main Thread sería el encargado de hacer esta tarea y en cambio es otro hilo secundario el que lo hace…

Threads Asíncronismo
Threads Asíncronismo

Por lo tanto, para solucionar este problema debemos utilizar la clase SynchronizationContext para poder asignar en su delegado dentro del evento BeginGetResponse el resultado a la caja de texto, a continuación veríamos como queda el código:

  var sync = SynchronizationContext.Current;
  HttpWebRequest.BeginGetResponse(
        asyncResult => {
               var resp = (HttpWebResponse) HttpWebRequest.EndGetResponse(asyncResult);
               string headersText = FormatHeaders (resp.Headers);
                   sync.Post(
                        delegate {
                             this.txtResult.Text = headersText;
                        },
                   null);
        },
  null);

Con este código tenemos el comportamiento normal de Multithreading, donde el hilo padre continúa su ejecución de forma normal hasta que finaliza (queda dormido, esperando a sus hijos, en este caso asyncResult), esto lo hacemos gracias al método Post que contiene la clase SynchronizationContext, tal y como entendemos por su nombre se encarga de capturar la finalización de la petición en su hilo correspondiente (padre) para terminar la ejecución del mismo, de esta forma podemos evitar posibles errores de contexto en la ejecución de nuestro programa.

Nota: Para aquellos que tengan un poquito más de conocimiento sobre el tema, deciros que ya no peligra el famoso estado de “inanición” de nuestros procesos gracias a la implementación del asincronismo facilitado por .NET Framework 4.0.

Resumiendo, antes con C# 4.0 era necesario implementar hasta 3 diferentes métodos para el correcto funcionamiento del asincronismo, eso sí de forma muy sencilla y aplicando el patrón de eventos asíncronos, comúnmente conocido como EAP ya disponible en la versión 2.0 de .NET.

Ahora con C# 5.0

Para empezar a trabajar con Async debemos añadir una nueva referencia a nuestro proyecto: AsyncCtpLibrary.dll localizada en ..\Microsoft Visual Studio Async CTP\Samples\. Ahora vamos a pasar a la implementación del asincronismo en unos pocos pasos muy sencillos de codificar:

1.  Declarar nuestros eventos asíncronos:

protected async void btnConfirmar_Click(object sender, RoutedEventArgs e)
{
}

Así cualquier desarrollador que retome este código sabrá que este método se utilizará de forma asíncrona en nuestra aplicación, podemos asemejarlo a la inclusión de los atributos [HttpGet] o [HttpPost] en la cabecera de las acciones MVC, indicando que tipo de peticiones atiende nuestro método. Pues aquí lo mismo, pero asociado al tipo de ejecución que se llevará a cabo (síncrono o asíncrono).

2.  Adaptar el código visto anteriormente para que nuestro compilador pueda atenderlo de forma asíncrona:

protected async void btnConfirmar_Click(object sender, RoutedEventArgs e)
{
     WebClient w = new WebClient();
     string txt = await w.DownloadStringTaskAsync("Mi url");
     this.txtResult.Text = txt;
}

La keyword “await” indica al compilador que esta sentencia será un trabajo a desempeñar de forma asíncrona y por tanto deberá tenerlo en cuenta en el momento de ejecución.

Como comentamos anteriormente, esto es muy útil cuando tenemos ciertas peticiones que no se pueden tener esperando más tiempo de lo normal o simplemente nos pueda devolver un 404/timeout, en ese período de tiempo el usuario podrá hacer más cosas mientras espera el resultado del click del botón.

Por tanto, await está preparado para funcionar con TPL (Task Parallel Library) que ya fue incluido en C# 4.0 donde  WebClient.DownloadStringTaskAsync devuelve un System.Threading.Task<string>. Pero claro en el caso de HttpWebRequest o HttpWebResponse no podemos trabajar con TPL tal cual, sino que debemos utilizar el modelo de programación asíncrona mediante la creación de tareas. Esto lo podemos ver en el siguiente ejemplo:

private async void btnConfirmar_Click(object sender, RoutedEventArgs e)
{
    var req = (HttpWebRequest) WebRequest.Create("mi url");
    req.Method = "HEAD";
    Task<WebResponse> getResponseTask = Task.Factory.FromAsync<WebResponse>(req.BeginGetResponse, req.EndGetResponse, null);
    var resp = (HttpWebResponse) await getResponseTask;
    string headersText = FormatHeaders (resp.Headers);
    this.txtResult.Text = headersText;
}

Como vemos en el código anterior,  HttpWebRequest no contiene una sobrecarga de algún método que podamos ejecutar de forma asíncrona (como WebClient), con lo cual debemos utilizar el TaskFactory para crear una tarea que luego posteriormente será tratada como asíncrona con la palabra reservada “await“, para hacer esto siempre debemos indicar la ejecución del código asíncrono y el método que recogerá la finalización de dicha ejecución:

csharp-5-async-3

El único requisito que debemos tener en cuenta, es el tipo de datos que devolvemos para posteriormente tratarlo en nuestro callback de la llamada asíncrona, es decir, si nuestra instancia Task que debe gestionar TaskFactory es de tipo string también debemos pasarle este mismo tipo de datos para no obtener ninguna excepción de “casting“.

Conclusion: C# 5.0 pretende que el asincronismo a nivel de código sea igual de simple que la implementación del sincronismo.

Caller Information (Herramientas de diagnosis y seguimiento)

Otra de las principales novedades que trae la nueva versión de C#, es la posibilidad de usar de forma nativa multitud de herramientas de diagnosis y seguimiento, sin tener que depender de aplicaciones/esamblados “third-party”para solucionar problemas tan cotidianos como la depuración, traceo y la creación de herramientas de diagnosis.

Esto nos ahorra (en el caso de ser un equipo de varios desarrolladores) duplicidad en el código, ya que puede pasar que cada utilice su propio sistema de traceo, depuración de errores… duplicando al fin y al cabo la misma funcionalidad.

Por tanto, Microsoft nos facilita las siguientes clases con información útil para este propósito:

Un ejemplo de como utilizar estos nuevos atributos lo podemos ver a continuación:

public void TracingMethod()
{
   TraceException("ERROR!!");
}

public void TraceException(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
    Trace.WriteLine("Mensaje de error: " + message);
    Trace.WriteLine("Nombre del método: " + memberName);
    Trace.WriteLine("Path del fichero: " + sourceFilePath);
    Trace.WriteLine("Línea de error: " + sourceLineNumber);
}
//Salida:
//  Mensaje de error: ERROR!!
// Nombre del método: TracingMethod
//  Path del fichero: C:\...\Visual Studio 2012\Projects\...\Tracing\CallerInformation.cs
//  Línea de error: 3

Como podéis ver con este método sencillo de implementar, tenemos cubierto cualquier petición de tracing o depuración de errores en nuestra aplicación.

Bueno pues hasta aquí este artículo dedicado especialmente al lenguaje de programación que yo utilizo diariamente, como vemos Microsoft sigue aportando valor a este excelente lenguaje de programación de alto nivel.