A continuación se muestra un ejemplo de cómo aplicar el principio OCP en C#.
Tratamos de implementar una clase que se encarge de sanitizar la entrada de un usuario a través de un campo de texto (quitar tags html, etc.)
Una primera aproximación sería lo siguiente:
class SanitizeInputUser
{
public string stripHTML( string inputString )
{
string result = inputString;
List<string> patternsToRemove = new List<string>() { "<h1.*?>", "", "<h2.*?>", "" };
foreach ( string pattern in patternsToRemove )
{
result = Regex.Replace(result, pattern, string.Empty);
}
return result;
}
}
El código cliente sería algo así:
SanitizeInputUser s = new SanitizeInputUser();
string str = "<h1 class='main-header'>Sample header</h1><script>var foo = 'sample';</script>";
Console.WriteLine(s.stripHTML(str));
Sin embargo, esta clase, aún haciendo justo lo que queremos, viola el principio OCP, porque:
- Si queremos agregar más filtros, tenemos que modificar la clase (OCP = abierto para la extensión, cerrado para los cambios).
- Si en el futuro queremos agregar un método de sanitizar la cadena algo más sofisticado, tendríamos que cambiar en profundidad el método stripHTML()
Una mejor implementación sería la siguiente:
public interface ISanitizer
{
string sanitize(string input);
}
public class SanitizerBasicHTags : ISanitizer
{
public string sanitize(string input)
{
string result = input;
List<string> patternsToRemove = new List<string>() { "<h1.*?>", "</h1>;", "<h2.*?>", "</h2>" };
foreach (string pattern in patternsToRemove)
{
result = Regex.Replace(result, pattern, string.Empty);
}
return result;
}
}
public class SanitizeScriptTags : ISanitizer
{
public string sanitize(string input)
{
string result = input;
string pattern = "<script.*?>*</script>";
return( Regex.Replace(result, pattern, string.Empty) );
}
public class SanitizeInputUser
{
List<ISanitizer> _sanitizers;
public SanitizeInputUser(List<ISanitizer> sanitizers)
{
_sanitizers = sanitizers;
}
public string SanitizeString( string input )
{
string result = input;
foreach (ISanitizer iSanitizer in _sanitizers )
{
result = iSanitizer.sanitize(result);
}
return result;
}
}
De este modo, el código cliente sería algo así:
List<ISanitizer> sanitizers = new List<ISanitize>() {
new SanitizerBasicHTags(),
new SanitizeScriptTags() };
string str = "<h1 class='main-header'>Sample header</h1><script>var foo = 'sample';</script>";
SanitizeInputUser siu = new SanitizeInputUser(sanitizers);
Console.WriteLine(siu.SanitizeString(str));
Puede parecer que escribimos más código, y así es, pero hemos implementado una clase que recibe un número indeterminado de sanitizadores y si mañana los requisitos de nuestra aplicación cambian, podemos añadir nuevos sin modificar SanitizeInputuser.
Además, con esta nueva implementación, también cumplimos el principio ISP (Interface Segregation Principle) así como DIP (Dependency Inversion Principle)
No hay que obviar que con esta nueva implementación conseguimos una estructura de la aplicación más granular que permitirá hacer tests con más facilidad.