农民伯伯 谈 接口 [interface]

 

前言

     相信大家对接口是不陌生的,但是你真的理解什么是接口吗?真的能用好吗?我们口口声声说按接口编程,到底如何接口编程呢?接口编程的意义在哪呢?...对于接口的迷茫,经常在三层结构里面看到的,千篇一律的把每一个Dao都写一个接口,每个Service再写一个接口,因为他们看的例子就是这样的,网上很多例子都是这样的,这就叫按接口编程了?!心里没底,到下次自己写项目自己设计的时候再加上赶进度怕是没这么勤奋的复制粘贴了,原因还是没有明白接口到底有什么用!甚着感觉接口这玩意就像脱裤子放屁——多此一举!真的是这样么?那么,接下来我和大家一起来探讨关于接口的种种...

 

正文

     一、什么是接口、接口有什么用

          我们先看看别人是怎么说接口的,我收集总结了一下,仅列出以下五种说法:

          1.     接口的意义在于顶替多重继承。

          2.     接口的作用,一言以蔽之,就是标志类的类别(type of class)。把不同类型的类归于不同的接口,可以更好的管理他们。

          3.     接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致,使用接口可以使程序更加清晰和条理化。

          4.     接口就是定义了一个合同,实现这个接口的类都保证自己符合这个合同要求。

          5.     接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

          这里我就不评价这几种说法了,下面说说我理解的接口是什么样子的。这里拿电脑里的主板来讲,主板上有USB总线接口、基本外设接口(用来连接键盘、鼠标、打印机等传统外设)、驱动器接口(用来连接硬盘驱动器、光盘驱动器和软盘驱动器等)...很眼熟吧!!这些东西都是带接口两字的,我们称之为硬件接口或接口类型,在翻阅关于这些硬件借口资料的时候你经常会发现这些接口都是由许多有名的公司如Microsoft、IBM、Intel、Apple等公司共同约定、开发的一种标准!!例如:USB是Compaq、DEC、IBM、Intel、Microsoft、NEC(日本)、Nothern Telecom(加拿大)等7家公司与1994年11月联合开发的计算机串行接口总线标准;IEEE 1394是1986年由Apple公司和TI(德克萨克仪器)公司开发的高速串行接口标准,命名为“火线”(Fire Wire)等。那么为什么要制定这些标准呢?包括现在都在争的3G标准,更有专门的标准组织和标准委员会。全世界硬件厂商多不胜数,随便列几个:

          CPU:     Intel、AMD

          内存:     金士顿、黑金刚、宇瞻

          硬盘:     日立、希捷

          显示器:     飞利浦、三星、LG、明基、优派等,这么多厂商,这么多品牌,我们没有因为把飞利浦的显示器换成三星的电脑就不能用了,任意换硬盘、换内存,加显卡,接不同牌子的鼠标,用不同牌子的键盘,为什么没有问题?关键就在这里了——他们都遵循了标准,这些硬件都是按标准生产出来的!!所以我们用盗版的硬件(如 鼠标)也可以很爽,因为盗版他也遵循了标准!!可以说没有这些硬件标准——个人电脑也不能像今天如此普及!!现在我们再回过头来看接口,请告诉我你有什么感觉?我的感觉就是接口就是标准,或者称之为标准接口!!在硬件里面是,软件里面也是同样如此。好处是显而易见的,下面我们将上面的硬件接口“转换”成下面的软件接口的代码:

    #region CPU接口
    
public interface CPU接口 { }
    
public interface 针脚式 : CPU接口 { }
    
public interface 卡式 : CPU接口 { }
    
public interface 触点式 : CPU接口 { }
    
public interface 针脚式 : CPU接口 { }
    
public interface Socket478 : 针脚式 { }
    
public interface Socket754 : 针脚式 { }
    
public interface Socket940 : 针脚式 { }
    
#endregion

    
#region 内存接口
    
public interface 内存接口 { }
    
public interface I144Pin : 内存接口 { }
    
public interface I168Pin : 内存接口 { }
    
public interface I240Pin : 内存接口 { }
    
