Разрешение конкретных типов при использовании полиморфизма в Autofac - PullRequest
0 голосов
/ 13 сентября 2018

Рассмотрим следующий код:

public interface IFileBackup
{
    Task Backup(byte[] file);
}

public class BackUpMechanismA : IFileBackup
{
    //Implementation
    public async Task Backup(byte[] file)
    {
        //Attempts to backup using mechanism A
    }
}

public class BackUpMechanismB : IFileBackup
{
    //Implementation
    public async Task Backup(byte[] file)
    {
        //Attempts to backup using mechanism B
    }
}

Тогда вызывающий класс выглядит следующим образом:

public class Caller
{
    private readonly IFileBackup _backupA;
    private readonly IFileBackup _backupB;

    public Caller(IFileBackup backupA, IFileBackup backupB)
    {
        _backupA = backupA;
        _backupB = backupB;
    }


     public async Task BackupFile(byte[] file)
     {
         try
         {
             await _backupA.Backup(file);
         }
         catch(SomeException)
         {
             await _backupB.Backup(file);
         }
     }
}

Так что я пытаюсь здесь сделать, это использовать полиморфизм.Таким образом, BackupMechanismA и BackupMechanismB реализуют метод Backup по-своему.В вызывающей программе я хочу попробовать первый механизм, и если он не работает, мы ловим исключение и пробуем второй подход.

У меня проблемы с разрешением правильных реализаций с использованием Autofac.Я попытался с:

builder.RegisterType<BackupMechanismA>().As<IFileBackup>().AsSelf(); builder.RegisterType<BackupMechanismB>().As<IFileBackUp>().AsSelf();

Но это не сработает, потому что мне все еще нужно сказать вызывающей стороне, какой из типов разрешить.Как мне это сделать в вызывающей программе?

Кроме того, я сомневаюсь, действительно ли этот дизайн подходит для этого.До этого проекта у меня был только один класс с двумя разными методами, один для механизма A и один для механизма B, а затем вызывающая сторона просто вызывала различные методы в try catch.Поэтому я хотел реорганизовать это, потому что класс стал довольно большим, и я хотел разделить два разных механизма на их собственные классы.

Итак, могу ли я решить эту проблему с помощью Autofac?И это правильный дизайн для этого сценария?

Ответы [ 4 ]

0 голосов
/ 16 сентября 2018

Чтобы ваш дизайн работал, вы можете попробовать следующий подход:

static void Main(string[] args)
{
    var builder = new ContainerBuilder();
    builder.RegisterType<BackUpMechanismA>().Keyed<IFileBackup>("A");
    builder.RegisterType<BackUpMechanismB>().Keyed<IFileBackup>("B");
    builder.RegisterType<Caller>()
            .WithParameter((p, ctx) => p.Position == 0, (p, ctx) => ctx.ResolveKeyed<IFileBackup>("A"))
            .WithParameter((p, ctx) => p.Position == 1, (p, ctx) => ctx.ResolveKeyed<IFileBackup>("B"));
    IContainer container = builder.Build();

    var caller = container.Resolve<Caller>();

    Console.ReadKey();
}

Однако, на мой взгляд, вам, вероятно, не нужен такой полиморфизм здесь.Будет гораздо очевиднее и нагляднее реализовать что-то вроде этого:

public async Task BackupFile(byte[] file)
{
    try
    {
        await BackUpToAmazonS3(file);
    }
    catch (AmazonS3LoadingException)
    {
        await BackUpToLocalDisk(file);
    }
}

В этом примере очевидно, что происходит.И в BackUpToAmazonS3 вы можете использовать некоторые введенные AmazonS3FileBackUp, а в BackUpToLocalDisk используйте LocalDiskFileBackUp или что-то еще.Дело в том, что вам не нужен полиморфизм, когда вы не планируете менять реализацию.В твоем контексте это должно быть понятно?что вы пытаетесь поместить резервную копию в какое-то удаленное хранилище, а затем, в случае сбоя, поместить на локальный дискВам не нужно скрывать значение здесь.Это ваша логика и должно быть понятно, когда вы читаете код, я полагаю.Надеюсь, это поможет.

