35

Я настраиваю Automapper в Bootstrapper, и я вызываю Bootstrap() в Application_Start(), и мне сказали, что это неправильно, потому что я должен изменить класс Bootstrapper каждый раз, когда мне нужно добавить новое сопоставление, поэтому я нарушаю принцип открытого закрытия.Настройка Automapper в Bootstrapper нарушает принцип открытого закрывания?

Как вы думаете, действительно ли я нарушаю этот принцип?

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>(); 
    }  
} 

ответ

39

Я бы сказал, что вы нарушаете два принципа: принцип единой ответственности (SRP) и принцип открытого/закрытого (OCP).

Вы нарушаете SRP, потому что класс начальной загрузки имеет более чем одну причину изменения: если вы измените привязку модели или конфигурацию автоматического сопоставления.

Вы нарушили бы OCP, если бы вы добавили дополнительный код начальной настройки для настройки другого подкомпонента системы.

Как я обычно обрабатываю это, я определяю следующий интерфейс.

public interface IGlobalConfiguration 
{ 
    void Configure(); 
} 

Для каждого компонента в системе, которая нуждается в самонастройке, я бы создал класс, который реализует этот интерфейс.

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. 
    } 
} 

Я использую Ninject для ввода зависимостей.IConfiguration - это базовая реализация статического класса AutoMapper, а ModelBinderDictionary - объект ModelBinders.Binder. Затем я бы определил NinjectModule, который сканировал указанную сборку для любого класса, который реализует интерфейс IGlobalConfiguration и добавит эти классы в композит.

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); 
    } 
} 

Я бы добавил следующий код в файл Global.asax.

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

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

Теперь мой код начальной загрузки придерживается как SRP, так и OCP. Я могу легко добавить дополнительный код начальной загрузки, создав класс, который реализует интерфейс IGlobalConfiguration, и мои глобальные классы конфигурации имеют только одну причину изменения.

+3

, и вам все равно придется менять настройку метод в AutoMapperGlobalConfiguration каждый раз, когда вам нужно добавить новое сопоставление – Omu

+10

Но это не нарушит OCP. OCP не пишет один раз, никогда не трогайте его снова. OCP заявляет, что потребитель кода начальной загрузки, GlobalConfigurationModule (GCM), должен полагаться на абстракцию и не выполнять конкретную реализацию. Если бы я должен был добавить загрузку для log4net, я бы создал класс класса Log4NetGlobalConfiguration, который бы реализовал IGlobalConfiguration. Тем не менее, мне не пришлось бы изменять какую-либо другую часть моего кода и, безусловно, не GCM, потому что у нее нет сложных знаний о реализации интерфейса IGlobalConfiguration. – mrydengren

+0

Я сомневаюсь. Как только Mapper.CreateMap <>() выполняется, карты существуют до выключения приложения? – JPCF

3

Чтобы иметь его полностью закрытым, вы можете иметь статический инициализатор для регистрации картографирования, но это было бы излишним.

Некоторые вещи действительно полезны для централизации до степени с точки зрения возможности реверсирования.

В NInject существует понятие наличия Module для каждого проекта или подсистемы (набор проектов), что представляется разумным компромиссом.

2

Если у вас есть принцип единственной ответственности, который вы нарушаете, в этом классе есть несколько причин для изменения.

У меня лично был бы класс ConfigureAutoMapper, с которым была выполнена вся моя конфигурация для AutoMapper. Но можно утверждать, что это зависит от личного выбора.

+0

да, и все же, после того, как я перевешу его в другой класс, я не уйду от принципа открытого закрывания – Omu

2

Omu, я борюсь с аналогичными вопросами, когда дело доходит до загрузки контейнера IoC в рутину запуска приложения. Для IoC руководство, которое мне дано, указывает на то, что вы можете централизовать свою конфигурацию, а не разбрызгивать ее по всему приложению при добавлении изменений. Для настройки AutoMapper я считаю, что преимущество централизации гораздо менее важно. Если вы можете получить свой контейнер AutoMapper в свой контейнер IoC или Service Locator, я согласен с предложением Ruben Bartelink о настройке сопоставлений один раз на сборку или в статических конструкторах или что-то децентрализованное.

В принципе, я рассматриваю это как вопрос о том, хотите ли вы централизовать загрузку или децентрализовать ее. Если вы обеспокоены принципом Open/Closed в вашей рутине запуска, пойдите с децентрализацией. Но ваша привязка к OCP может быть набрана в обмен на ценность всей вашей начальной загрузки, сделанной в одном месте. Другим вариантом было бы, чтобы bootstrapper просматривал определенные сборки для реестров, предполагая, что AutoMapper имеет такую ​​концепцию.

3

Я знаю, что это старый, но вам может быть интересно узнать, что я создал библиотеку с открытым исходным кодом, которая называется Bootstrapper, которая касается именно этой проблемы. Вы можете проверить это. Чтобы избежать нарушения принципа OC, вам необходимо определить ваши карты в отдельных классах, которые реализуют IMapCreater. Boostrapper найдет эти классы с помощью отражения и будет инициализировать все картографы при запуске

 Смежные вопросы

  • Нет связанных вопросов^_^