前言
相信大家對接口是不陌生的,但是你真的理解什麼是接口嗎?真的能用好嗎?我們口口聲聲說按接口 編程,到底如何接口編程呢?接口編程的意義在哪呢?...對於接口的迷茫,經常在三層結構裡面看到的 ,千篇一律的把每一個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;
}
}
說明:DaoBase中HibernateDaoSupport在這裡就不介紹了,主要是this.getHibernateTemplate()的兩 個方法save和update,這兩個方法的所需參數均是Object,以前的做法就是每一個表寫一個Dao,每個Dao 裡面寫一個add方法,然後參數為特定Model或者說是VO,極其繁瑣,經過這樣改裝後就可以有一個通用的 Dao了,也減少了許多代碼量,而且比起直接用Object參數更加安全,因為它幫助save和update明確指定 了只有繼承了這個接口的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裡,我們把HttpContext也Reflectors出來,直接搜索這兩個接口,果然大有斬獲,能看到如 下代碼:
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 來獲取對象實例的時候,比如用工廠來生成對象、通過其他對象生成,簡稱間接生成的時候使用是大有益 處的,接口這個時候也作為一種手段來達到我的目的,而且很好用!!
結束
寫的時候苦於找不到合適的例子來說服自己,一直努力的闡述關於接口的所見所聞和所想,希望能帶 給你多一份關於接口的收獲,熱烈歡迎交流心得!!