1. 程式人生 > >介面和抽象類的區別---相信你看完不會再混淆了

介面和抽象類的區別---相信你看完不會再混淆了

一、引言

        本文主要是由一些個人觀點和網上搜集的資料整理而成。對於各位使用面向物件程式語言的程式設計師來說,“介面”這個名詞一定不陌生,但是不知各位有沒有這樣的疑惑:介面有什麼用途?讓它和抽象類有什麼區別?能不能用抽象類代替介面呢?而且,作為程式設計師,一定經常聽到“面向介面程式設計”這個短語,它是什麼意思,有什麼思想內涵?和麵向物件程式設計是什麼關係?本文將一一解答這些疑問。

二、面向介面程式設計和麵向物件程式設計是什麼關係

        首先,面向介面程式設計和麵向物件程式設計並不是平級的,它並不是比面向物件程式設計更先進的一種獨立的程式設計思想,而是附屬於面向物件思想體系,屬於其一部分。或者說,它是面向物件程式設計體系中的思想精髓之一。

三、介面的本質

介面,在表面上是由幾個沒有主體程式碼的方法定義組成的集合體,有唯一的名稱,可以被類或其他介面所實現(或者也可以說繼承)。它在形式上可能是如下的樣子:

public interface InterfaceName
{
    void Method1();
    void Method2(int para1);
    void Method3(String para2,String para3);
}

那麼,介面的本質是什麼呢?或者說介面存在的意義是什麼。我認為可以從以下兩個視角考慮:

1)介面是一組規則的集合,它規定了實現本介面的類或介面必須擁有的一組規則。體現了自然界“如果你是……則必須能……”的理念。

         例如,在自然界中,人都能吃飯,即“如果你是人,則必須能吃飯”。那麼模擬到計算機程式中,就應該有一個IPerson(習慣上,介面名由“I”開頭)介面,並有一個方法叫Eat(),然後我們規定,每一個表示“人”的類,必須實現IPerson介面,這就模擬了自然界“如果你是人,則必須能吃飯”這條規則。
         從這裡,我想各位也能看到些許面向物件思想的東西。面向物件思想的核心之一,就是模擬真實世界,把真實世界中的事物抽象成類,整個程式靠各個類的例項互相通訊、互相協作完成系統功能,這非常符合真實世界的執行狀況,也是面向物件思想的精髓。

2)介面是在一定粒度檢視上同類事物的抽象表示。注意這裡我強調了在一定粒度檢視上,因為“同類事物”這個概念是相對的,它因為粒度檢視不同而不同。

        例如,在我的眼裡,我是一個人,和一頭豬有本質區別,我可以接受我和我同學是同類這個說法,但絕不能接受我和一頭豬是同類。但是,如果在一個動物學家眼裡,我和豬應該是同類,因為我們都是動物,他可以認為“人”和“豬”都實現了IAnimal這個介面,而他在研究動物行為時,不會把我和豬分開對待,而會從“動物”這個較大的粒度上研究,但他會認為我和一棵樹有本質區別。
        現在換了一個遺傳學家,情況又不同了,因為生物都能遺傳,所以在他眼裡,我不僅和豬沒區別,和一隻蚊子、一個細菌、一顆樹、一個蘑菇乃至一個SARS病毒都沒什麼區別,因為他會認為我們都實現了IDescendable這個介面(注:descend vi. 遺傳),即我們都是可遺傳的東西,他不會分別研究我們,而會將所有生物作為同類進行研究,在他眼裡沒有人和病毒之分,只有可遺傳的物質和不可遺傳的物質。但至少,我和一塊石頭還是有區別的。可不幸的事情發生了,某日,地球上出現了一位偉大的人,他叫列寧,他在熟讀馬克思、恩格斯的辯證唯物主義思想鉅著後,頗有心得,於是他下了一個著名的定義:所謂物質,就是能被意識所反映的客觀實在。至此,我和一塊石頭、一絲空氣、一條成語和傳輸手機訊號的電磁場已經沒什麼區別了,因為在列寧的眼裡,我們都是可以被意識所反映的客觀實在。如果列寧是一名程式設計師,他會這麼說:所謂物質,就是所有同時實現了“IReflectabe”和“IEsse”兩個介面的類所生成的例項。
        也許你會覺得我上面的例子像在瞎掰,但是,這正是介面得以存在的意義。面向物件思想和核心之一叫做多型性,什麼叫多型性?說白了就是在某個粒度檢視層面上對同類事物不加區別的對待而統一處理。而之所以敢這樣做,就是因為有介面的存在。像那個遺傳學家,他明白所有生物都實現了IDescendable介面,那隻要是生物,一定有Descend()這個方法,於是他就可以統一研究,而不至於分別研究每一種生物而最終累死。
         可能這裡還不能給你一個關於介面本質和作用的直觀印象。那麼在後文的例子和對幾個設計模式的解析中,你將會更直觀體驗到介面的內涵。