#endregion

    
#region 硬盘接口
    
public interface 硬盘接口 { }
    
public interface IDE : 硬盘接口 { }
    
public interface SCSI : 硬盘接口 { }
    
public interface SATA : 硬盘接口 { }
    
#endregion

    
public class 精英A780GM
    {
        
/// <summary>
        
/// 构造一块主板
        
/// </summary>
        
/// <param name="cpu">Socket AM2/AM2+</param>
        
/// <param name="hd">SATA接口</param>
        
/// <param name="ddr">DDR2</param>
        public 精英A780GM(Socket940 cpu, SATA hd, I240Pin ddr)
        {

        }
    }

     这款精英A78GM主板是我随便从中关村在线里面找的一块板子,而上面的接口就是许多厂商坐在一起约定出来的标准接口,当然这里只是例举了主板的部分组件,但是可以看到,主板厂商都是按标准来进行制造的,他们生产不担心你插什么样的牌子CPU、硬盘、内存到主板上,只要你符合这个标准接口就行!!需要说明的是,上面五种对于接口的说法都是有一定道理的,而这里,我认为接口可以是标准,接口的意义更大体现在制定标准上面!!

 

二、如何使用标准

     1.     标准接口

          在上面的例子中,制定标准体现出良好的兼容性,有效降低了组合成本,更促进厂商按照标准专注本身等,下面我们再从软件编程中找更加贴切的例子来说明这一点。在跨数据库或数据库切换的时候我们可以用标准的接口来约束和规范数据库操作,以达到无缝切换(实际中可能有部分需要特殊处理)和跨数据库应用。下面给出一段无缝切换数据库的例子:

    public interface IDAL
    {
        
/// <summary>
        
/// 根据主键删除数据
        
/// </summary>
        
/// <param name="pk">主键</param>
        void Delete(string pk);
    }

    
public class SqlDAL : IDAL
    {

        
#region IDAL 成员

        
public void Delete(string pk)
        {
            SqlHelper.ExecuteNonQuery(
string.Concat("DELETE TABLE [TEST] WHERE ID = ", pk));
        }

        
#endregion
    }

    
public class OracleDAL : IDAL
    {

        
#region IDAL 成员

        
public void Delete(string pk)
        {
            OracleHelper.ExecuteNonQuery(
string.Concat("DELETE TABLE [TEST] WHERE ID = ", pk));
        }

        
#endregion
    }

    
public class MySqlDAL : IDAL
    {

        
#region IDAL 成员

        
public void Delete(string pk)
        {
            MySqlHelper.ExecuteNonQuery(
string.Concat("DELETE TABLE [TEST] WHERE ID = ", pk));
        }
        
#endregion
    }

    