0 голосов
/ 13 сентября 2018

Попробуйте зарегистрироваться с именем, а затем разрешите, используя имя:

builder.RegisterType<BackupMechanismA>().Named<IFileBackup>("BackUpMechanismA");
builder.RegisterType<BackupMechanismB>().Named<IFileBackUp>("BackUpMechanismB");

_backupA = container.ResolveNamed<IFileBackUp> 
("BackUpMechanismA"); 
_backupB = container.ResolveNamed<IFileBackUp> 
("BackUpMechanismB");

Разрешайте экземпляры во время выполнения, а не вводите через конструктор.Это позволит вам разрешить соответствующий тип при необходимости.Дайте мне знать, если это работает.

0 голосов
/ 14 сентября 2018

Согласитесь с Jogge, что итерация IFileBackup s была бы лучшим вариантом, но создание интерфейса для каждого типа не требуется.Вместо этого вы можете добавить класс, который обеспечивает IEnumerable<IFileBackup> (совокупность).Например:

public class BackupBundle : IEnumerable<IFileBackup>
{
    private readonly List<IFileBackup> _backups = new List<IFileBackup>();

    // default constructor creates default implementations
    public BackupBundle()
        : this(new List<IFileBackup> {new BackUpMechanismA(), new BackUpMechanismB()}) {}

    // allow users to add custom backups
    public BackupBundle(IEnumerable<IFileBackup> backups)
    {
        foreach (var backup in backups)
            Add(backup);
    }

    public void Add(IFileBackup backup)
    {
        if (backup == null) throw new ArgumentNullException(nameof(backup));
        _backups.Add(backup);
    }

    public IEnumerator<IFileBackup> GetEnumerator()
    {
        foreach (var backup in _backups)
            yield return backup;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class Caller
{
    private readonly IEnumerable<IFileBackup> _backups;

    public Caller(IEnumerable<IFileBackup> backups)
    {
        _backups = backups ?? throw new ArgumentNullException(nameof(backups));
    }

    public async Task BackupFile(byte[] file)
    {
        foreach (var b in _backups)
        {
            try
            {
                await b.Backup(file);
                break;
            }
            catch (Exception e) { }
        }
    }
}

Регистрация может быть выполнена следующим образом:

builder.RegisterInstance(new BackupBundle()).As<IEnumerable<IFileBackup>>();
builder.RegisterType<Caller>();

, что позволяет разрешить по имени класса:

var caller = scope.Resolve<Caller>();

Как видите,BackupBundle имеет зависимость BackUpMechanismA и BackUpMechanismB.Вы можете избавиться от этого, введя еще один уровень абстракции, но я бы предпочел этого не делать.Моя главная задача - сделать Caller более надежным.Возможно, вы захотите ввести логику повтора, время ожидания и т. Д.

0 голосов
/ 13 сентября 2018

По моему опыту, вам лучше создать интерфейс для каждого типа:

public interface IFileBackup
{
    Task Backup(byte[] file);
}

public interface IBackUpMechanismA : IFileBackup
{
}

public class BackUpMechanismA : IBackUpMechanismA
{
    //...
}

public interface IBackUpMechanismB : IFileBackup
{
}

public class BackUpMechanismB : IBackUpMechanismB
{
    //...
}

Если вы этого не хотите, вы можете получить список IFileBackupи просто повторять их.Если вы зарегистрируете BackUpMechanismA первым, он будет первым в списке.Я не уверен, что это гарантировано, вы должны посмотреть.

public class Caller
{
    private readonly ICollection<IFileBackup> _fileBackups;

    public Caller(ICollection<IFileBackup> fileBackups)
    {
        _fileBackups = fileBackups;
    }

    public async Task BackupFile(byte[] file)
    {
        foreach (var fileBackup in _fileBackups)
        {
            try
            {
                await fileBackup.Backup(file);
                break;
            }
            catch { }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...