3

假设我有一个抽象对象,可以由多个单独的插件作者实现。(例如,一个错误数据库连接)我不希望我的位的消费者必须处理每个特定的插件类型。

我还想将解析配置文件的过程与实际初始化数据库插件和其他类似事情的过程分开。

为此,我想出了这样的事情:

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 的特定实现者知道它需要从文件加载的配置中的哪些位,但是该库的用户应该与该信息隔离。

但是有一个空的界面对我来说似乎很奇怪。这种事情有意义还是我做错了什么?

4

2 回答 2

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 上实现的状态或逻辑数据(可能是所述的名称或其他标识符配置,或将其加载或持久保存到所选数据存储的方法)可以通过一些强制标准化来完成。

于 2013-01-22T01:23:36.313 回答
0

我认为这是完全正确的。我正在设计一个 API,调用者必须首先获取一个不透明的“会话”对象,然后将其传递给后续调用。

API 的不同实现会使用完全不同的会话对象实现,所以会话对象显然不是具有不同子类的抽象类;这是一个界面。由于会话对象对调用者没有可见的行为,在我看来,唯一的逻辑模型是没有成员的接口。

于 2016-07-18T08:30:37.767 回答