在Autofac中使用多态时解析具体类型

时间:2018-09-13 11:32:27

标签: c# oop polymorphism autofac

考虑以下代码:

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

所以我在这里要做的就是使用多态。因此BackupMechanismABackupMechanismB都以自己的方式实现了Backup方法。在调用方中,我想尝试第一种机制,如果该机制不起作用,我们会捕获异常并尝试第二种方法。

我无法使用Autofac解决正确的实现。我尝试过:

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

但是这行不通,因为我仍然需要告诉调用方要解析的类型。如何在呼叫者中做到这一点?

此外,我怀疑这种设计是否真的是正确的设计。在进行此设计之前,我只有一个类,其中包含两种不同的方法,一种用于机制A,一种用于机制B,然后调用方将在try catch中调用不同的方法。所以我想重构它,因为类很大,我想将两种不同的机制分成各自的类。

那么,我可以使用Autofac解决此问题吗?在这种情况下是正确的设计吗?

4 个答案:

答案 0 :(得分:1)

同意Jogge的观点,迭代IFileBackup会是更好的选择,但是为每种类型创建接口都是不可行的。相反,您可以添加一个提供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具有BackUpMechanismABackUpMechanismB的依存关系。您可以通过引入另一层抽象来摆脱它,但是我不希望这样做。我主要关心的是使Caller更强大。您可能要引入重试逻辑,超时等。

答案 1 :(得分:0)

尝试使用名称进行注册,然后使用该名称进行解析:

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

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

在运行时解析实例,而不是通过构造函数进行注入。这样,您就可以根据需要解析为相应的类型。 让我知道这是否有效。

答案 2 :(得分:0)

要使您的设计生效,您可以尝试以下方法:

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或其他方法。关键是,当您不打算更改实现时,就不需要多态。在您的上下文中应该清楚吗?您尝试将备份放到某个远程存储中,然后,如果备份失败,则放到本地磁盘上。您无需在这里隐藏含义。我想这是您的逻辑,应该清楚,当您阅读代码时。希望对您有所帮助。

答案 3 :(得分:-1)

根据我的经验,最好为每种类型创建一个接口:

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 { }
        }
    }
}