接口成員的顯示實現

(interface)用來定義一種程序的協定。實現接口的類或者結構要與接口的定義嚴格一致。在前面的文章中,我們已經對C#接口的概念,如何定義接口以及如何對接口進行訪問等問題進行了詳細的討論。在這些知識的基礎上,本文我們將來了解實現接口的方法。

 

顯式實現接口成員

爲了實現接口,類可以定義顯式接口成員執行體(Explicit interface member

implementations)。顯式接口成員執行體可以是一個方法、一個屬性、一個事件或者是一個索引指示器的定義,定義與該成員對應的全權名應保持一致。

using System ;
interface ICloneable {
    object Clone( ) ;
}
interface IComparable {
    int CompareTo(object other) ;
}
class ListEntry: ICloneable, IComparable {
    object ICloneable.Clone( ) {…}
    int IComparable.CompareTo(object other) {…}
}

上面的代碼中ICloneable.Clone 和IComparable.CompareTo 就是顯式接口成員執行體。

說明:

· 不能在方法調用、屬性訪問以及索引指示器訪問中通過全權名訪問顯式接口成員執行體。事實上,顯式接口成員執行體只能通過接口的實例,僅僅引用接口的成員名稱來訪問。

· 顯式接口成員執行體不能使用任何訪問限制符,也不能加上abstract, virtual, override或static 修飾符。

· 顯式接口成員執行體和其他成員有着不同的訪問方式。因爲不能在方法調用、屬性訪問以及索引指示器訪問中通過全權名訪問,顯式接口成員執行體在某種意義上是私有的。但它們又可以通過接口的實例訪問,也具有一定的公有性質。

· 只有類在定義時,把接口名寫在了基類列表中,而且類中定義的全權名、類型和返回類型都與顯式接口成員執行體完全一致時,顯式接口成員執行體纔是有效的,例如:

class Shape: ICloneable {
    object ICloneable.Clone( ) {…}
    int IComparable.CompareTo(object other) {…}
}

使用顯式接口成員執行體通常有兩個目的:

· 因爲顯式接口成員執行體不能通過類的實例進行訪問,這就可以從公有接口中把接口的實現部分單獨分離開。如果一個類只在內部使用該接口,而類的使用者不會直接使用到該接口,這種顯式接口成員執行體就可以起到作用。

· 顯式接口成員執行體避免了接口成員之間因爲同名而發生混淆。如果一個類希望對名稱和返回類型相同的接口成員採用不同的實現方式,這就必須要使用到顯式接口成員執行體。如果沒有顯式接口成員執行體,那麼對於名稱和返回類型不同的接口成員,類也無法進行實現。

下面的定義是無效的,因爲Shape 定義時基類列表中沒有出現接口IComparable。

class Shape: ICloneable
{
    object ICloneable.Clone( ) {…}
}
class Ellipse: Shape
{
    object ICloneable.Clone( ) {…}
}

在Ellipse中定義ICloneable.Clone是錯誤的,因爲Ellipse即使隱式地實現了接口ICloneable,ICloneable仍然沒有顯式地出現在Ellipse定義的基類列表中。

接口成員的全權名必須對應在接口中定義的成員。如下面的例子中,Paint的顯式接口成員執行體必須寫成IControl.Paint。

using System ;
interface IControl
{
    void Paint( ) ;
}
interface ITextBox: IControl
{
    void SetText(string text) ;
}
class TextBox: ITextBox
{
    void IControl.Paint( ) {…}
    void ITextBox.SetText(string text) {…}
}

實現接口的類可以顯式實現該接口的成員。當顯式實現某成員時,不能通過類實例訪問該成員,而只能通過該接口的實例訪問該成員。顯式接口實現還允許程序員繼承共享相同成員名的兩個接口,併爲每個接口成員提供一個單獨的實現。

下面例子中同時以公制單位和英制單位顯示框的尺寸。Box類繼承IEnglishDimensions和IMetricDimensions兩個接口,它們表示不同的度量衡系統。兩個接口有相同的成員名 Length 和 Width。

程序清單1  DemonInterface.cs
interface IEnglishDimensions  {
   float Length ( ) ;
   float Width ( ) ;
}
interface IMetricDimensions {
   float Length ( ) ;
   float Width ( ) ;
}
class Box : IEnglishDimensions, IMetricDimensions {
   float lengthInches ;
   float widthInches ;
   public Box(float length, float width) {
      lengthInches = length ;
      widthInches = width ;
   }
   float IEnglishDimensions.Length( ) {
      return lengthInches ;
   }
   float IEnglishDimensions.Width( ) {
      return widthInches ;      
   }
   float IMetricDimensions.Length( ) {
      return lengthInches * 2.54f ;
   }
   float IMetricDimensions.Width( ) {
      return widthInches * 2.54f ;
   }
   public static void Main( ) {
      //定義一個實類對象 "myBox"::
      Box myBox = new Box(30.0f, 20.0f);
      // 定義一個接口" eDimensions"::
      IEnglishDimensions eDimensions = (IEnglishDimensions) myBox;
      IMetricDimensions mDimensions = (IMetricDimensions) myBox;
      // 輸出:
      System.Console.WriteLine(" Length(in): {0}", eDimensions.Length( ));
      System.Console.WriteLine(" Width (in): {0}", eDimensions.Width( ));
      System.Console.WriteLine(" Length(cm): {0}", mDimensions.Length( ));
      System.Console.WriteLine(" Width (cm): {0}", mDimensions.Width( ));
   }
}

