2013年2月20日 星期三

探索產生物件的技巧

探索產生物件的技巧(1)當心隨手New一下引發的衝擊效應 
文/iThome (記者) 2007-08-23 

在程式設計裡,資訊不流通是一件好事。因為一個類別知道的資訊越少,就越不會因為資訊的改變而受到影響。 


在物件導向程式設計中,倚靠的是封裝資料及行為的物件,透過物件所提供的機制以及彼此之間的相互合作,達成系統的需求。因此,產生類別物件就成了物件導向程式中最基礎的動作。在常見的物件導向程式語言中,若要產生某類別的物件,最直接的方式莫過於透過像new之類的關鍵字,例如: 
Object obj = new Object(); 

New的範圍越廣,影響的層面越大 
然而,在實際的設計工作中,好的設計者及程式員,往往會運用更複雜的間接機制,產生所需類別的物件供系統之用。直接使用new語法產生物件,並直接運用會有什麼缺點?為什麼需要引入更複雜的間接機制? 

先讓我們定義出在物件產生、運用的過程中會需要涉及到的角色:1.被產生者(The Created Class)2.產生者(Creator)3.用戶端(Client)。所謂的被產生者,就是會被產生的物件、產生者便是產生的物件,而用戶端,則是使用物件的另一個物件。 

最常見到的情況,便是用戶兼任產生者的情況。也就是說,因為需要動用到某類別的物件,所以便自己產生一個或若干個該物件,以便運用。例如(以Java寫成): 
LDAPAuthenticator authLDAP =
new LDAPAuthenticator ();
authLDAP.authenticate(id, passowrd);


如果單單只考慮這樣的應用情境,上述的程式碼是沒有什麼重大的缺陷。不過問題總在改變之後來臨。原始系統也許只需要以LDAP認證,但是也許突然有一天,開發團隊被要求變更系統,讓系統可以允許多種認證的方式,包括讀取資料庫中的使用者帳號資料,或是以客製化方式認證。至於採用那一種認證方式,是設定系統的組態決定。 

設計者也許會考慮選擇提供多種不同認證方式的類別,然後在呼叫時,依據系統的組態設定值,決定究竟要運用那種認證方式,好比如下的寫法: 
boolean fAuthResult = false;
if( authType == LDAP_AUTH )
{
    LDAPAuthenticator authLDAP =
    new LDAPAuthenticator ();
    fAuthResult = authLDAP.authenticate
    (id, passowrd);
}
else if(authType == DB_AUTH )
{
    DBAuthenticator authDB =
    new DBAuthenticator ();
    fAuthResult = authDB.authenticate
    (id, passowrd);
}


但是,這樣的寫法存在一些問題。當你需要增加新的認證方式時,免不了必須修改程式碼。除了要新增類別之外,用戶端的程式也必須同步修改,才能夠將組態設定值,對映到新增的認證用類別,因而使得需求變更的影響層面更大。 

知道的越少,就越不會被影響 
倘若我們改用另一種寫法,針對認證功能制定出一個介面: 
public interface Authenticator
{
   boolean authenticate
   (String id, String password);
}


並為各種認證功能實作其類別,使各類別實作此一介面: 
class LDAPAuthenticator implements Authenticator { }
class DBAuthenticator implements Authenticator { }
class Cusomter1Authenticator implements Authenticator { }


利用另一個類別負責產生提供認證功能的類別: 
public class AuthenticatorCreator
{
   public static Authenticator createAuthenticator() { }
}


那麼需要使用認證功能的程式碼,就能夠寫成: 
Authenticator auth = 
AuthenticatorCreator.createAuthenticator();
auth.authenticate(id, passowrd);


在這個新版的程式碼中,我們看到了一個重大的變化:用戶端並不知道自己究竟產生的物件其確切型別為何,只知道它實作了某一個介面。在這段程式碼中,絲毫看不到new的語法,因為我們拆散了原先合併在一塊的產生者與用戶端兩角色。 

在這個版本中,我們將產生認證類別物件的工作,委派由另一個叫做AuthenticatorCreator的類別執行。所以對用戶端程式碼來說,只需要知道(1)所有實作Authenticator的類別都提供了認證的功能(2)AuthenticatorCreator能夠回傳實作目前系統組態所設定的認證方式類別。因此,用戶端的程式碼妥善地受到阻隔,因為它不需要知道系統組態目前的設定為何,也不需要知道如何對應各種認證功能設定與相對應實作的類別,再者,它不需要知道系統究竟提供多少種認證的方式,而後續新增加認證的方式,也不會影響到用戶端程式碼。 

上述的寫法,善用設計模式(Design Patterns)裡的「針對介面寫程式,而不要針對實作(Program to an interface, not an implementation)」。我們讓用戶端的程式碼存取的是認證的介面(Authenticator),而非存取各式認證的實作(LDAPAuthenticator、DB Authenticator、Customer1 Authenticator…)。這使得用戶端的程式碼,對真正的實作為何,一無所悉。 

在程式設計裡,資訊不流通是一件好事。一個類別知道的資訊越少,就越不會因為資訊的改變而被影響。 

此外,用戶端除了它不知道自己所使用的究竟是什麼之外,它甚至不知道本身所使用的物件,究竟是如何產生出來的!這就是將產生者的角色,自用戶端程式碼中拆開之後得到的好處。 

封裝與隱藏產生物件的邏輯,才能消除物件之間的相依性 
不在用戶端程式碼中直接使用new,為的是封裝並隱藏產生物件的邏輯。而封裝與隱藏,為得是希望能夠消除用戶端程式和它所欲使用的物件之間的相依性,避免當用戶端所使用的物件改變時,會被影響。 

許多人觀察到在各種不同的應用情境下,倘若將產生物件的動作,自用戶端程式碼中抽離,並以特定的方式產生,便能夠從中得到設計的好處。而這類的特定方式,就被整理設計模式裡所謂的「生成模式(Creational Patterns)」。 

各種不同的生成模式,其實都在告訴我們,各種間接產生物件的方式、在不同的應用情境下加以運用,可以得到什麼好處。在後續的介紹中,我將不拘泥於特定的生成模式,而將主題放在產生物件的方式,探討相關的諸般技巧。 

《作者簡介》王建興 
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。

沒有留言: