A continuación se muestra un sencillo ejemplo en C# del principio LSP. Se pretende implementar una pequeña jerarquía de clases para guardar mensajes de log bien a un fichero de texto bien a una base de datos.
En una primera aproximación, podríamos pensar en algo así:
public class BasicMessage
{
string _msg;
DateTime _dateTime;
public string Msg
{
set {
_msg = value;
_dateTime = DateTime.Now;
}
get { return _msg; }
}
public string FormatMessage()
{
return string.Format("Set time at: {0} - Message: {1}", _dateTime, _msg);
}
}
class TextLogMessage : BasicMessage
{
public void WriteMessageToLog()
{
//...
}
}
class DataBaseLogMessage : BasicMessage
{
public void WriteMessageToDataBase()
{
//...
}
}
De este modo, si tenemos una función que quiera escribir los mensajes iniciales al comenzar la ejecución de la aplicación al fichero de texto, tendría el siguiente prototipo:
void writeInitialLogs( TextLogMessage msg )
{
log.Msg = "Application up&running";
log.WriteMessageToLog();
}
Y si se quisiera guardar en su lugar los mensajes en la base de datos, la función debería ser así:
void writeInitialLogs( DataBaseLogMessage msg )
{
log.Msg = "Application up&running";
log.WriteMessageToDataBase();
}
Pero..., esta jerarquía de ejemplo viola LSP, porque en nuestro código cliente (la función writeInitialLogs), no se puede usar indistintamente como parámetro TextLogMessage o DataBaseLogMessage, que son las clases hijas de BasicMessage. De este modo estamos implementando las writeInitialLogs() con rigidez, ligado a una solución concreta en la forma de guardar el mensaje de log.
Una versión correcta de esta jerarquía que cumpliría LSP sería la siguiente:
public class BasicMessage
{
string _msg;
DateTime _dateTime;
public string Msg
{
set
{
_msg = value;
_dateTime = DateTime.Now;
}
get { return _msg; }
}
public string FormatMessage()
{
return string.Format("Set time at: {0} - Message: {1}", _dateTime, _msg);
}
public virtual void WriteMessage()
{
}
}
class TextLogMessage : BasicMessage
{
public override void WriteMessage()
{
//...
}
}
class DataBaseLogMessage : BasicMessage
{
public override void WriteMessage()
{
//...
}
}
De este modo, la función writeInitialLogs() se podría reescribir como
void writeInitialLogs( BasicMessage msg )
{
log.Msg = "Application up&running";
log.WriteMessage();
}
Y se invocaría como
writeInitialLogs( new TextLogMessage() );
O bien
writeInitialLogs( new DataBaseLogMessage() );
De este modo:
- La función writeInitialLogs() queda desacoplada de una implementación concreta de BasicMessage.
- Se cumple el principio LSP ya que writeInitialLogs() puede usar indistintamente todas las clases hija de BasicMessage y su funcionamiento será válido.
- Se cumple además OCP, ya que hemos diseñado BasicMessage abierto para la extensión pero cerrada a las modificaciones.
- Lo dejamos todo listo y preparado para el principio de inversión de dependencias o DI, ya que la instancia del objeto que recibe writeInitialLogs() como parámetro se puede establecer como un mecanismo general a la aplicación.