輸出:Length(in): 30,Width (in): 20,Length(cm): 76.2,Width (cm): 50.8

代碼討論:如果希望默認度量採用英制單位,請正常實現 Length 和 Width 這兩個方法,並從 IMetricDimensions 接口顯式實現 Length 和 Width 方法:

public float Length( ) {
   return lengthInches ;
}
public float Width( ){
   return widthInches;
}
float IMetricDimensions.Length( ) {
   return lengthInches * 2.54f ;
}
float IMetricDimensions.Width( ) {
   return widthInches * 2.54f ;
}

這種情況下,可以從類實例訪問英制單位,而從接口實例訪問公制單位:

System.Console.WriteLine("Length(in): {0}", myBox.Length( )) ;
System.Console.WriteLine("Width (in): {0}", myBox.Width( )) ;    
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length( )) ;
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width( )) ;

 

繼承接口實現

接口具有不變性,但這並不意味着接口不再發展。類似於類的繼承性,接口也可以繼承和發展。

注意:接口繼承和類繼承不同,首先,類繼承不僅是說明繼承,而且也是實現繼承;而接口繼承只是說明繼承。也就是說,派生類可以繼承基類的方法實現,而派生的接口只繼承了父接口的成員方法說明,而沒有繼承父接口的實現,其次,C#中類繼承只允許單繼承,但是接口繼承允許多繼承,一個子接口可以有多個父接口。

接口可以從零或多個接口中繼承。從多個接口中繼承時,用":"後跟被繼承的接口名字,多個接口名之間用","分割。被繼承的接口應該是可以訪問得到的,比如從private類型或internal類型的接口中繼承就是不允許的。接口不允許直接或間接地從自身繼承。和類的繼承相似,接口的繼承也形成接口之間的層次結構。

請看下面的例子:

using System ;
interface IControl {
    void Paint( ) ;
}
interface ITextBox: IControl {
    void SetText(string text) ;
}
interface IListBox: IControl {
    void SetItems(string[] items) ;
}
interface IComboBox: ITextBox, IListBox { }

對一個接口的繼承也就繼承了接口的所有成員,上面的例子中接口ITextBox和IListBox都從接口IControl中繼承,也就繼承了接口IControl的Paint方法。接口IComboBox從接口ITextBox和IListBox中繼承,因此它應該繼承了接口ITextBox的SetText方法和IListBox的SetItems方法,還有IControl的Paint方法。

一個類繼承了所有被它的基本類提供的接口實現程序。

不通過顯式的實現一個接口,一個派生類不能用任何方法改變它從它的基本類繼承的接口映射。例如,在聲明中:

interface IControl {
	void Paint( );
}
class Control: IControl {
	public void Paint( ) {...}
}
class TextBox: Control {
	new public void Paint( ) {...}
}

TextBox中的方法Paint隱藏了Control中的方法Paint,但是沒有改變從Control.Paint到IControl.Paint 的映射,而通過類實例和接口實例調用Paint將會有下面的影響。

Control c = new Control( ) ;
TextBox t = new TextBox( ) ;
IControl ic = c ;
IControl it = t ;
c.Paint( ) ;			// 影響Control.Paint( ) ;
t.Paint( ) ;			// 影響TextBox.Paint( ) ;
ic.Paint( ) ;			// 影響Control.Paint( ) ;
it.Paint( ) ;			// 影響Control.Paint( ) ;

但是,當一個接口方法被映射到一個類中的虛擬方法,派生類就不可能覆蓋這個虛擬方法並且改變接口的實現函數。例如,把上面的聲明重新寫爲:

interface IControl {
	void Paint( ) ;
}
class Control: IControl {
	public virtual void Paint( ) {...}
}
class TextBox: Control {
	public override void Paint( ) {...}
}

就會看到下面的結果:

Control c = new Control( ) ;
TextBox t = new TextBox( ) ;
IControl ic = c ;
IControl it = t ;
c.Paint( ) ;			// 影響Control.Paint( );
t.Paint( ) ;			// 影響TextBox.Paint( );
ic.Paint( ) ;			// 影響Control.Paint( );
it.Paint( ) ;			// 影響TextBox.Paint( );

由於顯式接口成員實現程序不能被聲明爲虛擬的,就不可能覆蓋一個顯式接口成員實現程序。一個顯式接口成員實現程序調用另外一個方法是有效的,而另外的那個方法可以被聲明爲虛擬的以便讓派生類可以覆蓋它。例如:

