2012-01-03

D筆記 -- D的OO

主要是參考Learn to Tango with D的內容,把我想記下來的部份寫在這…

模組Modules

基本而言,每一個D的原始碼檔都可以被稱為模組且為語言中可以被定址的實體。在大多數的情況下,模組可以被視為一個簡單且靜態的分類(class)。當程式員要在模組內取用其它模組的符號(symbol)時,可以在一開頭的位置import整個實體或是要使用到特定符號。

符號可以是原始碼的任何部份,通常是變數、函式名稱與使用者定義型態的名字。

模組可以被啟始化,用來控制存取原始碼與在package(是一堆被放在同一目錄下的模組,package的名稱要與目錄名稱相同)內分組以便未來使用。

模組命名

模組名稱可以在程式碼內宣告,且必須為第一個出現在原始碼內的事物,如:

module Time;

如果並沒有給定模組宣告,編譯器將會自動使用原始碼檔名作為模組名稱。譬如,Time.d將會讓模組被稱為Time。但為了便於開發大型專案,建議還是要自行宣告給定。當程式員將模組組成package時,程式員需考量到模組宣告與package等級。如同模組與原始碼檔有一對一的關係,D package也與目錄結構有關。如果程式員的最頂層原始碼目錄為src,所有目錄內的模組都需要包含模組宣告(如先前之module Time;)。在任何次目錄內的模組則必須以次目錄名稱來作為模組宣告,而使用圓點(.)註記來區分各名稱。譬如在src/tango/time/Time.d的模組就會被看作module tango.time.Time;

引用其它模組

當程式員開始使用函式、層級與其它多個模組內的元件,你需要將有關符號叫用進來。

基本引用

import tango.time.Time;

基本引用預設是私用而隱藏著。如果要公開引用,就要加入public

public import tango.time.Time;

也可以同時引用多個模組

import tango.io.FilePath,              
       tango.io.FileConduit;

更名引用

透過更名引用,程式員可以更有效率地建立一個命名空間來取用相關符號。這裡就是一個範例:
import Int = tango.text.convert.Integer;
如果Integer有個函式叫做format,接著程式員就可以這樣來呼叫它:
Int.format();

選擇性引用

如果只會用到引用模組內的少部份符號,程式員可以選擇引用:
import tango.text.Util : strip, trim;

程式員可以結合選擇性引用與更名引用,方法有二:可以從選擇性引用模組來建立一個新的命名空間,也可以對已選擇符號來作更名。
import TextUtils = tango.text.Util;
import TextUtils = tango.text.Util : clean = strip;

靜態引用
這是用來避免名稱衝突的作法

static import tango.text.Util; 
// Using the function strip 
tango.text.Util.strip(); 

在模組裡取用變數

模組本身是自己的原始碼載具與其它如結構與類別的程式碼載具的載具。如果程式員使用上遇到想使用變數與類別內變數同名時,只要透過圓點符號"."來區別即可,它會把變數設定為整體。

以下例子,outerState傳回的變數值是來自module而非class.

module Parser; 
 
int state; 
 
class Parser { 
    int state; 
    int outerState() { return .state; } 
} 

建立單元測試

D相較於許多主流程式語言更先進處,在於讓程式員更易於建立程式碼之測試與約定。D內建有單元測試功能。以下是一個例子:

unittest { 
    assert (square(4) == 16); 
} 

unittest區塊可以需要來設置assert的數量,並沒有加以限制。要執行單元測試,編譯時需要在dmd後加上-unittest的參數。如果要修改或控制單元測試的執行,程式員可以透過tango.core.Runtime裡的moduleUnitTester來處理。

注意:程式員不可以在自己的單元測試內引用其它模組!

結構與聯合

結構是單純數值型態,讓程式員可以組合數據與函式並便於使用,記憶體的安排也較為緊湊。但D的結構不同於C/C++,D不像C,結構可以支援成員函式.不像C++,D不支援結構內的多型態.

在建立程式員自訂型態時採用組合取代繼承這點來說,結構非常適用於OOP上.不過,你無法從結構取得次類別或使用它們的介面.否則,結構非常是合用在老式資料物件. 事實上,結構是被放置在堆疊而非堆積上常被視為主要好處. 在Tango, Time透過結構完成而非類別就是這考量.

D裡面,結構可以用在以下情況:

1. 作為取用C函式庫與作業系統的介面,如給定自變量與回傳結構
2. 如果是直接取用硬體,這時節購的內容可以直接對應到記憶體的範圍、輸入與輸出
3. 當程式員要包裝日常D程式寫作常用的數據,且類別的負擔無法接受時就可以考慮使用結構. 不過在考量效率時,程式員也損失了類別與介面的好處.

定義結構

通常在設定結構時,無非就是命名且指派數據欄位與操作等。但程式員仍然事後透過指定對齊或配置之位元邊界、任何非變量或單元測試來客製化。invariant是內建測試,用於確保元件在操作前後仍維持一致的狀態.結構也可以擁有被初始化的靜態數據且加以靜態構築與解構。

struct Time { 
    uint hour; 
    int timeZone; 
    bool usingAM; 
 
