Настройка Automapper в Bootstrapper нарушает принцип Open-Closed? - PullRequest
35 голосов
/ 27 ноября 2009

Я настраиваю Automapper в Bootstrapper, и я вызываю Bootstrap() в Application_Start(), и мне сказали, что это неправильно, потому что я должен изменять свой класс 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>();
    }    
}

Ответы [ 5 ]

39 голосов
/ 28 ноября 2009

Я бы сказал, что вы нарушаете два принципа: принцип единой ответственности (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 голосов
/ 25 ноября 2011

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

3 голосов
/ 27 ноября 2009

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

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

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

2 голосов
/ 27 ноября 2009

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

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

2 голосов
/ 27 ноября 2009

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

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

...