public class Business
    {
        
#region 变量

        
private static Type dalType;
        
private IDAL dal;

        
#endregion

        
#region 构造函数

        
public Business()
        {
            dal 
= Activator.CreateInstance(dalType) as IDAL;
        }

        
static Business()
        {

            //web.config:     <add key="DatabaseType" value="sqlserver" />
            
string DatabaseType = ConfigurationManager.AppSettings["DatabaseType"];
            
if (!string.IsNullOrEmpty(DatabaseType))
            {
                
switch (DatabaseType.ToLower())
                {
                    
case "sqlserver":
                        dalType 
= typeof(SqlDAL);
                        
break;
                    
case "mysql":
                        dalType 
= typeof(MySqlDAL);
                        
break;
                    
case "oracle":
                        dalType 
= typeof(OracleDAL);
                        
break;
                    
default:
                        dalType 
= typeof(SqlDAL);
                        
break;
                }
            }
            
else
                dalType 
= typeof(SqlDAL);
        }

        
#endregion

        
/// <summary>
        
/// 删除一笔数据
        
/// </summary>
        
/// <param name="id"></param>
        public void Remove(string id)
        {
            dal.Delete(id);
        }
    }

          注意:这段代码不考虑SQL语句安全、效率等问题,关键是体现接口的作用。

          说明:好处是显而易见的,可维护性高,这段业务代码在切换数据库时是不需要更改任何代码的,只需轻松的把web.config的DatabaseType指定为其他的数据库类型就行了。这得益于SqlDAL、MySqlDAL、OracleDAL都是按标准的方式来实现的数据库操作的。便于分工,我们可以把这三个类分别交给三个人精通各自数据库的人来编写,这样同时也将业务层和数据层解耦了,只要标准一出,数据层和业务层的员工就可以同时开始编写代码,业务层员工只管按标准调用,而数据层员工只管按标准来编写。

          有些朋友可能说,既然接口主要用于标准,我小型项目就没必要弄这么复杂了,但是我的朋友,你应该知道复用这回事吧,也就是说我做完这个项目我还得做下一个项目。如果我能够把这个项目的数据层直接移植到下个项目该多好,即使数据库改变也没有关系,假如你花心思设计好了我相信这不会很困难:)

     2.     参数传递

          对于接口的用途我最早是在java里面用于参数传递的,Java和C#都是强类型语言,也就是你传一个参数过来的时候需要明确指定一个类型。但是有一个类型非常特别,那就是如果我将参数的类型指定为object的时候,你不管传什么参数都可以,因为所有类型都继承自object!而将接口用于参数传递实现方式同object是一样的,只要你继承了你就可以被传输,所以大家经常能看到空的接口。接下来也会贴Java下使用Hibernate的一个例子 ,也是我第一次认识到接口作用的例子:

               DaoBase.java

public class DaoBase extends HibernateDaoSupport {

    
public boolean add(IModel model) throws MyException {
        
try {
            
this.getHibernateTemplate().save(model);
            
return true;
        } 
catch (Exception e) {
            
throw new MyException(e);
        }
    }
    
    
public boolean modified(IModel model)throws MyException {
        
try {
            
this.getHibernateTemplate().update(model);
            
return true;
        } 
catch (Exception e) {
            
throw new MyException(e);
        }
    }
}

               IModel.java

public interface IModel extends java.io.Serializable {

}

               Account.java

public class Account implements IModel {


    
// Fields    

     
private Integer id;
     
     
private String password;


    
// Constructors

    
/** default constructor */
    
public Account() {
    }

    
// Property accessors

    
public Integer getId() {
        
return this.id;
    }
    
    
public void setId(Integer id) {
        
this.id = id;
    }

    
public String getPassword() {
        
return this.password;
    }
    
    
public void setPassword(String password) {
        
this.password = password;
    }
}

          说明:DaoBaseHibernateDaoSupport在这里就不介绍了,主要是this.getHibernateTemplate()的两个方法saveupdate,这两个方法的所需参数均是Object,以前的做法就是每一个表写一个Dao,每个Dao里面写一个add方法,然后参数为特定Model或者说是VO,极其繁琐,经过这样改装后就可以有一个通用的Dao了,也减少了许多代码量,而且比起直接用Object参数更加安全,因为它帮助saveupdate明确指定了只有继承了这个接口的VO才能传递进来!

     3.     其他用法

          在继承IHttpHandler实现自己Handler的时候,如果我们需要用到Session就需要继承接口IRequiresSessionState或IReadOnlySessionState,需要注意的是这两个都是空接口,不知道大家有没有问个为什么!!接下来我和大家一起分析这一用法,在ASP.NET中使用AjaxPro时,有一步就是将方法标记AjaxMethod,如果需要在方法中使用Session需要如下标记:

          [AjaxPro.AjaxMethod(AjaxPro.HttpSessionStateRequirement.Read)]

          [AjaxPro.AjaxMethod(AjaxPro.HttpSessionStateRequirement.ReadWrite)]

          首先从web.config入手,在http节点下可以看到这段代码:

<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro.2"/>

          OK!从AjaxHandlerFactory入手,拿出Reflector,可以得到以下关键代码:

AjaxMethodAttribute[] customAttributes = (AjaxMethodAttribute[]) processorArray[i].Method.GetCustomAttributes(typeof(AjaxMethodAttribute), true);
if (customAttributes.Length > 0)
{
    if (customAttributes[0].RequireSessionState == HttpSessionStateRequirement.Read)
    {
          if (!customAttributes[0].UseAsyncProcessing)
           {
                 return new AjaxSyncHttpHandlerSessionReadOnly(processorArray[i]);
           }
           return new AjaxAsyncHttpHandlerSessionReadOnly(processorArray[i]);
     }
     if (customAttributes[0].RequireSessionState == HttpSessionStateRequirement.ReadWrite)
     {
          if (!customAttributes[0].UseAsyncProcessing)
          {
                return new AjaxSyncHttpHandlerSession(processorArray[i]);
          }
          return new AjaxAsyncHttpHandlerSession(processorArray[i]);
      }
 }

           注意红色代码部分,然后关注类AjaxSyncHttpHandlerSessionReadOnly和AjaxSyncHttpHandlerSession,我们看这两个类是干嘛的:

    public class AjaxSyncHttpHandlerSessionReadOnly : AjaxSyncHttpHandler, IReadOnlySessionState, IRequiresSessionState
    {
        public AjaxSyncHttpHandlerSessionReadOnly(IAjaxProcessor p) : base(p)
        {
        }
    }
    public class AjaxSyncHttpHandlerSession : AjaxSyncHttpHandler, IRequiresSessionState
    {
        public AjaxSyncHttpHandlerSession(IAjaxProcessor p) : base(p)
        {
        }
    }

      看到没有?!他分别继承了这两个接口,而普通的只是实现了IHttpHander接口,也就是能不能使用Session关键就在到底有没有继承这两个接口之一。但是我们使用Session仍然是从HttpContext.Current.Session取得,没有因为继没继承那两个接口之一而改变,所以问题应该在HttpContext里,我们把HttpContextReflectors出来,直接搜索这两个接口,果然大有斩获,能看到如下代码:

        public IHttpHandler Handler
        {
            
get
            {
                
return this._handler;
            }
            
set
            {
                
this._handler = value;
                
this.RequiresSessionState = false;
                
this.ReadOnlySessionState = false;
                
this.InAspCompatMode = false;
                
if (this._handler != null)
                {
                    
if (this._handler is IRequiresSessionState)
                    {
                        
this.RequiresSessionState = true;
                    }
                    
if (this._handler is IReadOnlySessionState)
                    {
                        
this.ReadOnlySessionState = true;
                    }
                    Page page 
= this._handler as Page;
                    
if ((page != null&& page.IsInAspCompatMode)
                    {
                        
this.InAspCompatMode = true;
                    }
                }
            }
        }

          看到这里虽然还没有完全水落石出,但是基本原理应该是明白了的,有需要深入的朋友可以看看HttpApplication和SessionStateModule等相关类。

          讲到这里基本上告一段了,看到评论里面仍然有人用肯定的词语“是”、“就是”来评论接口,我觉得是不恰当的,我用的是“可以”二字,因为我觉得接口可能还有其他作用,不仅仅只是约束和规范或者说是标准。当接口不为空的时候,我觉得接口可以说是标准约束,因为你继承了就必须实现接口里的东西,如方法;但是接口为空的时候请问你,你约束什么?第二种用法可以说得过去,约束了参数,但是第三种呢?有约束吗?我觉得就是纯粹的身份、标示或者理解为类似于AOP的功能,这对于我们不直接用new来获取对象实例的时候,比如用工厂来生成对象、通过其他对象生成,简称间接生成的时候使用是大有益处的,接口这个时候也作为一种手段来达到我的目的,而且很好用!!(最后修改 2009-2-6)

 

 

     后期维护
           2012-1-6  今天看Practical Java,发现Java里有一种叫标示接口(marker interface):即不含任何函数的一种接口,用来表示实现此接口的任何类一定具有某种属性(性质,propterty)。

 

结束

     写的时候苦于找不到合适的例子来说服自己,一直努力的阐述关于接口的所见所闻和所想,希望能带给你多一份关于接口的收获,热烈欢迎交流心得!!

posted @ 2009-02-05 00:05 农民伯伯 阅读(...) 评论(...) 编辑 收藏