【发布时间】:2017-06-28 04:56:32
【问题描述】:
我正在为多个源代码托管商编写一个离线备份工具(在 C#/.NET Core 中,如果这很重要),例如GitHub 和 Bitbucket。
每个宿主(GithubHoster、BitbucketHoster 等)都会有一个实现相同接口的类。
我希望该工具具有可扩展性,因此只需创建一些由 IoC 自动注册自动获取的类,就可以轻松添加更多主机。
对于每个主机,该工具必须:
- 在工具的配置文件中验证该主机的设置
- 连接到托管商的 API 并获取存储库 URL 列表
- 执行适当的源代码控制工具将所有存储库克隆/拉取到本地计算机
这显然太多了,不能放在一个单独的类中,所以我使用组合(或者我认为组合的意思)将它分成子部分:
interface IHoster
{
IConfigSourceValidator Validator { get; }
IHosterApi Api { get; }
IBackupMaker BackupMaker { get; }
}
interface IConfigSourceValidator
{
Validate();
}
interface IHosterApi
{
GetRepositoryList();
}
interface IBackupMaker
{
Backup();
}
问题:为了将子类注入到IHoster实现中,我不能直接使用上面显示的子接口,因为这样容器就不知道要注入哪个实现了。
所以我需要创建一些更多的空标记界面,特别是为此目的:
interface IGithubConfigSourceValidator : IConfigSourceValidator
{
}
interface IGithubHosterApi : IHosterApi
{
}
interface IGithubBackupMaker : IBackupMaker
{
}
...所以我可以这样做:
class GithubHoster : IHoster
{
public GithubHoster(IGithubConfigSourceValidator validator, IGithubHosterApi api, IGithubBackupMaker backupMaker)
{
this.Validator = validator;
this.Api = api;
this.BackupMaker = backupMaker;
}
public IConfigSourceValidator Validator { get; private set; }
public IHosterApi Api { get; private set; }
public IBackupMaker BackupMaker { get; private set; }
}
...容器知道要使用哪些实现。
当然我需要实现子类:
class GithubConfigSourceValidator : IGithubConfigSourceValidator
{
public void Validate()
{
// do stuff
}
}
// ...and the same for GithubHosterApi and GithubBackupMaker
到目前为止这工作,但不知何故感觉不对。
我有“基本”接口:
IHoster
IConfigSourceValidator
IHosterApi
IBackupMaker
..以及 GitHub 的所有类和接口:
IGithubConfigSourceValidator
IGithubApi
IGithubBackupMaker
GithubHoster
GithubConfigSourceValidator
GithubApi
GithubBackupMaker
以后每次添加新的托管商时,我都必须重新创建所有这些:
IBitbucketConfigSourceValidator
IBitbucketApi
IBitbucketBackupMaker
BitbucketHoster
BitbucketConfigSourceValidator
BitbucketApi
BitbucketBackupMaker
我这样做对吗?
我知道我需要所有的类,因为我使用的是组合而不是将所有东西都放在一个神类中(而且它们更容易测试,因为它们每个只做一件事)。
但我不喜欢必须为每个 IHoster 实现创建的额外接口。
也许将来将我的托管程序分成更多的子类是有意义的,然后我将在每个实现中拥有四五个这样的接口。
...这意味着在实现了对一些额外主机的支持之后,我最终会得到 30 个或更多的空接口。
@NightOwl888 要求的其他信息:
我的应用程序支持从多个源代码托管方进行备份,因此它可以在运行时使用多个 IHoster 实现。
您可以将多个主机的用户名等放入配置文件中,如"hoster": "github"、"hoster": "bitbucket" 等。
所以我需要一个工厂,它从配置文件中获取字符串github 和bitbucket,并返回GithubHoster 和BitbucketHoster 实例。
我希望IHoster 实现提供自己的字符串值,这样我就可以轻松地自动注册它们。
因此,GithubHoster 有一个带有字符串github 的属性:
[Hoster(Name = "github")]
internal class GithubHoster : IHoster
{
// ...
}
这里是工厂:
internal class HosterFactory : Dictionary<string, Type>, IHosterFactory
{
private readonly Container container;
public HosterFactory(Container container)
{
this.container = container;
}
public void Register(Type type)
{
if (!typeof(IHoster).IsAssignableFrom(type))
{
throw new InvalidOperationException("...");
}
var attribute = type.GetTypeInfo().GetCustomAttribute<HosterAttribute>();
this.container.Register(type);
this.Add(attribute.Name, type);
}
public IHoster Create(string hosterName)
{
Type type;
if (!this.TryGetValue(hosterName, out type))
{
throw new InvalidOperationException("...");
}
return (IHoster)this.container.GetInstance(type);
}
}
注意:如果你想看真正的代码,我的项目在 GitHub 上是公开的,the real factory is here
在启动时,我从 Simple Injector 和 register each one in the factory 获得所有 IHoster 实现:
var hosterFactory = new HosterFactory(container);
var hosters = container.GetTypesToRegister(typeof(IHoster), thisAssembly);
foreach (var hoster in hosters)
{
hosterFactory.Register(hoster);
}
为了给予应有的荣誉,工厂是用Steven 的Steven 的a lot of help 创建的,Simple Injector 的创建者。
【问题讨论】:
-
你现在使用什么容器?
标签: c# oop dependency-injection composition single-responsibility-principle