interface IControl {
	void Paint( ) ;
}
class Control: IControl {
	void IControl.Paint( ) { PaintControl( ); }
	protected virtual void PaintControl( ) {...}
}
class TextBox: Control {
	protected override void PaintControl( ) {...}
}

這裏,從Control繼承的類可以通過覆蓋方法PaintControl來對IControl.Paint的實現程序進行特殊化。

使用getResourceAsStream()時,必須給定一個URL,由於我們要存取JAR內部的資源,如果使用「/」作爲開頭,代表絕對路徑,「/」代表JAR文件之中的根目錄。如果沒有使用「/」,則視爲相對路徑,要看調用getResourceAsStream()類別的所在路徑而定,這樣容易造成混淆。所以請儘量使用「/」作爲開頭的絕對路徑。

 

MIDlet的程序結構

如果曾經撰寫過Java Applet或Java Servlet,就知道製作Java Applet,必須繼承自java.applet.Applet這個類別;製作Java Servlet,則程序必須繼承自javax.servlet. http.HttpServlet這個類別。同理,要撰寫能夠在手機上執行的Java MIDlet必須要繼承自javax.microedition.midlet. MIDlet類別,如圖8所示。

 

 

圖8 MIDlet繼承體系圖

javax.microedition.midlet.MIDlet類別中定義了三個抽象方法,它們分別是:startApp() ==> 至運作狀態;pauseApp() ==> 至停止狀態;destoryApp() ==> 至消滅狀態。應用程序管理員就是透過這三個抽象方法來控制MIDlet的生命週期。因此,所有的MIDlet都必須實現這三個方法,才保證能正常運作。因此,一個MIDlet的程序外殼至少要如下:

import javax.microedition.midlet.*;
public class HelloMIDlet extends MIDlet
{
 public HelloMIDlet() 
 {
  //建構式
 }
 public void startApp() 
 {
 }
 public void pauseApp() 
 {
 }
 public void destroyApp(boolean unconditional) 
 {
 }
}

根據MIDP規格,MIDlet中不應該有“public static void main(Straing[] args)”這個方法。如果有則應用程序管理員會忽略不管。

一旦MIDlet被JAM載入之後,首先會先呼叫MIDlet中沒有參數的建構式以進行初始化的工作。如果熟悉Java語法一定知道Java語言的一些特性,就是如果沒有在程序中加入任何建構式,編譯器會自動幫助加入一個預設建構式。但是如果已經撰寫了自己的建構式(有任何參數),那麼編譯器將不會自動幫助加上預設建構式。因此,如果有必要撰寫有參數的建構式,別忘了再手動加上一個沒有參數的建構式,否則MIDlet將無法正確地初始化。

 

MIDlet的起始行爲

當MIDlet被應用程序管理員產生之後,MIDlet就開始運作,程序設計師應該做的事情如圖9所示。

 

 

圖9 MIDlet起始行爲圖

我們會使用Display.getDisplay(this)來取得代表該MIDlet顯示屏的Display對象。從應用程序管理員呼叫startApp()到MIDlet結束運作這段時間之內,不管何時呼叫Display.getDisplay(this),取得的都是同一份Display對象的參考。

要設定顯示在屏幕上的畫面,會使用Display對象的參考,並調用其setCurrent()方法,即display.setCurrent( Displayable類別的子類別實體)。因此一個可以運作的MIDlet程序,至少如HelloMIDlet.java,代碼爲:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class HelloMIDlet extends MIDlet
{
 private Display display;	
 public HelloMIDlet() 
 {
  display = Display.getDisplay(this);
 }
 public void startApp() 
 {
  Form t = new Form("畫面");
  display.setCurrent(t);
 }
 public void pauseApp() 
 {
 }
 public void destroyApp(boolean unconditional) 
 {
 }
}

注意:根據規格MIDlet只能由應用程序管理員產生,我們不能自己在程序裏生成其它的MIDlet,並呼叫其生命週期函數,這樣做將引發SecurityException異常。

 

MIDlet的生命週期

前面簡單地敘述了應該如何撰寫一個MIDlet的輪廓。但事實上,MIDlet的運作稍微複雜一點。所以接下來要仔細探討MIDlet的運作細節,也就是MIDlet的生命週期。

當MIDlet被應用程序管理員成功地初始化之後,就開始展開了它的生命週期。圖10描述了一個MIDlet的生命週期。MIDlet的生命週期完全由應用程序管理員控制,也就是說,當MIDlet要從一個狀態變成另外一個狀態時,應用程序管理員會呼叫對應的回呼函數(Call Back,也就是MIDlet類別定義的那三個抽象方法)。MIDlet基本上有三種狀態,分別是停止狀態(Paused)、啓動狀態(Active)及毀滅狀態(Destroyed)。MIDlet開始時一定是先進入停止狀態,然後應用程序管理員再將它轉換成啓動狀態,然後調用startApp(),見圖10。只有當應用程序管理員認爲MIDlet的狀態必須改變時,纔會呼叫圖中的相關函數。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章