四、面向介面程式設計綜述

          通過上文,我想大家對介面和介面的思想內涵有了一個瞭解,那麼什麼是面向介面程式設計呢?我個人的定義是:在系統分析和架構中,分清層次和依賴關係,每個層次不是直接向其上層提供服務(即不是直接例項化在上層中),而是通過定義一組介面,僅向上層暴露其介面功能,上層對於下層僅僅是介面依賴,而不依賴具體類。
        這樣做的好處是顯而易見的,首先對系統靈活性大有好處。當下層需要改變時,只要介面及介面功能不變,則上層不用做任何修改。甚至可以在不改動上層程式碼時將下層整個替換掉,就像我們將一個WD的60G硬碟換成一個希捷的160G的硬碟,計算機其他地方不用做任何改動,而是把原硬碟拔下來、新硬碟插上就行了,因為計算機其他部分不依賴具體硬碟,而只依賴一個IDE介面,只要硬碟實現了這個介面,就可以替換上去。從這裡看,程式中的介面和現實中的介面極為相似,所以我一直認為,介面(interface)這個詞用的真是神似!使用介面的另一個好處就是不同部件或層次的開發人員可以並行開工,就像造硬碟的不用等造CPU的,也不用等造顯示器的,只要介面一致,設計合理,完全可以並行進行開發,從而提高效率。
        本篇文章先到這裡。最後我想再囉嗦一句:面向物件的精髓是模擬現實,這也可以說是我這篇文章的靈魂。所以,多從現實中思考面向物件的東西,對提高系統分析設計能力大有脾益。

五、對本文的補充

  • 關於“面向介面程式設計”中的“介面”與具體面向物件語言中“介面”兩個詞

