StructureMap resolve dependency through injection instead of service location

后端 未结 4 768
孤城傲影
孤城傲影 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<ISerializer>(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<ISerializer>().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<ISerializer>(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.

    0 讨论(0)
  • 2021-02-09 20:38

    Since your code assumes it is getting a JsonSerializer, create a new IJsonSerializer interface that only the JsonSerializer implements. Any class that needs the JsonSerializer should accept an IJsonSerializer. If you still need the ISerializer interface to be common across all serializers, the IJsonSerializer can be used just as a marker interface.

    Alternatively, you can tie the specific ISerializer implementation to your class when you register your class in StructureMap.

    x.For<MySomeClass>().Use(c => new MySomeClass(c.GetInstance<JsonSerializer>()));
    
    0 讨论(0)
  • 2021-02-09 20:49

    As far as I know, that's not really what the assembly scanning functionality is meant for. It's more useful when a single assembly has numerous implementations of different interfaces (eg. IRepository<File>, IRepository<Folder>, etc.). So, for example, when you're referencing your test assembly you're injecting test repositories, and when you're in production you're injecting Entity Framework repositories.

    In your case, it doesn't look like any of your examples are fully injecting dependencies. In other words, when you write

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

    you still have a dependency on the Json serializer by virtue of hard-coding the string, and it wouldn't make sense for StructureMap to ever return some other kind of serializer from that call.

    I can't tell exactly what you mean to accomplish with StructureMap, but if you need to return a particular serializer depending on a certain set of runtime conditions, you could look into conditional construction.

    On the other hand, it doesn't really sound like a switch of that sort is what you're going for here, so you should definitely consider getting rid of it. After all, the above code is really no different from

    new JsonSerializer();
    

    StructureMap is a wonderful tool, but it's not necessary for every project.

    Good luck!

    0 讨论(0)
  • 2021-02-09 20:49

    I am curious. What value a ISerializer on it's own is adding? Let's go from specific implementation to one or many picked at runtime.

    If your type is dependent on a specific type of serializer take a dependency on it (IJsonSerializer). This requires that a default instance of that type be registered with the container.

    However, if you are thinking more of having ISerializers as Strategies you would register all your ISerializers and then take a dependency on an array of them and StructureMap will push in an array of all registered ISerializers. The class consuming these serializers then is responsible for selecting which one to use.

    In the strategy scenario you'll likely need some metadata on the serializer for use by your coordinating class to discriminate between them. IMHO, this should really not be container configuration, like names on registered types, but metadata on the implementation itself.

    0 讨论(0)
提交回复
热议问题