StructureMap разрешает зависимость посредством внедрения вместо расположения службы - PullRequest
6 голосов
/ 28 марта 2010

В моем проекте я регистрирую множество реализаций 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, ....)

что-то в этом роде с конкретным классом?

Ответы [ 4 ]

5 голосов
/ 07 апреля 2010

Когда вы выполняете Dependency Injection и вам необходимо создавать экземпляры определенного типа для определенного интерфейса, рекомендуется создать специализированных фабричных классов.Это позволяет вам использовать именованный аргумент без фактического внедрения контейнера.

Пример

Это абстрактный тип, который вы будете вводить:

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и фактически сопоставлять значения перечисления с именами классов, которые вы регистрируете.

Я бы, вероятно, поместил весь этот код - включая код автоматической регистрации в вашем вопросе - в то же пространство имен или даже в то же самоекод фиle, чтобы ясно указать, что все эти части взаимозависимы.

2 голосов
/ 28 марта 2010

Насколько я знаю, это не совсем то, для чего предназначена функциональность сканирования сборок. Это более полезно, когда одна сборка имеет множество реализаций различных интерфейсов (например, IRepository<File>, IRepository<Folder> и т. Д.). Так, например, когда вы ссылаетесь на свою тестовую сборку, вы вводите тестовые репозитории, а когда вы работаете, вы вводите репозитории Entity Framework.

В вашем случае, похоже, что ни один из ваших примеров не вводит полностью зависимости. Другими словами, когда вы пишете

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

вы все еще зависите от сериализатора Json благодаря жесткому кодированию строки, и для StructureMap не имеет смысла возвращать какой-либо другой тип сериализатора из этого вызова.

Я не могу точно сказать, что вы хотите сделать с помощью StructureMap, но если вам нужно вернуть конкретный сериализатор в зависимости от определенного набора условий выполнения, вы можете изучить условную конструкцию .

С другой стороны, на самом деле это не похоже на то, что вы собираетесь использовать здесь, поэтому вам определенно стоит подумать об этом. Ведь приведенный выше код действительно ничем не отличается от

new JsonSerializer();

StructureMap - замечательный инструмент, но он не необходим для каждого проекта.

Удачи!

1 голос
/ 07 апреля 2010

Мне любопытно. Какую ценность добавляет ISerializer? Давайте перейдем от конкретной реализации к одной или нескольким выбранным во время выполнения.

Если ваш тип зависит от определенного типа сериализатора, возьмите зависимость от него (IJsonSerializer). Для этого требуется, чтобы экземпляр этого типа по умолчанию был зарегистрирован в контейнере.

Однако, если вы больше думаете о том, чтобы иметь ISerializer как Стратегии , вы должны зарегистрировать все свои ISerializer и затем получить зависимость от их массива, и StructureMap добавит массив всех зарегистрированных ISerializer. Класс, потребляющий эти сериализаторы, отвечает за выбор того, какой из них использовать.

В сценарии стратегии вам, вероятно, понадобятся некоторые метаданные на сериализаторе для использования вашим координирующим классом, чтобы различать их. ИМХО, это действительно должна быть не конфигурация контейнера, как имена на зарегистрированных типах, а метаданные на самой реализации.

1 голос
/ 29 марта 2010

Поскольку ваш код предполагает, что он получает JsonSerializer, создайте новый интерфейс IJsonSerializer, который реализует только JsonSerializer. Любой класс, которому нужен JsonSerializer, должен принимать IJsonSerializer. Если вам все еще нужно, чтобы интерфейс ISerializer был общим для всех сериализаторов, IJsonSerializer можно использовать просто как интерфейс маркера.

В качестве альтернативы, вы можете привязать конкретную реализацию ISerializer к вашему классу при регистрации вашего класса в StructureMap.

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