看到有朋友提出“面向介面程式設計”中的“介面”二字應該比單純程式語言中的interface範圍更大。我經過思考,覺得很有道理。這裡我寫的確實不太合理。我想,面嚮物件語言中的“介面”是指具體的一種程式碼結構,例如C#中用interface關鍵字定義的介面。而“面向介面程式設計”中的“介面”可以說是一種從軟體架構的角度、從一個更抽象的層面上指那種用於隱藏具體底層類和實現多型性的結構部件。從這個意義上說,如果定義一個抽象類,並且目的是為了實現多型,那麼我認為把這個抽象類也稱為“介面”是合理的。但是用抽象類實現多型合理不合理?在下面第二條討論。概括來說,我覺得兩個“介面”的概念既相互區別又相互聯絡。面向介面程式設計”中的介面是一種思想層面的用於實現多型性、提高軟體靈活性和可維護性的架構部件,而具體語言中的“介面”是將這種思想中的部件具體實施到程式碼裡的手段。

  • 關於抽象類與介面
        如果單從具體程式碼來看,對這兩個概念很容易模糊,甚至覺得介面就是多餘的,因為單從具體功能來看,除多重繼承外(C#,Java中),抽象類似乎完全能取代介面。但是,難道介面的存在是為了實現多重繼承?當然不是。我認為,抽象類和介面的區別在於使用動機。使用抽象類是為了程式碼的複用,而使用介面的動機是為了實現多型性。所以,如果你在為某個地方該使用介面還是抽象類而猶豫不決時,那麼可以想想你的動機是什麼。
        看到有朋友對IPerson這個介面的質疑,我個人的理解是,IPerson這個介面該不該定義,關鍵看具體應用中是怎麼個情況。如果我們的專案中有Women和Man,都繼承Person,而且Women和Man絕大多數方法都相同,只有一個方法DoSomethingInWC()不同(例子比較粗俗,各位見諒),那麼當然定義一個AbstractPerson抽象類比較合理,因為它可以把其他所有方法都包含進去,子類只定義DoSomethingInWC(),大大減少了重複程式碼量。但是,如果我們程式中的Women和Man兩個類基本沒有共同程式碼,而且有一個PersonHandle類需要例項化他們,並且不希望知道他們是男是女,而只需把他們當作人看待,並實現多型,那麼定義成介面就有必要了。
         總而言之,介面與抽象類的區別主要在於使用的動機,而不在於其本身。而一個東西該定義成抽象類還是介面,要根據具體環境的上下文決定。再者,我認為介面和抽象類的另一個區別在於,抽象類和它的子類之間應該是一般和特殊的關係,而介面僅僅是它的子類應該實現的一組規則。(當然,有時也可能存在一般與特殊的關係,但我們使用介面的目的不在這裡)如,交通工具定義成抽象類,汽車、飛機、輪船定義成子類,是可以接受的,因為汽車、飛機、輪船都是一種特殊的交通工具。再譬如Icomparable介面,它只是說,實現這個介面的類必須要可以進行比較,這是一條規則。如果Car這個類實現了Icomparable,只是說,我們的Car中有一個方法可以對兩個Car的例項進行比較,可能是比哪輛車更貴,也可能比哪輛車更大,這都無所謂,但我們不能說“汽車是一種特殊的可以比較”,這在文法上都不通。

六、舉使用抽象類的和介面應用場景的例子

抽象類

學習java過java的人多多少少了解Android中的一些知識,因此這裡舉幾個Android中常用的例子。

Android中ListView作為開發最常用的元件之一,我們經常要使用自定義的Adapter來達到我們的目的,因此寫一個自己的BaseAdapter是很常見的事情,而重寫BaseAdapter的目的就是為了少寫重複程式碼和實現自己想要的getview()函式,這裡我們看看這個抽象類:

package com.winton.basemodule;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public abstract class NewBaseAdapter<T> extends BaseAdapter {

	public List<T> sourceList;
	public LayoutInflater inflater;
	public Context mContext;
	
	public NewBaseAdapter(List<T> list ,Context context) {
		// TODO Auto-generated constructor stub
		sourceList=list;
		mContext=context;
		inflater=LayoutInflater.from(context);
	}
	
	public void updateListData(List<T> list){
		sourceList=list;
		notifyDataSetChanged();
	}
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return sourceList==null?0:sourceList.size();
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return sourceList.get(position);
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	abstract public View getView(int position, View convertView, ViewGroup parent);
	
}

我們再來看看介面,介面的目的是為了讓實現介面的物件一不同方式實現同一方法,而不是為了減少重複程式碼,這裡舉一個Cache的程式碼,這一介面是制定了快取物件的規則,即實現快取的物件必須要實現接口裡定義的方法,這樣才能保證快取功能的完整:

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

import java.util.Collections;
import java.util.Map;

/**
 * An interface for a cache keyed by a String with a byte array as data.
 */
public interface Cache {
    /**
     * Retrieves an entry from the cache.
     * @param key Cache key
     * @return An {@link Entry} or null in the event of a cache miss
     */
    public Entry get(String key);

    /**
     * Adds or replaces an entry to the cache.
     * @param key Cache key
     * @param entry Data to store and metadata for cache coherency, TTL, etc.
     */
    public void put(String key, Entry entry);

    /**
     * Performs any potentially long-running actions needed to initialize the cache;
     * will be called from a worker thread.
     */
    public void initialize();

    /**
     * Invalidates an entry in the cache.
     * @param key Cache key
     * @param fullExpire True to fully expire the entry, false to soft expire
     */
    public void invalidate(String key, boolean fullExpire);

    /**
     * Removes an entry from the cache.
     * @param key Cache key
     */
    public void remove(String key);

    /**
     * Empties the cache.
     */
    public void clear();

    /**
     * Data and metadata for an entry returned by the cache.
     */
    public static class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** The last modified date for the requested object. */
        public long lastModified;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map<String, String> responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

}

七、結尾

學習的路上雖然很枯燥,但每進步一點,就能快樂一些。歡迎小夥伴們和我分享快樂。

/*

QQ討論群組:372702757

*