假设我有一个抽象对象,可以由多个独立的插件作者实现。 (例如,一个bug数据库连接)我不希望我的位消费者必须处理每个特定的插件类型。
我还想将解析配置文件的过程与实际初始化数据库插件和其他类似事件的过程分开。
为此,我想出了类似的东西:
public interface IConfiguration
{
// No members
}
public interface IConnection
{
// Members go in here
void Create();
void Update();
void Delete();
}
public interface IConnectionProvider
{
// Try to interpret file as a configuration, otherwise return null
IConfiguration ParseConfiguration(Stream configurationContents);
IConnection Connect(IConfiguration settings);
}
public class ThingyRepository
{
// Lets say there is a constructor that initializes this with something
List<IConnectionProvider> providers;
// Insulates people from the actual connection provider
KeyValuePair<IConfiguration, IConnectionProvider> Parse(string filename)
{
IConnection result = null;
IConnectionProvider resultProvider = null;
foreach (var provider in this.providers)
{
using (Stream fs = OpenTheFileReadonly(filename))
{
IConnection curResult = provider.ParseConfiguration(fs);
if (curResult == null)
{
continue;
}
else
{
if (result == null)
{
result = curResult;
resultProvider = provider;
}
else
{
throw new Exception ("ambguity!");
}
}
}
}
if (result == null)
{
throw new Exception ("can't parse!");
}
return new KeyValuePair<IConfiguration, IConnectionProvider>(
result, resultProvider);
}
}
我的问题是,我有这个空接口,它应该作为从指定文件加载的任何设置的不透明句柄。 IConnectionProvider的具体实现者知道它在配置中需要从文件加载的位数,但是该库的用户应该与该信息隔离。
但是有一个空的界面对我来说似乎很奇怪。这种事情是否有意义或我做了一些可怕的错误?
答案 0 :(得分:2)
没有成员的接口的基本概念,简单地将实现者识别为是某事物而不是接口识别对象具有或做什么的正常工作,被称为“标记接口” 。它有它的用途,但要谨慎使用它们。例如,我通常以分层格式使用它们来识别应该持久保存到特定数据存储的域对象:
//no direct implementors; unfortunately an "abstract interface" is kind of redundant
//and there's no way to tell the compiler that a class inheriting from this base
//interface is wrong,
public interface IDomainObject
{
int Id {get;}
}
public interface IDatabaseDomainObject:IDomainObject { }
public interface ICloudDomainObject:IDomainObject { }
public class SomeDatabaseEntity:IDatabaseDomainObject
{
public int Id{get;set;}
... //more properties/logic
}
public class SomeCloudEntity:ICloudDomainObject
{
public int Id{get;set;}
... //more properties/logic
}
派生接口告诉我一个关于实现对象结构的新内容,除了该对象属于该特定子域,允许我进一步控制可以传递到哪里:
//I can set up a basic Repository pattern handling any IDomainObject...
//(no direct concrete implementors, though I happen to have an abstract)
public interface IRepository<T> where T:IDomainObject
{
public TDom Retrieve<TDom>(int id) where TDom:T;
}
//... Then create an interface specific to a sub-domain for implementations of
//a Repository for that specific persistence mechanism...
public interface IDatabaseRepository:IRepository<IDatabaseDomainObject>
{
//... which will only accept objects of the sub-domain.
public TDom Retrieve<TDom>(int id) where TDom:IDatabaseDomainObject;
}
可以在编译时检查生成的实现及其用法,以证明ICloudDomainObject没有传递给IDatabaseRepository,并且任何时候都不能将String或byte []传递到存储库进行存储。使用属性或属性无法实现此编译时安全性,这是将类“标记”为具有某些特殊意义的其他主要方法。
所以简而言之,它本身并不是糟糕的练习,但绝对会问自己你想从标志界面中得到什么,并问自己是否有任何状态或逻辑数据通常会在IConfiguration(可能是所述配置的名称或其他标识符,或者将其加载或保存到所选数据存储的方法)可以通过一些强制标准化来实现。
答案 1 :(得分:0)
我认为这完全有效。我正在设计一个API,调用者必须首先获得一个不透明的“会话”对象,然后将其传递给后续调用。
API的不同实现将使用完全不同的会话对象实现,因此会话对象显然不是具有不同子类的抽象类;这是一个界面。由于会话对象没有调用者可见的行为,因此在我看来,唯一的逻辑模型是没有成员的接口。