【问题标题】:StructureMap resolve dependency through injection instead of service locationStructureMap 通过注入而不是服务位置来解决依赖关系
【发布时间】:2011-02-01 15:53:49
【问题描述】:

在我的项目中,我使用程序集扫描器注册了许多 ISerializers 实现。 FWIW 这是注册我的ISerializers的代码

Scan(scanner =>
{
    scanner.AssemblyContainingType<ISerializer>();
    scanner.AddAllTypesOf<ISerializer>().NameBy(type => type.Name);
    scanner.WithDefaultConventions();
});

然后正确注册

ISerializer (...ISerializer)
Scoped as:  Transient

JsonSerializer    Configured Instance of ...JsonSerializer
BsonSerializer    Configured Instance of ...BsonSerializer

等等。

目前,我能够弄清楚如何解决我想要的序列化程序的唯一方法是使用硬编码服务位置调用

jsonSerializer = ObjectFactory.GetNamedInstance<ISerializer>("JsonSerializer");

现在我知道在我的课堂上我特别想要 jsonSerializer,那么有没有办法配置一条规则或类似的规则,让 ISerializer 能够根据属性名称连接命名实例?这样我就可以拥有

MySomeClass(ISerializer jsonSerializer, ....)

StructureMap 正确地解决了这种情况?还是我接近这个错误,也许我应该只注册实现 ISerializer 的具体类型,然后专门使用

MySomeClass(JsonSerializer jsonSerializer, ....)

对于与具体类类似的东西?

