【问题标题】:Resolving concrete types when using polymorphism in Autofac在 Autofac 中使用多态性时解析具体类型
【发布时间】:2018-09-13 11:32:26
【问题描述】:

考虑以下代码:

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 是的,但我改变了我的设计,所以我没有使用多态性。我接受了我认为最好的答案

标签: c# oop polymorphism autofac


【解决方案1】:

同意 Jogge 的观点,即迭代 IFileBackups 会是一个更好的选择,但是为每种类型创建一个接口是不行的。相反,您可以添加一个提供IEnumerable&lt;IFileBackup&gt;(聚合)的类。例如:

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 更加健壮。您可能想要引入重试逻辑、超时等。

【讨论】:

    【解决方案2】:

    尝试使用名称注册,然后使用名称解决:

    builder.RegisterType<BackupMechanismA>().Named<IFileBackup>("BackUpMechanismA");
    builder.RegisterType<BackupMechanismB>().Named<IFileBackUp>("BackUpMechanismB");
    
    _backupA = container.ResolveNamed<IFileBackUp> 
    ("BackUpMechanismA"); 
    _backupB = container.ResolveNamed<IFileBackUp> 
    ("BackUpMechanismB");
    

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

    【讨论】:

      【解决方案3】:

      为了使您的设计工作,您可以尝试下一种方法:

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-11
          • 1970-01-01
          • 1970-01-01
          • 2021-09-16
          • 1970-01-01
          • 2015-05-11
          • 2019-06-21
          • 1970-01-01
          相关资源
          最近更新 更多