SOLID: Principio de Abierto Cerrado

Introducción al principio

Bertrand Meyer fue el primero que habló sobre este principio en 1988 en el libro Object-Oriented Software Construction. Este principio indica lo siguiente:

Las entidades de software deben estar abiertas para su extensión, pero cerradas para su modificación.

Abrir una clase para editarla conlleva riesgos de afectar a más cosas. Lo ideal es no volver a modificar una clase salvo por un bug. Por lo tanto las entidades de software deben poder extenderse o modificar su comportamiento sin editar su código original.
Patrones como Composite, Strategy, Decorator, Factory y Observer ayudan a cumplir este principio.

Ejemplo visual

Para ponerse un sombrero no hace falta abrir el cerebro

Ejemplo de código

Fijaros en en siguiente código:

static void Main(string[] args)
{
	if (string.Equals(args[0], UPDATE_CATALOGPRODUCT_TO_CA))
	{
    	  //Synchronizing from catalog to CA
    	  ...
	}
	if (string.Equals(args[0], UPDATE_CA_TO_INVENTORYPRODUCT))
	{
    	  //Synchronizing from CA to Catalog
    	  ...
	}
	if (string.Equals(args[0], ASSIGN_ALLLABELS_CA))
	{
    	  //Assigning labels to CA
    	  ...
	}
}

Para añadir más casos de sincronización hay que abrir la clase para modificarla.

Refactorizando

Vamos a refactorizarlo para que este abierto a la extensión pero cerrado a la modificación

public interface ISynchronizer
{
    string Key { get; }
    void Execute();
}
 
public class dTLPToCASynchronizer : ISynchronizer
{
    public string Key { get {return "-dtlptoca";} }
 
    public void Execute() { ... }
}
 
public class CATodTLPSynchronizer : ISynchronizer
{
    public string Key { get {return "-catodtlp";} }
 
    public void Execute() { ... }
}

public class SyncronizerFactory
{
   static List<ISynchronizer>  synchronizers = null;
 
   static SyncronizerFactory()
   {
       var types = Assembly.GetExecutingAssembly()             	   .GetTypes().Where(mytype => 
          mytype.GetInterfaces().Contains(typeof(ISynchronizer)));
 
       synchronizers = new List<ISynchronizer>();
 
        foreach (Type type in types)
        {            	 
           synchronizers.Add(Activator.CreateInstance(type));
        }
    }
 
    public static ISynchronizer GetSyncronizer(string key)
    {
       ISynchronizer synchzer = synchronizers.FirstOrDefault();
 
       if (synchronizer == null)
            throw new Exception("Synchronizer not found”);
 
       return synchronizer;
    }
}
class Program {
    static void Main(string[] args)
    {
         ISynchronizer synchronizer =  
            SyncronizerFactory.GetSyncronizer(args[0]);
 
        synchronizer.Execute();
    }
}

Para solucionar este caso nos hemos apoyado en el patrón Factory, pero además la factoría crea los sincronizadores mediante reflexión. Por lo tanto ahora para añadir un caso de sincronización nuevo no hay que modificar ninguna clase existente, solo tendríamos que crear un synchronizer nuevo implementando la abstracción ISynchronizer. De esta forma estamos cumpliendo el principio.

Curso y artículos relacionados

Conclusiones

Aplicando el Principio de Abierto Cerrado conseguimos un código menos frágil a cambios, porque para extender comportamientos no significa modificar código existente, por lo tanto es difícil estropear código que funciona.