    int time() {  
        if (usingAM && hour > 12) return hour – 12; 
        else return hour;
    }  
}

基本上以D語言撰寫結構時,無須作對齊的動作.但當程式員要設計結構來對應特定C函式庫時,就有必要考慮該如何對應。D有設計align這屬性來對應這樣的需求,範例如下:

struct S { 
    align(4) int a; 
    align(4) int b; 
}

定義聯合

聯合主要適用於在程式運作期間,用來追蹤會有不同資料型態表現的數值.通常存在於低階程式碼之中,這樣可以節省記憶體.

union Error { 
    int errorCode; 
    char[] errorMessage; 
} 

初始結構

結構是數值型態--僅為數值欄位的集合體--所以在初始時,就要讓它易於參看。程式員可以動態或靜態地來初始它。

靜態初始

程式員可以用初始聯合的方式來對結構作靜態初始,另外一個方式則是如下:

static Time t = { hour:7, timeZone:-2 };

這樣會宣告一個Time型態變數t,而t.hour為7且t.timeZone為-2。

static Time t = { 7, -2 }; 

如果是這樣,則會把兩個值設定到結構裡面的頭兩個欄位裡面。

動態初始

在D 1.0裡面,結構並無構造器(constructor),但仍可以進行動態初始。首先程式員可以透過已存在的同類型數值來參與,範例如下:

// Given a defined struct Time 
Time t; 
t.hour = 3; 
Time at = t; 

這時候 at就初始了t內的數據,當然也可以說是t內各欄位的數值都被複製到了at的欄位裡面,所以at.hour就會變成3。另外一個方式,就可以透過靜態opCall方法來模擬構造器,範例如下:

static Time opCall(int time) { 
    Time t; 
    t.hour = time; 
    return t; 
} 

Time t = Time(3);

結構文字

程式員可以建立結構值並且直接傳送到函式來使用其型態,或者透過使用結構文字來設定一個變數值。

void setTime(Time t); 
setTime( Time(1, 2) ); 

結構文字的參數/自變量將依照結構順序針對開頭的欄位來加以設定。如果結構擁有比傳來參數/自變量更多的欄位,則為依照預設/缺省值來設定。

結構配置

結構被compiler視為與其它基本型態一樣並安排在堆疊上.如果程式員需要保留結構並且傳送到當初建立的範圍外時,就需要把他們安置在堆積上.以下這樣就會把結構配置在堆積上...
Time * dt = new Time; 

類別/Classes

D語言主要透過類別與介面來支援OO程式寫作.類別提供比結構更多彈性.它包括繼承/inheritance,多形/polymorphism, 虛擬函式與特徵/identity.D的類別是參考型態,類似Java而非C++的數值型態.

OO程式術語

當程式員依循OO來撰寫程式或者閱讀相關資料時,需要特別注意它也是有自己的術語.以下就是會常用的部份:

  • Class/類別:程式員自己定義的型態
  • Object/物件/對象:計畫的本體.OO計畫內所有的本體都是物件,類別只是用來定義物件的主要意義.在D/Java內,所有類別都是Object類別的子類別.
  • Instance/實例:來自類別定義的物件實例.每一個實例都有它自己的特徵,且可為實例的任意量數字.
  • Subclass/子類別:一個被定義成繼承其它類別屬性的類別,但可增加自己特有的屬性.
  • Superclass/超類別:一個透過繼承而被延伸的類別
  • Method/方法/類函數:透過類別定義的函數,且可以運算其內資料.要注意C++並未使用此一術語,而採取函數.
  • Getters & Setters/獲取者與安放者:用來簡易取得類別內數值或者設定數值的方法.通常也被稱為類別的屬性.

類別定義與實例

簡單的類別可在語法定義上視為如同結構的定義方式,除了關鍵字外.D語言1.0內類別只比結構多出只有構件子/constructor, 解構子/destructor, 與繼承相關功能.

class Time { 
    uint h; 
    int tz; 
 
    uint hour() { return h; } 
 
    this(uint hour, int timeZone) { 
        this.h = hour; 
        this.tz = timeZone; 
    } 
}

上例顯示Time類別帶有h與tz欄位(預設為公開),且hour的類函數(將傳回類函數呼叫的實例內之h數值).this是個特別的類函數並作為類別的建構子.此時,建構子透過傳送參數到設定實例內欄位來設定其值.在類別內,this關鍵字可用在類別範疇內透過參數名稱與展示指標與實例本身來辨識符號.

當程式員定義類別後,都會想要在程式碼內建立實例.下例顯示如何透過new語法為之:
File f = new File("tango/io/File.d"); 
這裡透過呼叫File構件子來建立帶有File類別的實例f,構件子會取得帶有路徑檔案名稱的字串來開啟檔案.執行建構子外,也會配置類別與實例傳回的參考所需之記憶體.(此例內就指派給f)
基本上,所有類別實例都是被配置在堆積上.如果程式員考量配置類別實例在堆疊上時,就要透過scope屬性來作為變數宣告,如:
scope f = new File("tango/io/File.d");

沒有留言: