35

Estoy configurando Automapper en el Bootstrapper y llamo al Bootstrap() en el Application_Start(), y me han dicho que esto es incorrecto porque tengo que modificar mi clase Bootstrapper cada vez que tengo que agregar un nuevo mapeo, entonces estoy violando el principio abierto-cerrado¿Configurar el Automapper en Bootstrapper viola el principio de Open-Closed?

¿Cómo crees que violo realmente este principio?

public static class Bootstrapper 
{ 
    public static void BootStrap() 
    { 
     ModelBinders.Binders.DefaultBinder = new MyModelBinder(); 
     InputBuilder.BootStrap(); 
     ConfigureAutoMapper(); 
    } 

    public static void ConfigureAutoMapper() 
    { 
     Mapper.CreateMap<User, UserDisplay>() 
      .ForMember(o => o.UserRolesDescription, 
         opt => opt.ResolveUsing<RoleValueResolver>()); 
     Mapper.CreateMap<Organisation, OrganisationDisplay>(); 
     Mapper.CreateMap<Organisation, OrganisationOpenDisplay>(); 
     Mapper.CreateMap<OrganisationAddress, OrganisationAddressDisplay>(); 
    }  
} 

Respuesta

39

Argumentaría que está violando dos principios: el principio de responsabilidad única (SRP) y el principio de apertura/cierre (OCP).

Está violando el SRP porque la clase de arranque tiene más de un motivo para cambiar: si modifica el enlace del modelo o la configuración del asignador automático.

Usted estaría violando el OCP si tuviera que agregar un código de arranque adicional para configurar otro subcomponente del sistema.

Como suelo manejar esto es que defino la siguiente interfaz.

public interface IGlobalConfiguration 
{ 
    void Configure(); 
} 

Para cada componente del sistema que necesita un arranque, crearía una clase que implemente esa interfaz.

public class AutoMapperGlobalConfiguration : IGlobalConfiguration 
{ 
    private readonly IConfiguration configuration; 

    public AutoMapperGlobalConfiguration(IConfiguration configuration) 
    { 
     this.configuration = configuration; 
    } 

    public void Configure() 
    { 
     // Add AutoMapper configuration here. 
    } 
} 

public class ModelBindersGlobalConfiguration : IGlobalConfiguration 
{ 
    private readonly ModelBinderDictionary binders; 

    public ModelBindersGlobalConfiguration(ModelBinderDictionary binders) 
    { 
     this.binders = binders; 
    } 

    public void Configure() 
    { 
     // Add model binding configuration here. 
    } 
} 

Uso Ninject para inyectar las dependencias.IConfiguration es la implementación subyacente de la clase estática AutoMapper y ModelBinderDictionary es el objeto ModelBinders.Binder. Luego definiría un NinjectModule que escanearía el ensamblado especificado para cualquier clase que implemente la interfaz IGlobalConfiguration y agregue esas clases a un compuesto.

public class GlobalConfigurationModule : NinjectModule 
{ 
    private readonly Assembly assembly; 

    public GlobalConfigurationModule() 
     : this(Assembly.GetExecutingAssembly()) { } 

    public GlobalConfigurationModule(Assembly assembly) 
    { 
     this.assembly = assembly; 
    } 

    public override void Load() 
    { 
     GlobalConfigurationComposite composite = 
      new GlobalConfigurationComposite(); 

     IEnumerable<Type> types = 
      assembly.GetExportedTypes().GetTypeOf<IGlobalConfiguration>() 
       .SkipAnyTypeOf<IComposite<IGlobalConfiguration>>(); 

     foreach (var type in types) 
     { 
      IGlobalConfiguration configuration = 
       (IGlobalConfiguration)Kernel.Get(type); 
      composite.Add(configuration); 
     } 

     Bind<IGlobalConfiguration>().ToConstant(composite); 
    } 
} 

Me gustaría agregar el siguiente código al archivo Global.asax.

public class MvcApplication : HttpApplication 
{ 
    public void Application_Start() 
    { 
     IKernel kernel = new StandardKernel(
      new AutoMapperModule(), 
      new MvcModule(), 
      new GlobalConfigurationModule() 
     ); 

     Kernel.Get<IGlobalConfiguration>().Configure(); 
    } 
} 

Ahora mi código de arranque se adhiere tanto a SRP como a OCP. Puedo agregar fácilmente código de arranque adicional creando una clase que implementa la interfaz IGlobalConfiguration y mis clases de configuración global solo tienen un motivo para cambiar.

+3

y todavía tiene que cambiar el Configurar método en AutoMapperGlobalConfiguration cada vez que necesita agregar una nueva asignación – Omu

+10

Pero eso no violaría OCP. OCP no es escribir una vez, nunca volver a tocar. OCP afirma que el consumidor del código de arranque, el GlobalConfigurationModule (GCM), debe confiar en la abstracción y no en la implementación conrete. Si tuviera que agregar bootstrapping para log4net, crearía una clase Log4NetGlobalConfiguration que implementaría IGlobalConfiguration. Sin embargo, no tendría que modificar ninguna otra parte de mi código y definitivamente no el GCM porque no tiene un conocimiento complejo de la implementación conrete de la interfaz IGlobalConfiguration. – mrydengren

+0

Estoy en una duda. Una vez que se ejecuta Mapper.CreateMap <>(), ¿los mapas existen hasta el cierre de la aplicación? – JPCF

3

Para tenerlo completamente cerrado, podría tener un inicializador estático por registro de Mapeo, pero eso sería excesivo.

Algunas cosas son realmente útiles para tener un grado de centralización desde el punto de vista de la capacidad de ingeniería inversa.

En NInject, existe la noción de tener un Module por proyecto o subsistema (conjunto de proyectos), lo que parece un compromiso razonable.

2

En todo caso, es el principio de responsabilidad única que está infringiendo, ya que la clase tiene más de un motivo para cambiar.

Yo personalmente tendría una clase ConfigureAutoMapper con la que se realizó toda mi configuración para AutoMapper. Pero podría argumentarse que se trata de elección personal.

+0

sí, y aún así, después de moverlo a otra clase, no me escapo del Principio de Cerrado Abierto – Omu

2

Omu, lucho con preguntas similares cuando se trata de arrancar un contenedor IoC en la rutina de inicio de mi aplicación. Para IoC, la guía que me han dado apunta a la ventaja de centralizar su configuración en lugar de rociarla sobre su aplicación cuando agrega cambios. Para configurar AutoMapper, creo que la ventaja de la centralización es mucho menos importante. Si puede obtener su contenedor AutoMapper en su contenedor IoC o en el Localizador de servicios, estoy de acuerdo con la sugerencia de Ruben Bartelink de configurar las asignaciones una vez por ensamblado o en constructores estáticos o algo descentralizado.

Básicamente, lo veo como una cuestión de decidir si desea centralizar el bootstrapping o descentralizarlo. Si le preocupa el principio abierto/cerrado en su rutina de inicio, vaya con la descentralización. Pero su adhesión a OCP se puede marcar a cambio del valor de todo el arranque realizado en un solo lugar. Otra opción sería hacer que el programa de arranque analice ciertos ensamblajes para registros, suponiendo que AutoMapper tenga dicho concepto.

3

Sé que este es uno antiguo, pero puede que le interese saber que he creado una biblioteca de código abierto llamada Bootstrapper que se ocupa precisamente de este problema. Quizás quieras revisarlo. Para evitar romper el principio de OC, necesita definir sus correlacionadores en clases separadas que implementen IMapCreater. Boostrapper encontrará estas clases utilizando la reflexión e inicializará todos los mapeadores al inicio

Cuestiones relacionadas