Вопрос

Я настраиваю 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>();
    }    
}
Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top