Vous êtes sur la page 1sur 19

PRINCIPIO ABIERTO/CERRADO

Este principio afirma que una clase debe estar abierta a extensin pero cerrada a
modificaciones. Esto lo que viene a decir es que nuestra aplicacin debera estar
diseada de manera que ante peticiones de cambio en la aplicacin, deberamos
ser capaces de aadir funcionalidad sin modificar la existente (siempre que sea
posible).

Por lo general, en un mal diseo, modificar una funcionalidad durante el ciclo de


vida, suele conllevar una cadena de cambios en mdulos dependientes, y este
efecto puede propagarse en cascada si el cdigo est muy acoplado.

Como normal general, la forma ms habitual de conseguir cumplir el principio OCP


es usar interfaces o clases abstractas de las que dependen implementaciones
concretas. De esta forma puede cambiarse la implementacin de la clase concreta
mantenindose la interfaz intacta.

Para comprender mejor este principio vamos a ejemplificarlo por medio de un


diseo.

Diagrama clases Explicacin del Principio Abierto/Cerrado

El diagrama anterior es un ejemplo de una clase que rompe con el principio


abierto/cerrado (OCP). Vamos a suponer un sistema de gestin de proyectos, de tal
manera nos vamos a centrar en la entidad Tarea y vamos a descubrir como rompe
con el principio abierto/cerrado.

Como se puede observar en las relaciones dicha clase viene determinando por uno
de los estados de la enumeracin EstadoTarea (Pendiente, Finalizada, Cancelada),
adems se puede observar que la clase implementa 3 mtodos, Iniciar, Cancelar y
Finalizar que cambian si es posible el estado de la tarea. Veamos cmo sera una
posible implementacin del mtodo Finalizar.

Violacin al Principio de Abierto/Cerrado

Al parecer esta implementacin no est nada mal, de hecho no est nada mal si no
se agregarn nuevos estados, pero como comentamos al inicio, todas las
aplicaciones cambian durante su ciclo de vida.

Supongamos entonces un nuevo requerimiento o cambio tpico solicitado por el


cliente de la aplicacin, el cual sera la adicin de un nuevo estado para controlar
las tareas que se han Propuesto, con lo que la implementacin de este mtodo
podra ser:
Violacin al Principio de Abierto/Cerrado

Aparentemente, parece una modificacin trivial pero este cambio puede involucrar
muchos otros, en los mtodos y/o clases donde se utilice el EstadoTarea, no
olvidemos que este nuevo cambio nos hara modificar tambin la implementacin
del mtodo Cancelar.

Violacin al Principio de Abierto/Cerrado

Vemos claramente, por cada nuevo estado que implementemos tendremos que
identificar la implementacin para todas las clases que lo utilizan y modificarlas,
violando el principio Abierto/Cerrado (OCP).
A continuacin planteamos una solucin a la violacin del principio utilizando el
patrn State.

Solucin a violacin del Principio Abierto/Cerrado

Bsicamente, lo que se ha hecho es crear una clase por cada estado en lugar de
tener una nica clase cuyos mtodos estn basados en sentencias condicionadas
por el estado de la tarea. Adems, con esta nueva implementacin se ha delegado
la responsabilidad de finalizar, cancelar o posponer a una nueva clase
BaseEstadoTarea que que se ha marcado como abstracta. La clase Tarea
implementar sus propios mtodos y delegar la responsabilidad a travs de las
clases TareasEstados que heredan de BaseEstadoTarea. Debido a que la clase
Tarea gira en torno a un estado, asumimos que el estado inicial por defecto es
Pendiente, y as se deber especificar en el constructor, instanciando a
TareaEstadoPendiente.

Vamos a ver como seria la implementacin para esta solucin.


Implementacin de la Solucin con el patrn State
Ante un nuevo requisito en el que intervenga un nuevo estado, lo nico que se
deber hacer es crear una nueva clase que herede de BaseEstadoTarea e
implementar los mtodos virtuales, extendiendo as el comportamiento de la
aplicacin sin comprometer el cdigo existente.

PRINCIPIO DE SUSTITUCION DE LISKOV

Pese a lo complejo que pueda llegar a parecer la definicin en la prctica lo


podemos ver con algo ms de claridad con el tpico ejemplo del cuadrado y el
rectngulo. En el siguiente snippet de cdigo en C# se muestran las clases.

public abstract class Rectangulo

public virtual int Ancho { get; set; }

public virtual int Alto { get; set; }

public abstract float Area();

public class Cuadrado : Rectangulo

public override int Ancho {

get {

return base.Ancho;
}

set {

base.Ancho = value;

base.Alto = value;

public override int Alto {

get {

return base.Alto;

set {

base.Ancho = value;

base.Alto = value;

public override float Area()

return Ancho*Alto;

Aparentemente a simple vista puede parecernos que la herencia es correcta, al


menos desde el punto de vista tcnico su implementacin lo es, sin embargo el
comportamiento es bien distinto. Mientras que en un Rectangulo el Ancho y el Alto
pueden contener cualquier valor, un Cuadrado, por definicin, el Ancho y el Alto
debe ser del mismo valor, de otra forma seria no seria un cuadrado. Fijmonos en
el setter de las propiedades Ancho y Alto de la clase Cuadrado; ambas se anulan
entre s. Podemos verlo ms claro con un Test Unitario.

[TestFixture]

public class Test

[Test]

public void AreaRectangulo()

Rectangulo r = new Cuadrado

Ancho = 5,

Alto = 2

};

Assert.AreEqual(10.0f, r.Area());

Basicamente comprobamos que, si un objecto Cuadrado con Ancho = 5 y Alto = 2,


su Area, ya que asumimos que es un Rectangulo, es Ancho * Alto, es decir igual 10.
El Test falla pues el setter de Alto impone adems de ste el Ancho a 2, con lo cual
el Area es 4.
Diseo por Contratos

Por otro lado, Bertrand Meyer acu el Diseo por Contratos (DbC) en el que se
especifica un contrato a nivel de mtodo para establecer los valores/tipos
aceptables de entrada y de retorno, las condiciones de valor o de tipo para los
errores y excepciones que puedan ocurrir, la precondicin y pos condicin y las
invariantes.

Para cumplir con el Principio de Substitucin de Liskov, la implementacin de la


clase derivada debe:

Ser menos restrictivas en la precondicin.

Ser ms restrictivas en el pos condicin.

Preservar la invariancia.

En nuestro ejemplo podramos decir que el contrato de la


propiedad Ancho de Rectngulo seria:

[Precondicion(Ancho > 0)]

[Poscondicion("$after(Ancho)>0")]

[Poscondicion("$after(Alto)=$before(Alto)")]

Y el de Cuadrado:

[Precondicion(Ancho > 0)]

[Poscondicion("$after(Ancho)>0")]
En primer lugar, la propia naturaleza de la clase Cuadrado implica, precisamente
para preservar la Invariancia, que la propiedad Ancho modifique, adems, la
propiedad Alto y esto deriva en que la postcondicin de Cuadrado para el setter sea
menos restrictiva que en Rectangulo y por tanto no cumpla con El Principio de
Substitucin de Liskov

PRINCIPIO DE SEGREGACIN DE INTERFACES

El Principio de Segregacin de Interfaces fue utilizado por primera vez por Robert
C. Martin durante unas sesiones de consultora en Xerox. Por aquella poca, Xerox
estaba diseando una impresora multifuncional. El software diseado para la
impresora funcionaba y se adaptaba perfectamente a las necesidades iniciales de
la impresora; sin embargo, conforme fue evolucionando, y por lo tanto cambiando,
se hizo cada vez ms difcil de mantener. Cualquier modificacin tena un gran
impacto global sobre el sistema. La utilizacin de ISP permiti reducir los riesgos de
las modificaciones y otorg una mayor facilidad al mantenimiento. El ISP declara
que:

Los clientes no deben ser forzosamente dependientes de las interfaces que no


utilizan.

A continuacin veremos la utilidad de ISP y su significado, una vez que


identifiquemos qu es una interfaz "pesada" y qu problemas lleva asociados.

Interfaces "pesadas"

Observemos el diagrama de clases de la figura 1. Bsicamente, consta de dos


modelos de impresoras representadas por las clases Modelo1998 y Modelo2000,
ambas herederas de la clase abstracta ImpresoraMultifuncional.
Inicialmente, la clase abstracta ImpresoraMultifuncional declaraba los mtodos
correspondientes a las funciones tpicas que realiza una impresora multifuncional,
como son la propia impresin, el escaneado, el envo de fax y la cancelacin de
cualquier operacin. La impresora Modelo1998 fue el primer modelo en basarse en
esta interfaz; poco despus se aadi un nuevo modelo, Modelo2000, que adems
de las funciones anteriores aada la posibilidad de hacer fotocopias.

Posteriormente, surgi un nuevo modelo (Modelo2002) que se basaba en la misma


clase abstracta ImpresoraMultifuncional e incorporaba el soporte para
comunicaciones TCP/IP en lugar del servicio de fax; este modelo permita enviar un
documento directamente por correo electrnico, evitando as los altos costes de
telefona. El problema se presenta al implementar en Modelo2002 el mtodo
heredado EnviarFax, ya que dicho modelo prescinde de dicha funcionalidad. Una
posible implementacin sera la que se presenta en el listado 1.
Listado 1:
class Modelo2002 : ImpresoraMultifuncional
{
public override void Imprimir()
{
Impresion.EnviarImpresion();
}
public override void Escanear()
{
Escaner.DigitalizarAFormatoPng();
}
public override void Cancelar()
{
Impresion.CancelarImpresion();
}
public override void EnviarFax()
{
throw new System.NotImplementedException();
}
public void EnviarEMail()
{
// Enviamos por correo electrnico
}
}

El mtodo Enviar Fax no se implementa, y por consiguiente una llamada al mtodo


generara una excepcin del tipo NotImplementedException. S, es cierto que
podramos quitar dicha excepcin y podramos dejar el mtodo vaco; pero entonces
el programador que utilice la clase se encontrar con un mtodo que sencillamente
no hace nada. Esto podramos intentar solucionarlo de varias formas: mediante
documentacin, indicando que el mtodo no es funcional; mediante comentarios en
el cdigo (pero es posible que un programador que utilice la clase no tenga acceso
al cdigo), etc. En cualquier caso, el problema seguira existiendo, y solo estaramos
ocultndolo.

De "pesada" a confusa

Es importante que nos concienciemos de este problema. En nuestro ejemplo, se


trata de un nico mtodo, y eludir el problema puede ser bastante obvio; pero si la
clase abstracta implementara una docena de mtodos y nicamente utilizramos
tres o cuatro de ellos en un contexto no tan claro como el de las impresoras
multifuncionales, el problema se hara ms complejo.

Un ejemplo de esto lo tenemos en el propio .NET Framework. La clase abstracta


System.Web.Security.Member - shipProvider contiene todos los mtodos
necesarios para la autenticacin ASP.NET: valido credenciales accediendo a algn
mecanismo de almacenamiento (SQL Server, sistema de archivos, etc.), bloquea
usuarios, gestiona contraseas, etc. Si implementamos nuestro propio proveedor
de autenticacin e implementamos la clase MembershipProvider, seguramente solo
utilizaremos algunos de los mtodos heredados (es decir, implementaremos
nicamente ciertas funcionalidades disponibles en la clase base), y en este caso no
es tan evidente cules de esos mtodos deberemos redefinir. Cmo sabra el
programador que utilice nuestra clase qu mtodos sta implementa? Qu
comportamiento debera tener nuestra clase ante la llamada a un mtodo no
implementado? En definitiva, qu valor real tienen los mtodos que estn
disponibles pero no implementados? La respuesta es: ninguno, aparte de crear
confusin.

En nuestro ejemplo, la clase Modelo2000 implementa, al contrario que Modelo1998,


la caracterstica de Fotocopiar. Dicho mtodo se implementa en la propia clase
Modelo2000, y quizs nos hayamos preguntado por qu no hemos aadido el
mtodo a la clase abstracta ImpresoraMultifuncional. El motivo puede ser bien
dispar. Quizs quin dise el sistema decidi no tocar la clase abstracta y extender
la clase Modelo2000; sin embargo, resulta que a partir de Modelo2000 todas las
impresoras tienen soporte de fotocopia y por lo tanto todos los modelos debern
implementar el mtodo Fotocopiar. Utilizando esta estrategia, tenemos que vulnerar
el principio DRY (Don't Repeat Yourself); y lo que es ms preocupante, otro
programador puede tener la ocurrencia o la necesidad de modificar el contrato o el
nombre del mtodo. En definitiva, ello complica el mantenimiento del sistema;
cualquier modificacin sobre del mtodo Fotocopiar implicar buscarlo por todo el
cdigo, aumentando por tanto el riesgo de error. Es contradictorio que tengamos
encapsulados en una clase abstracta miembros que no usamos, por ejemplo, en
Modelo2002, mientras que otros que s seran firmes candidatos a serlos, como el
caso del mtodo Fotocopiar, no lo son.

Para solucionar el problema, debemos segregar las operaciones en pequeas


interfaces. Una interfaz es un contrato que debe cumplir una clase, y tales contratos
deben ser especficos, no genricos; esto nos proporcionar una forma ms gil de
definir una nica responsabilidad por interfaz - de otra forma, violaramos adems
el Principio de Responsabilidad nica (Single Responsibility Principle, SRP).

Retomando de nuevo el ejemplo prctico, volvamos a replantear el sistema y


separemos las responsabilidades por interfaces. En el listado 2 podemos ver el
resultado de dicha segregacin.

Listado 2:
public interface IImprimible
{
void Imprimir();
}
public interface IFotocopiable
{
void Fotocopiar();
}
public interface IEscaneable
{
void Escanear();
}
public interface IFaxCompatible
{
void EnviarFax();
void RecibirFax();
}
public interface ITcpIpCompatible
{
void EnviarEMail();
}
class Modelo1998 : IImprimible, IEscaneable, IFaxCompatible
{
// ...
}
class Modelo2000 : IImprimible, IEscaneable, IFaxCompatible,
IFotocopiable
{
// ...
}
class Modelo2002 : IImprimible, IEscaneable, IFotocopiable,
ITcpIpCompatible
{
// ...
}
PRINCIPIO DE INVERSIN DE DEPENDENCIAS

Este principio es una tcnica bsica, y ser el que ms presente tengas en tu da a


da si quieres hacer que tu cdigo sea testable y mantenible. Gracias al principio de
inversin de dependencias, podemos hacer que el cdigo que es el ncleo de
nuestra aplicacin no dependa de los detalles de implementacin, como pueden ser
el framework que utilices, la base de datos, cmo te conectes a tu servidor Todos
estos aspectos se especificarn mediante interfaces, y el ncleo no tendr que
conocer cul es la implementacin real para funcionar.

La definicin que se suele dar es:

A. Las clases de alto nivel no deberan depender de las clases de bajo nivel. Ambas
deberan depender de las abstracciones.

B. Las abstracciones no deberan depender de los detalles. Los detalles deberan


depender de las abstracciones.

Pero entiendo que slo con esto no te quede muy claro de qu estamos hablando,
as que voy a ir desgranando un poco el problema, cmo detectarlo y un ejemplo.

Ejemplo

Imaginemos que tenemos una cesta de la compra que lo que hace es almacenar la
informacin y llamar al mtodo de pago para que ejecute la operacin. Nuestro
cdigo sera algo as:

public class ShoppingBasket


{
public void buy(Shopping shopping) {

SqlDatabase db = new SqlDatabase();


db.save(shopping);

CreditCard creditCard = new CreditCard();


creditCard.pay(shopping);
}
}

public class SqlDatabase {


public void save(Shopping shopping){
// Saves data in SQL database
}
}

public class CreditCard {


public void pay(Shopping shopping){
// Performs payment using a credit card
}
}

Aqu estamos incumpliendo todas las reglas que impusimos al principio. Una clase
de ms alto nivel, como es la cesta de la compra, est dependiendo de otras de alto
nivel, como cul es el mecanismo para almacenar la informacin o para realizar el
mtodo de pago. Se encarga de crear instancias de esos objetos y despus
utilizarlas.

Piensa ahora qu pasa si quieres aadir mtodos de pago, o enviar la informacin


a un servidor en vez de guardarla en una base de datos local. No hay forma de
hacer todo esto sin desmontar toda la lgica. Cmo lo solucionamos?

Primer paso, dejar de depender de concreciones. Vamos a crear interfaces que


definan el comportamiento que debe dar una clase para poder funcionar como
mecanismo de persistencia o como mtodo de pago:
public interface Persistence {

void save(Shopping shopping);

public class SqlDatabase implements Persistence {

@Override

public void save(Shopping shopping){

// Saves data in SQL database

public interface PaymentMethod {

void pay(Shopping shopping);

public class CreditCard implements PaymentMethod {

@Override

public void pay(Shopping shopping){

// Performs payment using a credit card

Vous aimerez peut-être aussi