Tags: interface class method 動態語言
racklin 說 : 我的重點還是只放在 "關注類別是否有實作方法" 也就是 "介面" 的這個概念, 因為原文是討論這個議題 . 嗯,我大概是跳太快了。我清楚 interface 是什麼。所以我的回應 是在說明「個體之間如何協議互動行為」亦即「軟體合約」的形式。
以C/C++為例,在早期,程序員學了 C++ 可是還是要寫 C 程式的時代,我們會自己用 C 語言實作類別繼承、動態連結等觀念。但我們用的是 C compiler 而非 C++ compiler ,所以很多事我們必須自己處理。其中一點就是個體行為的協議。方法一、在函數文件上說明傳入的個體需擁有哪些行為,我在函數中會檢查此個體是否擁有此行為(函數指標是否為給定了)。方法二、限定一個 struct (只有純函數指標宣告),呼叫者要自己填一張函數指標表傳入,這其實就是 interface 的概念。
例如下列的程式碼,就是用 struct 宣告一張個體行為介面表。完整程式碼可見於 bbslib-20010331 。
/*
string_i.h Header file of string interface.
Copyright (C) 1998, Sh Yunchen, rock@bbs.isu.edu.tw
Licensed by GNU Lesser General Public License.
*/
#if !defined( __STRING_I_H )
#define __STRING_I_H
struct stringinterface
{
void * striobj ;
size_t ( * length )( const void * );
char * ( * text )( const void * );
char * ( * strcpy )( void * , const char * );
};
typedef struct stringinterface string_i ;
#endif // defined( __STRING_I_H )
使用方式通常如下列所示。宣告一個 buffer struct ,定義需要的函數。建構函數中要配置並指派函數指標。注意,這是 C ,不是 C++ 。一切要自己來。用 Java 的用語說明,此 buffer 類別的用途,在於將 primitive type 的字串轉成 object 。亦即 buffer 是一個 Wrapper Class 。最後在 main 和 print_content_and_length 中展示了近似 C++ 的敘述。
/*
buffer.c String buffer routines.
Copyright (C) 1999, Sh Yunchen, shirock@mail.educities.edu.tw
Licensed by GNU Lesser General Public License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "string_i.h"
struct buffer
{
string_i striobj ;
char * bufptr ;
void * freeptr ;
};
typedef struct buffer buffer_t ;
size_t buffer_length ( const buffer_t * b ) {
return strlen ( b -> bufptr );
}
char * buffer_text ( const buffer_t * b ) {
return b -> bufptr ;
}
char * buffer_strcpy ( buffer_t * b , const char * src ) {
return strcpy ( b -> bufptr , src );
}
buffer_t * buffer_construct ( buffer_t * b , char * bufptr ) {
buffer_t * tmp ;
if ( b == NULL )
tmp = ( buffer_t * ) malloc ( sizeof ( buffer_t ));
else
tmp = b ;
memset ( tmp , 0 , sizeof ( buffer_t ));
// all of the elements set to clear (null).
tmp -> striobj . striobj = tmp ;
tmp -> striobj . length = ( size_t ( * )( const void * )) buffer_length ;
tmp -> striobj . text = ( char * ( * )( const void * )) buffer_text ;
tmp -> striobj . strcpy = ( char * ( * )( void * , const char * )) buffer_strcpy ;
bufptr [ 0 ] = '\0' ;
tmp -> bufptr = bufptr ;
if ( b == NULL )
tmp -> freeptr = tmp ; //MALLOC
else
tmp -> freeptr = NULL ;
return tmp ;
}
/*
Define a function use string_i.
*/
void print_content_and_length ( string_i * str ) {
printf ( "Length of %s is %d \n " ,
str -> text ( str ), str -> length ( str ));
}
int main () {
buffer_t buf ;
char s [ 1024 ];
buffer_construct ( & buf , s );
buf . striobj . strcpy ( & buf , "hello world" );
print_content_and_length (( string_i * ) & buf );
//因為address對齊了, &buf = &buf->striobj.
return 0 ;
}
在當時,因為我們總是用強迫轉型的方式傳遞個體,所以 struct 中的宣告,僅是告知程序員應有什麼,對 compiler 是沒用的。程序員要自己掌握型態,軟體合約在程序員腦中、口中與文件中。
當然,C++/Java compiler 就不用那麼麻煩了。它會幫我們處理這些小事。它們會幫我們維護那張表,而 interface 的宣告內容就成為函數的說明文件之一。若以虛擬碼巨集表示:
declare interface ObjectAccess
method get ( k )
method set ( k , v )
end
define class Obj implements ObjectAccess
id
end
#expand Obj
type Obj
id
method get ( k )
method set ( k , v )
end
#end
function test ( interface Abc o );
var a ;
test ( a );
#expand
#if a has method 'get' and method 'set'
call test ( a );
#else
#throw TYPE ERROR
#end
#end
上述動作隱含於 C++/Java 的 compile 動作中,而在動態語言中往往是明顯地呈現在程式碼中。這並無不妥,因為動態語言往往具有中介語言/巨集語言的泛型表達能力。
function test ( o ) {
if o . has_method ? ( ' get ' ) and o . has_method ? ( ' set ' )
do something
else
throw TYPE ERROR
end
}
對於錯誤捕捉之事。靜態語言仰賴各種宣告資訊,提供 compiler 在編譯時期核對。但我因為以往有 C 語言實作的經驗,所以向來不太倚重 interface 防錯能力。
另一方面,動態語言沒有編譯時期,它們在執行時期才能確定個體資訊,故必須仰賴更動態的處理策略。現行最有效的策略就是玩真的,提供 test case 供 tester 在執行時期測試。
嗯。我這幾天就一直在想,為什麼我在寫動態語言程式時,不像寫 C/C++ 那麼強調型態宣告,卻不覺得程式會出錯?後來想到了,那是因為有 TDD 。而且 tester 所產生的結果更可靠。
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/3556355.html