簡體   English   中英

在Autofac中使用多態時解析具體類型

[英]Resolving concrete types when using polymorphism in 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解決此問題嗎? 在這種情況下,它是正確的設計嗎?

同意Jogge的觀點,迭代IFileBackup會是一個更好的選擇,但是為每種類型創建接口都是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的依賴BackUpMechanismB 您可以通過引入另一層抽象來擺脫它,但是我不希望這樣做。 我主要關心的是使Caller更加強大。 您可能要引入重試邏輯,超時等。

嘗試使用名稱進行注冊,然后使用該名稱進行解析:

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

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

在運行時解析實例,而不是通過構造函數進行注入。 這樣,您就可以根據需要解析為相應的類型。 讓我知道這個是否奏效。

要使設計工作正常,您可以嘗試以下方法:

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或其他方法。 關鍵是,當您不打算更改實現時,就不需要多態。 在您的上下文中應該清楚嗎? 您嘗試將備份放到某個遠程存儲中,然后,如果備份失敗,則放到本地磁盤上。 您無需在這里隱藏含義。 我想這是您的邏輯,應該清楚,當您閱讀代碼時。 希望能幫助到你。

以我的經驗,最好為每種類型創建一個接口:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM