Estrategias de código portable : #1 PCL - Portable Class Library | C#
Intermedio
Artículos relacionados
Estrategias de código portable : #2 Código Vinculado | C#
Estrategias de código portable : #3 Universal Apps | C#
C#, Visual Studio y herramientas como Xamarin nos permiten utilizar y aprovechar mucho mejor nuestro código a traves de las diferentes plataformas de Apps.
- iOS
- Android
- Windows Phone
- Windows 8 [WinRT]
Como developer debes tener las habilidades necesarias para sacar provecho a todas estas herramientas, por ello he creado esta serie de artículos junto con algunos videos (coming soon...) que estoy seguro te van a mostrar que tipo de magia es capaz de hacer un programador con las herramientas correctas y algo de creatividad.
Código fuente de esta serie de artículos
El código fuente completo de esta seria de artículos se encuentra disponible en GitHub, incluye proyectos de Apps con todos los casos expuestos.
https://github.com/JuanKRuiz/Estrategias-de-Codigo-Portable
App de prueba
Nuestra App de prueba para WinRT y Windows Phone,lo único que hace es que al cargar la primera pantalla almacena la fecha y hora de la última ejecución.
Los proyectos de la solución están de la siguiente forma:
- Libreria PCL : Windows 8.1 + Windows Phone [Silverlight] 8.1
- Windows 8.1
- Windows Phone [Silverlight] 8.1
Al cargar la Page
principal programamos el evento Load
y allí hacemos la lógica necesaria para guardar en los settings.
A continuación veremos la versión más simple de programar este requerimiento en cada una de las plataformas:
Código WinRT
//LocalSettingsManager.cs
using System.Diagnostics;
using Windows.Storage;
namespace WinRTApp.Manager
{
public class LocalSettingsManager
{
public void Set(string key, string value)
{
ValidateSecurity();
ApplicationData.Current.LocalSettings.Values[key] = value;
Audit();
}
public void ValidateSecurity()
{
//TODO a lot of things
}
public void Audit()
{
Debug.WriteLine("Parameter Set on WinRT");
//TODO a lot of things
}
}
}
//MainPage.xaml.cs [fragment]
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var lsm = new LocalSettingsManager();
lsm.Set("Last start:", DateTime.Now.ToString());
}
Código Windows Phone
//LocalSettingsManager.cs
using System.Diagnostics;
using System.IO.IsolatedStorage;
namespace WinPhoneApp.Manager
{
public class LocalSettingsManager
{
public void Set(string key, string value)
{
ValidateSecurity();
IsolatedStorageSettings.ApplicationSettings[key] = value;
Audit();
}
public void ValidateSecurity()
{
//TODO a lot of things
}
public void Audit()
{
Debug.WriteLine("Parameter Set on WindowsPhone");
//TODO a lot of things
}
}
}
//MainPage.xaml.cs [fragment]
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
var lsm = new LocalSettingsManager();
lsm.Set("Last start:", DateTime.Now.ToString());
}
Output
Parameter Set on WinRT
Parameter Set on WindowsPhone
Código redundante
Como pueden apreciar en la solución tenemos una buena cantidad de código redundante.
Prácticamente toda la clase LocalSettingsManager
esta duplicada con excepción de la línea de código que guarda los settings y la línea con el mensaje de auditoria.
Observen que tenemos otros dos métodos
- Audit
- ValidateSecurity
Y esto es porque en el mundo real guardar estos settings puede involucrar decenas o cientos de líneas de código más relacionados con temas de seguridad o lógia de negocio.
Para poner solución a ese problema les enseñaré diferentes estrategias de código portable con PCL.
Librerias Portables
PCL: Portable Class Library
Una libreria portable es una libreria cuyo código y dependencias externas pueden ser llevadas a diversas plataformas sin perder funcionalidad.
Técnicamente no necesitas nada especial para hacer una PCL, pero tendrías que tu mismo estar pendiente de que funcionalidades están disponibles en cada una de las plataformas que deseas soportar, esto de por si puede ser bastante complicado y muy propenso a cometer errores que te pueden costar luego muchísimas horas de trabajo.
Por suerte Visual Studio incorpora una funcionalidad para crear PCL, y el mismo se encarga de controlar que clases puedes utilizar según las plataformas que decidas soportar desde un comienzo.
Escoger proyecto PCL
Establecer las plataformas a soportar
Cuidado con lo que deseas
Intuitivamente uno trataria de crear una PCL que soporte todas las plataformas y es perfectamente, pero tiene un problema.
Entre más plataformas decidas soportar menos cosas en común tendrán entre ellas por ende el código que puedes compartir fácilmente es mucho menor así como la cantidad de librerías compatibles.
Una vez se ha creado el proyecto es tiempo de programar, sin embargo crear un proyecto de libreria portable es fácil solo cuando el 100% del código será utilizado en las diferentes plataformas, pero por lo general este no es el caso y es por ello que revisaremos estrategias de programaciónpara reutilizar tanto codigo como sea posible.
Método 1: Patrón Estrategia
Este patrón esta basado principalmente en la inyección de comportamientos, por lo cual es una solución extremamente flexible pero también más exigente en cuanto diseño de la lógica.
Básicamente se debe programar contra interfaces para que luego se haga una composición con esas interfaces como si estas tuviesen toda la lógica necesaria, realmente la lógica se podría inclusive cambiar en tiempo de ejecución simplemente asignando un objeto distinto a la interfaz.
Lo primero que hacemos es encapsular lo que cambia, que en este caso es:
- Guardar los settings
- Mensaje de la plataforma
Dado que en ambas plataformas esto se hace diferente encapsulamos esta lógica en una interfaz ISettingsWriter
, dentro del LocalSettingsManager
modificamos la lógica para que esta funcione contra cualquier objeto que exponga dicha interfaz, e 'inyectamos' el comportamiento deseado desde el constructor.
Esta es la implementación en la PCL, como ven la lógica completa del componente está creada, pero la parte que cambia no ha sido implementada.
Código PCL
//ISettingsWriter.cs
namespace PCL_S.Manager
{
public interface ISettingsWriter
{
void Set(string key, string value);
string Message { get; }
}
}
//LocalSettingsManager.cs
using System.Diagnostics;
namespace PCL_S.Manager
{
public class LocalSettingsManager
{
public ISettingsWriter Writer { get; set; }
public LocalSettingsManager(ISettingsWriter writer)
{
Writer = writer;
}
public void Set(string key, string value)
{
ValidateSecurity();
Writer.Set(key, value);
Audit();
}
public void ValidateSecurity()
{
//TODO a lot of things
}
public void Audit()
{
Debug.WriteLine("Parameter Set on {0}", Writer.Message);
//TODO a lot of things
}
}
}
Ahora en cada uno de los proyectos agregamos una referencia a la PCL creada, y en cada uno creamos un objecto que implemente la interfaz ISettingsWriter
con la lógica requerida en cada caso.
Así mismo modificamos el evento Load
del Page
para que invoque el LocalSettingsManager
con el objeto ISettingsWriter
correspondiente en cada caso.
Código WinRT
//WinRTSettingsWriter.cs
using PCL_S.Manager;
using Windows.Storage;
namespace WinRTS.Manager
{
class WinRTSettingsWriter : ISettingsWriter
{
public void Set(string key, string value)
{
ApplicationData.Current.LocalSettings.Values[key] = value;
}
public string Message
{
get { return "WinRT - Strategy"; }
}
}
}
//MainPage.xaml.cs [fragment]
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var writer = new WinRTSettingsWriter();
var lsm = new LocalSettingsManager(writer);
lsm.Set("Last start", DateTime.Now.ToString());
}
Código Windows Phone
//PhoneSettingsWriter.cs
using PCL_S.Manager;
using System.IO.IsolatedStorage;
namespace WinPhoneS.Manager
{
class PhoneSettingsWriter : ISettingsWriter
{
public void Set(string key, string value)
{
IsolatedStorageSettings.ApplicationSettings[key]=value;
}
public string Message
{
get
{ return "Windows Phone - Strategy"; }
}
}
}
//MainPage.xaml.cs [fragment]
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
var writer = new PhoneSettingsWriter();
var lsm = new LocalSettingsManager(writer);
lsm.Set("Last start",DateTime.Now.ToString());
}
Output
Parameter Set on WinRT - Strategy
Parameter Set on Windows Phone - Strategy
Método 2: Patrón Método de Plantilla
La finalidad de este patrón es encapsular un algoritmo sin que este dependa explícitamente de la lógica interna de cada paso.
Otra forma de verlo es pensar en una receta, en algún momento de la receta te pedirán calentar la preparación, pero dependiendo del contexto a veces lo harás con una estufa, a veces on un horno o a veces con un micro ondas, incluso a veces con una vela o una cobija... no debería importar pues los pasos del algoritmo no dependen de la implementación de "Calentar" .
Como lo hemos anotado anteriormente las partes de la lógica que cambian son:
- Guardar los settings
- Mensaje de la plataforma
Por lo que podemos crear una clase abstracta que haga todo lo que se requiere y en estas partes específicas se llamen a métodos que no han sido implementados aún pero que se espera que sean implementados por las clases que hereden de la clase abstracta.
Hay dos mecanismos para lograr esto
- Método | Propiedad
abstract
: El método debe ser implementado por la clase que herede - Método | Propiedad
virtual
: El método no necesariamente debe ser implementado por la clase que herede, ya que hay una implementación por defecto.
Haremos uso de ambas opciones solo con fines didácticos.
Código PCL
//LocalSettingsManager.cs
using System.Diagnostics;
namespace PCL_T.Manager
{
public abstract class LocalSettingsManager
{
public void Set(string key, string value)
{
ValidateSecurity();
WriteSetting(key, value);
Audit();
}
public void ValidateSecurity()
{
//TODO a lot of things
}
public void Audit()
{
Debug.WriteLine("Parameter Set on {0}", this.Message);
//TODO a lot of things
}
public abstract void WriteSetting(string key, string value);
public virtual string Message { get{return "[Generic]";} }
}
}
Ahora en cada uno de los proyectos agregamos una referencia a la PCL creada, y en cada uno creamos un objecto que herede de la clase abstracta LocalSettingsManager
implementando la lógica requerida en cada caso.
Así mismo modificamos el evento Load
del Page
para que invoque el LocalSettingsManager
correspondiente de cada plataforma.
Código WinRT
//WinRTLocalSettingsManager.cs
using PCL_T.Manager;
using Windows.Storage;
namespace WinRTAppT.Manager
{
class WinRTLocalSettingsManager: LocalSettingsManager
{
public override void WriteSetting(string key, string value)
{
ApplicationData.Current.LocalSettings.Values[key] = value;
}
public override string Message
{ get { return "WinRT - Template Method"; } }
}
}
//MainPage.xaml.cs [fragment]
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var lsm = new WinRTLocalSettingsManager();
lsm.Set("Last start",DateTime.Now.ToString());
}
Código Windows Phone
En esta implementación intencionalmente he dejado comentadas dos versiones de la propiedad Message
con el fin de que ustedes exploren los diferentes resultados
- Al dejarlos comentados
- Al dejar la primera implementación
- Al dejar la segunda implementación
//WinPhoneLocalSettingsManager.cs
using PCL_T.Manager;
using System.IO.IsolatedStorage;
namespace WinPhoneAppT.Manager
{
class WinPhoneLocalSettingsManager : LocalSettingsManager
{
public override void WriteSetting(string key, string value)
{
IsolatedStorageSettings.ApplicationSettings[key] = value;
}
//public override string Message
//{ get { return "Windows Phone - Template Method"; } }
//public override string Message
//{ get { return base.Message + ": Windows Phone - Template Method"; } }
}
}
//MainPage.xaml.cs [fragment]
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
var lsm = new WinPhoneLocalSettingsManager();
lsm.Set("Last start", DateTime.Now.ToString());
}
Output
Parameter Set on WinRT - Template Method
Parameter Set on [Generic]
Conclusiones
Las PCL nos brindan una oportunidad importante de optimización de nuestro código, pero debemos ser ingeniosos en la construcción del código y en mayor medida debemos ser capaces de detectar oportunidades de refactoring para que nuestra PCL evolucione de la manera adecuada.
En cuanto a los patrones, podemos decir que ambas soluciones son ingeniosas y nos permiten maximizar la cantidad de código reutilizable, en este ejemplo fue evidente la conveniencia de utilizar Template Method ya que la cantidad de esfuerzo requerido para la creación es notablemente menor que con Estrategy. Sin embargo en muchos casos de la vida real se requiere la mayor flexibilidad del patrón estragia, ya que al trabajar por composición nos es mucho más fácil extender y encapsular las funcionalidades.