StructureMap resolve dependency through injection instead of service location

后端 未结 4 764
孤城傲影
孤城傲影 2021-02-09 19:50

In my project I register many ISerializers implementations with the assembly scanner. FWIW this is the code that registers my ISerializers



        
4条回答
  •  死守一世寂寞
    2021-02-09 20:29

    When you're doing Dependency Injection and need to be able to create specially-typed instances of a given interface, the recommended solution is to create specialized factory classes. This allows you to use a named argument without actually injecting the container.

    Example

    This is the abstract type that you'll be injecting:

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

    Here is the concrete type, which makes use of your container (StructureMap):

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

    Then your class would look like the following:

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

    I've written this passing "Json" instead of "JsonSerializer" which won't automatically work. But I think you should change your registration names to eliminate the redundant "Serializer" suffix (we already know it's a serializer because we're asking for an ISerializer). In other words create a method like this:

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

    And register it like this:

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

    Then you can just use the string "Json" to create it instead of "JsonSerializer", which will look a little less ugly and feel less coupled.

    If you don't like the hard-coded strings, then another thing you can do is create an enumeration for your factory:

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

    So instead of writing this:

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

    You get to write this instead:

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

    Which is going to be less error-prone in the long run.

    This will probably be more maintainable in the long run because if you start changing the class names of your serializers and/or the names are inconsistent, then you can replace the simple ToString() with a switch statement and actually map the enum values to the class names you're registering.

    I'd probably put all of this code - including the auto-registration code in your question - in the same namespace, or even the same code file, to clearly indicate that these pieces are all interdependent.

提交回复
热议问题