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();
公共空间处置()
{
控制台。写入线(“无操作处理”);
}
}
}
//毛皮