StructureMap通过注入解决依赖而不是服务定位

发布于 2024-08-27 08:23:55 字数 1038 浏览 13 评论 0原文

在我的项目中,我使用程序集扫描器注册了许多 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, ....)

And StructureMap 正确解决这种情况?或者我是否采取了错误的做法,也许我应该注册实现 ISerializer 的具体类型,然后专门用于

MySomeClass(JsonSerializer jsonSerializer, ....)

具体类中的某些内容?

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

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

Which then correctly registers

ISerializer (...ISerializer)
Scoped as:  Transient

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

And so forth.

Currently the only way I've been able to figure out how to resolve the serializer I want is to hardcode a service location call with

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

Now I know in my class that I specifically want the jsonSerializer so is there a way to configure a rule or similar that says for ISerializer's to connect the named instance based on the property name? So that I could have

MySomeClass(ISerializer jsonSerializer, ....)

And StructureMap correctly resolve this scenario? Or am I approaching this wrong and perhaps I should just register the concrete type that implements ISerializer and then just specifically use

MySomeClass(JsonSerializer jsonSerializer, ....)

for something along these lines with the concrete class?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

陈独秀 2024-09-03 08:23:55

当您进行依赖注入并需要能够创建给定接口的特殊类型实例时,建议的解决方案是创建专用工厂类。这允许您使用命名参数而无需实际注入容器。

示例

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

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 语句并将枚举值实际映射到您正在注册的类名。

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

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.

携余温的黄昏 2024-09-03 08:23:55

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

就您而言,您的任何示例似乎都没有完全注入依赖项。换句话说,当您编写时,

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

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

我无法确切地告诉您要使用 StructureMap 完成什么,但是如果您需要根据一组特定的运行时条件返回特定的序列化器,您可以查看 条件构造

另一方面,这听起来并不像您想要的那样,所以您绝对应该考虑摆脱它。毕竟,上面的代码确实与

new JsonSerializer();

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

祝你好运!

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!

从来不烧饼 2024-09-03 08:23:55

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

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

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

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>()));
狼性发作 2024-09-03 08:23:55

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

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

但是,如果您更多地考虑将 ISerializers 作为 策略,您将注册所有 ISerializers,然后依赖于它们的数组,StructureMap 将推入所有已注册 ISerializer 的数组。然后,使用这些序列化器的类负责选择要使用的序列化器。

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文