【问题讨论】:

    标签: c# dependency-injection inversion-of-control structuremap service-locator


    【解决方案1】:

    当您进行依赖注入并且需要能够创建给定接口的特殊类型实例时,推荐的解决方案是创建 specialized 工厂类。这允许您在不实际注入容器的情况下使用命名参数。

    示例

    这是您将要注入的抽象类型:

    public interface ISerializerFactory
    {
        ISerializer GetSerializer(string name);
    }
    

    这是使用容器(StructureMap)的具体类型:

    public class StructureMapSerializerFactory : ISerializerFactory
    {
        public ISerializer GetSerializer(string name)
        {
            return ObjectFactory.GetNamedInstance<ISerializer>(name);
        }
    }
    

    那么你的类将如下所示:

    public class MyClass
    {
        private readonly ISerializerFactory serializerFactory;
    
        public MyClass(ISerializerFactory serializerFactory)
        {
            if (serializerFactory == null)
                throw new ArgumentNullException("serializerFactory");
            this.serializerFactory = serializerFactory;
        }
    
        public string SerializeSomeData(MyData data)
        {
            ISerializer serializer = serializerFactory.GetSerializer("Json");
            return serializer.Serialize(data);
        }
    }
    

    我已经写了这个传递“Json”而不是“JsonSerializer”,它不会自动工作。但我认为您应该更改注册名称以消除多余的“Serializer”后缀(我们已经知道它是一个序列化程序,因为我们要求的是ISerializer)。换句话说,创建一个这样的方法:

    private static string ExtractSerializerName(Type serializerType)
    {
        string typeName = serializerType.Name;
        int suffixIndex = typeName.IndexOf("Serializer");
        return (suffixIndex >= 0) ?
            typeName.Substring(0, suffixIndex - 1) : typeName;
    }
    

    并像这样注册它:

    scanner.AddAllTypesOf<ISerializer>().NameBy(type => ExtractSerializerName(type));
    

    那么你可以直接使用字符串“Json”来创建它,而不是“JsonSerializer”,这样看起来会不那么难看,感觉不那么耦合。

    如果您不喜欢硬编码的字符串,那么您可以做的另一件事是为您的工厂创建一个枚举:

    public enum SerializationFormat { Json, Bson, Xml };
    
    public interface ISerializerFactory
    {
        ISerializer GetSerializer(SerializationFormat format);
    }
    
    public class StructureMapSerializerFactory : ISerializerFactory
    {
        public ISerializer GetSerializer(SerializationFormat format)
        {
            return ObjectFactory.GetNamedInstance<ISerializer>(format.ToString());
        }
    }
    

    所以不要写这个:

    ISerializer serializer = serializerFactory.GetSerializer("Json");
    

    你可以改写这个:

    ISerializer serializer =
        serializerFactory.GetSerializer(SerializationFormat.Json);
    

    从长远来看,这将不太容易出错。

    从长远来看,这可能更易于维护,因为如果您开始更改序列化程序的类名和/或名称不一致,那么您可以将简单的 ToString() 替换为 switch 语句并实际映射您正在注册的类名的枚举值。

    我可能会将所有这些代码(包括您问题中的自动注册代码)放在同一个命名空间,甚至是同一个代码文件中,以清楚地表明这些部分都是相互依赖的。

    【讨论】:

    • 感谢您的回答,我认为这样做有一个标准的实现模式,并且我同意枚举是在工厂内部处理它的更好方法。
    • 你的答案底部附近的&lt;ISerializer&gt;(name.ToString());也应该是&lt;ISerializer&gt;(format.ToString());
    • @Chris:哎呀,应该的。已更正!
    【解决方案2】:

    我很好奇。 ISerializer 本身增加了什么价值?让我们从具体的实现到运行时挑选的一个或多个。

    如果您的类型依赖于特定类型的序列化程序,请依赖它(IJsonSerializer)。这要求向容器注册该类型的默认实例。

    但是,如果您想更多地将 ISerializer 设置为 Strategies,您将注册所有 ISerializer,然后依赖它们的数组,StructureMap 将推入所有已注册 ISerializer 的数组。然后,使用这些序列化程序的类负责选择使用哪一个。

    在策略场景中,您可能需要序列化程序上的一些元数据供您的协调类使用来区分它们。恕我直言,这实际上不应该是容器配置,例如注册类型上的名称,而是实现本身的元数据。

    【讨论】:

    • 顺便回答你的问题,ISerializer 的价值在于它公开了 string Serialize(T obj)T Deserialize&lt;T&gt;(string obj) 的通用方法,然后可以包含任何序列化程序的所有使用,这将让我插入任何类型的我想要的序列化器,而不关心它是如何实现结果的。虽然我只是认为可能值得在我的所有序列化输出前面加上 SerializerName|data,然后让反序列化函数负责确定如何在工厂中反序列化我的对象,而不是需要被告知如何反序列化。
    【解决方案3】:

    由于您的代码假定它正在获取 JsonSerializer,因此创建一个只有 JsonSerializer 实现的新 IJsonSerializer 接口。任何需要 JsonSerializer 的类都应该接受 IJsonSerializer。如果您仍然需要 ISerializer 接口在所有序列化器中通用,则可以将 IJsonSerializer 用作标记接口。

    或者,当您在 StructureMap 中注册您的类时,您可以将特定的 ISerializer 实现绑定到您的类。

    x.For<MySomeClass>().Use(c => new MySomeClass(c.GetInstance<JsonSerializer>()));
    

    【讨论】:

    • 这绝对是一个可行的解决方法,但那时我可以完全放弃 ISerializer 接口,只为每个序列化器提供一个接口,然后有效地静态链接它们是微不足道的。我的问题更多地围绕着如何正确使用 IOC 来正确使用已注册的 ISerializer 集合并将特定的 ISerializer 取出,或者我只是看错了,我只想将多个实现注册到一个接口是如果我想取回整个集合并对其进行迭代操作?
    【解决方案4】:

    据我所知,这并不是程序集扫描功能的真正用途。当单个程序集有多个不同接口的实现(例如IRepository&lt;File&gt;IRepository&lt;Folder&gt; 等)时,它会更有用。因此,例如,当您引用测试程序集时,您正在注入测试存储库,而在生产环境中,您正在注入实体框架存储库。

    在您的情况下,您的任何示例看起来都没有完全注入依赖项。换句话说,当你写

    ObjectFactory.GetNamedInstance<ISerializer>("JsonSerializer");
    

    由于对字符串进行硬编码,您仍然依赖于 Json 序列化程序,而且 StructureMap 从该调用返回某种其他类型的序列化程序是没有意义的。

    我无法准确说出您要使用 StructureMap 完成什么,但如果您需要根据一组特定的运行时条件返回特定的序列化程序,您可以查看 conditional construction

    另一方面,这听起来不像是你想要的那种开关,所以你绝对应该考虑摆脱它。毕竟上面的代码真的和

    没什么区别
    new JsonSerializer();
    

    StructureMap 是一个很棒的工具,但并不是每个项目都需要它。

    祝你好运!

    【讨论】:

    • 优势来自这样一个事实,即序列化程序可以具有在调用 .GetNamedInstance 时解决的依赖关系,但也许我可以使用该条件帖子来做到这一点,但这是使用 2.5.2 而不是使用 2.6 的语法。 1 因此,尝试找出差异应该很有趣。
    猜你喜欢
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-02
    • 2011-06-26
    • 1970-01-01
    相关资源
    最近更新 更多