【问题标题】:Configuring Automapper in Bootstrapper violates Open-Closed Principle?在 Bootstrapper 中配置 Automapper 违反了开闭原则?
【发布时间】:2010-12-20 21:47:08
【问题描述】:

我在 Bootstrapper 中配置 Automapper,我在 Application_Start() 中调用了 Bootstrap(),我被告知这是错误的,因为每次我必须添加一个新映射,所以我违反了开闭原则。

你怎么看,我真的违反了这个原则吗?

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

【问题讨论】:

    标签: .net automapper bootstrapping solid-principles open-closed-principle


    【解决方案1】:

    我知道这是一个旧的,但您可能有兴趣知道我创建了一个名为 Bootstrapper 的开源库,它可以准确地处理这个问题。你可能想检查一下。 为避免破坏 OC 原则,您需要在实现 IMapCreater 的单独类中定义映射器。 Boostrapper 将使用反射找到这些类,并将在启动时初始化所有映射器

    【讨论】:

      【解决方案2】:

      要完全关闭它,您可以为每个映射注册设置一个静态初始化程序,但这有点过头了。

      从能够进行逆向工程的角度来看,将某些东西集中到一定程度实际上是有用的。

      在 NInject 中,每个项目或子系统(项目集)都有一个 Module 的概念,这似乎是一个明智的折衷方案。

      【讨论】:

        【解决方案3】:

        我认为您违反了两个原则:单一责任原则 (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 类的底层实现,ModelBinderDictionaryModelBinders.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 接口的类,我可以轻松添加额外的引导代码,而我的全局配置类只有一个更改的理由。

        【讨论】:

        • 而且每次需要添加新映射的时候还是要更改 AutoMapperGlobalConfiguration 中的 Configure 方法
        • 但这不会违反 OCP。 OCP 不是写一次就不会再碰。 OCP 指出,引导代码的使用者 GlobalConfigurationModule (GCM) 应该依赖于抽象而不是具体的实现。如果我要为 log4net 添加引导程序,我将创建一个类 Log4NetGlobalConfiguration 类来实现 IGlobalConfiguration。但是,我不必修改代码的任何其他部分,当然也不必修改 GCM,因为它不了解 IGlobalConfiguration 接口的具体实现。
        • 我有疑问。一旦 Mapper.CreateMap() 被执行,地图就会一直存在,直到应用程序关闭?
        • 好问题@Lol 编码器。我什至没有意识到它在那里(我不再真正使用它,因为它对我来说有点过度设计)。无论如何,看看Composite patternGlobalConfigurationComposite 实现了IGlobalConfiguration 接口,但它也定义了Add(IGlobalConfiguration configuration)。组合上的 Configure() 方法循环遍历每个先前添加的配置对象并在其上调用 Configure()
        • mrydengren,您能否分享一下您现在如何执行上述操作的更新来源?谢谢!
        【解决方案4】:

        Omu,在我的应用程序启动例程中引导 IoC 容器时,我遇到了类似的问题。对于 IoC,我得到的指导指出了集中配置的优势,而不是在添加更改时将其散布在整个应用程序中。对于配置 AutoMapper,我认为集中化的优势并不重要。如果您可以将 AutoMapper 容器放入您的 IoC 容器或服务定位器中,我同意 Ruben Bartelink 的建议,即在每个程序集或静态构造函数或分散的东西中配置一次映射。

        基本上,我认为这是一个决定是要集中引导还是分散引导的问题。如果您对启动程序中的开放/封闭原则感到担忧,那就去中心化它。但是,您对 OCP 的遵守可能会被调低,以换取您在一个地方完成的所有引导的价值。另一种选择是让引导程序扫描注册表的某些程序集,假设 AutoMapper 有这样的概念。

        【讨论】:

          【解决方案5】:

          如果你违反了单一责任原则,那么这个类有不止一个改变的理由。

          我个人会有一个 ConfigureAutoMapper 类,我对 AutoMapper 的所有配置都已完成。但可以说这取决于个人选择。

          【讨论】:

          • 是的,而且,在我把它移到另一个班级之后,我仍然没有摆脱开放封闭原则
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-12-15
          • 1970-01-01
          • 2019-07-09
          • 2010-10-08
          • 2020-07-11
          • 1970-01-01
          相关资源
          最近更新 更多