[英]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);
}
}
}
所以我在这里要做的就是使用多态。 因此, BackupMechanismA
和BackupMechanismB
都以自己的方式实现了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
具有BackUpMechanismA
和BackUpMechanismB
的依赖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.