C# NET中的接口分解/接口设计
我想设计一个由几个松散耦合的组件组成的内部框架 在一本书中,我发现了这样一个建议:最好用一个具体的类实现几个接口,而不要构建接口树 数据访问接口(系列)示例: 我的做法:C# NET中的接口分解/接口设计,c#,interface,components,C#,Interface,Components,我想设计一个由几个松散耦合的组件组成的内部框架 在一本书中,我发现了这样一个建议:最好用一个具体的类实现几个接口,而不要构建接口树 数据访问接口(系列)示例: 我的做法: interface ISession : IDisposable { void OpenSession(); void CloseSession(); } interface IDataAccess: ISession { void SetData(string data); string G
interface ISession : IDisposable
{
void OpenSession();
void CloseSession();
}
interface IDataAccess: ISession
{
void SetData(string data);
string GetData();
}
class MyTextFileAccess : IDataAccess
{
// no-op Open, Close, Dispose
}
class SQLDataAccess : IDataAccess
{
// all interface methods are really needed
}
书中似乎倾向的方法可能如下所示:
interface ISession
{
void OpenSession();
void CloseSession();
}
interface IDataAccess
{
void SetData(string data);
string GetData();
}
class MyTextFileAccess : IDataAccess
{
// don't have to implement unused interface methods
}
class SQLDataAccess : IDataAccess, IDisposable, ISession
{
// same as above
}
接口的使用也会有所不同:
void UseMyWay(IDataAccess da) // IDataAccess inherits from ISession and IDisposable
{
da.OpenSession();
da.GetData();
da.SetData("");
da.CloseSession();
da.Dispose();
}
void UseTheBooksWay(IDataAccess da) // IDataAccess doesn't inherit from ISession and IDisposable
{
var da_session = da as ISession;
var da_disposable = da as IDisposable;
if (da_session != null)
da_session.OpenSession();
da.GetData();
da.SetData("");
if (da_session != null)
da_session.CloseSession();
if (da_disposable != null)
da_disposable.Dispose();
}
我更喜欢我的方法中的用法,因为它是统一的,用户不必考虑对几个接口进行测试(仅通过查看IDataAccess
),因此不能忘记这样做
但我也更喜欢书中只涉及一个主题的简洁界面;这还有一个优点,即我不必实现显然不必要的接口(文本文件访问)。
我的方法中的“最终”接口需要了解所有实现,但这似乎并不正确,因为接口不应该依赖于实现细节。如果我想添加一个新的SecureDataAccess
实现,这可能是一个问题,它首先需要身份验证
是否有一种方法可以将这两种优势结合起来,或者您是否使用了其他方法?我不会说您是对的还是错的,这可能是使用哪种方法的间接原因。在您的方法中,如果您实现IDataAccess,则需要实现ISession和IDisposable,如果您始终需要,这是可以的 另一种方法具有优势,因为您可以独立使用所有3种方法,使其更加灵活。您可以使用IDataAccess和ISession,而不必实现IDiposable。我真的无法告诉您哪些可以使用,哪些不可以使用,因为您应该根据您的情景权衡优势/劣势
我认为,将这两种方法结合起来意味着在一个项目中,你有一些采用你的方法,一些采用书本方法,就个人而言,我会使用书本方法,就好像你走得更远,需要使用一种方法而不使用另一种方法一样,那么它可能会成为一个可维护性的问题,也许,您可以说您的方法与松散耦合的体系结构背道而驰 这是迄今为止我提出的最好的解决方案: 它可以从库端进行扩展,但从用户端也可以安全地进行扩展(用户端不知道应该使用哪些接口(
ISession
,IDisposable
…)
要点:
- 其基本思想是在消费者和图书馆之间建立一个类来定义合同,例如哪些接口可用/必须使用
- 如果合同不包括
和ISession
,则IDisposable
将不可用SQLDataAccess
- 通过此合同使用接口的客户机至少知道预期的接口,并且有机会正确使用这些接口
可以使用“简单”契约FileDataAccess
,并且在一些无操作实现的帮助下,可以使用更高级的PlainDataAccess
sessionedataaccess
本身不需要实现这些接口FileDataAccess
using System;
namespace InterfaceDesign
{
// the interface we're really interested in
public interface IDataAccess
{
string GetData();
void SetData(string data);
}
// a requirement for SQLDataAccess
public interface ISession
{
void OpenSession();
void CloseSession();
}
/// <summary>
/// This "interface for the consumer" is intended only for classes that have no special needs before/after accessing data
/// </summary>
public class PlainDataAccess
{
internal PlainDataAccess(IDataAccess dataAccess)
{
this.DataAccess = dataAccess;
}
public IDataAccess DataAccess { get; private set; }
}
/// <summary>
/// This "interface for the consumer" is intended for classes that have only the special needs to open/close sessions and be disposed.
/// </summary>
public class SessionedDataAccess
{
public IDataAccess DataAccess { get; private set; }
public ISession Session { get; private set; }
public IDisposable Disposable { get; private set; }
/// <summary>
/// Simplifies creation by not having to pass in the same variable 3 times
/// </summary>
internal static SessionedDataAccess Create<T>(T sessionedDataAccess) where T :IDataAccess, ISession, IDisposable
{
return new SessionedDataAccess(dataAccess: sessionedDataAccess , session: sessionedDataAccess , disposable: sessionedDataAccess);
}
private SessionedDataAccess(IDataAccess dataAccess, ISession session, IDisposable disposable)
{
this.DataAccess = dataAccess;
this.Session = session;
this.Disposable = disposable;
}
public static SessionedDataAccess Create(PlainDataAccess dataAccess)
{
return new SessionedDataAccess(dataAccess: dataAccess.DataAccess, session: NoOpSession.Instance,
disposable: NoOpDisposable.Instance);
}
private class NoOpSession : ISession
{
public static readonly ISession Instance = new NoOpSession();
public void OpenSession()
{
Console.WriteLine("no op session opened");
}
public void CloseSession()
{
Console.WriteLine("no op session closed");
}
}
private class NoOpDisposable : IDisposable
{
public static readonly IDisposable Instance = new NoOpDisposable();
public void Dispose()
{
Console.WriteLine("no op disposed");
}
}
}
// further "consumer interfaces" can be added. Extension methods provide easy promotions to more "demanding" interfaces by using no-op implementations.
/// <summary>
/// A sample class that has no special need for sessions etc.
/// </summary>
public class FileDataAccess : IDataAccess
{
private FileDataAccess()
{
}
public static PlainDataAccess Create()
{
return new PlainDataAccess(dataAccess: new FileDataAccess());
}
public string GetData()
{
return "from file";
}
public void SetData(string data)
{
Console.WriteLine("written to file");
}
}
/// <summary>
/// A more complex data access requiring sessions
/// </summary>
public class SQLDataAccess : IDataAccess, ISession, IDisposable
{
private SQLDataAccess()
{
}
public static SessionedDataAccess Create()
{
return SessionedDataAccess.Create(new SQLDataAccess());
}
public string GetData()
{
return "from sql";
}
public void SetData(string data)
{
Console.WriteLine("written to SQL");
}
public void OpenSession()
{
Console.WriteLine("session opened");
}
public void CloseSession()
{
Console.WriteLine("Session closed");
}
public void Dispose()
{
Console.WriteLine("disposed");
}
}
public static class Extensions
{
/// <summary>
/// allows to use a plain data access as a sessioned data access by providing no-op ISession and IDisposable implementations
/// </summary>
public static SessionedDataAccess Promote(this PlainDataAccess self)
{
return SessionedDataAccess.Create(dataAccess: self);
}
}
public class ConsumerExamples
{
public void Provider()
{
var fileAccess = FileDataAccess.Create();
var sqlDataAccess = SQLDataAccess.Create();
Consume1(sqlDataAccess);
//Consume2(sqlDataAccess); // this won't compile
Consume1(fileAccess.Promote());
Consume2(fileAccess);
}
/// <summary>
/// This consumer is prepared to handle sessions.
/// Can handle our sql data access and (after promotion) the basic file data access
/// </summary>
public void Consume1(SessionedDataAccess sessionedDataAccess)
{
sessionedDataAccess.Session.OpenSession();
Console.WriteLine(
sessionedDataAccess.DataAccess.GetData()
);
sessionedDataAccess.DataAccess.SetData("data");
sessionedDataAccess.Session.CloseSession();
sessionedDataAccess.Disposable.Dispose();
}
// can consume only file access
/// <summary>
/// This consumer is NOT prepared to handle sessions.
/// It can only handle the file access.
/// There is no way (without cheating) to pass in a SQL data access here
/// </summary>
public void Consume2(PlainDataAccess dataAccess)
{
Console.WriteLine(
dataAccess.DataAccess.GetData()
);
dataAccess.DataAccess.SetData("data");
}
}
}
使用系统;
名称空间接口设计
{
//我们真正感兴趣的界面
公共接口IDataAccess
{
字符串GetData();
void SetData(字符串数据);
}
//对SQLDataAccess的要求
公共接口会话
{
void OpenSession();
void CloseSession();
}
///
///此“消费者接口”仅适用于在访问数据之前/之后没有特殊需要的类
///
公共类数据访问
{
内部数据访问(IDataAccess数据访问)
{
this.DataAccess=DataAccess;
}
公共IDataAccess数据访问{get;private set;}
}
///
///此“使用者接口”用于仅具有打开/关闭会话和进行处置的特殊需要的类。
///
公共类SessionedDataAccess
{
公共IDataAccess数据访问{get;private set;}
公共ISession会话{get;private set;}
公共IDisposable一次性{get;private set;}
///
///通过不必三次传入同一变量简化创建
///
内部静态SessionedDataAccess创建(T SessionedDataAccess),其中T:IDataAccess、ISession、IDisposable
{
返回新的SessionedDataAccess(数据访问:SessionedDataAccess,会话:SessionedDataAccess,一次性:SessionedDataAccess);
}
专用会话数据访问(IDataAccess数据访问、ISession会话、IDisposable一次性)
{
this.DataAccess=DataAccess;
这个会话=会话;
这个.一次性的=一次性的;
}
公共静态会话数据访问创建(PlainDataAccess数据访问)
{
返回新的SessionedDataAccess(dataAccess:dataAccess.dataAccess,会话:NoOpSession.Instance,
一次性:NoOpDisposable.Instance);
}
私家课NoOpSession:ISession
{
public static readonly ISession实例=new NoOpSession();
public void OpenSession()
{
Console.WriteLine(“未打开操作会话”);
}
公共会话()
{
Console.WriteLine(“无操作会话关闭”);
}
}
私有类NoOpDisposable:IDisposable
{
public static readonly IDisposable Instance=new NoOpDisposable();
公共空间处置()
{
控制台。写入线(“无操作处理”);
}
}
}
//毛皮