對象的自治與智能化

作者: 張逸  發布時間: 2011-10-03 10:38  閱讀: 1506 次  推薦: 0   原文鏈接   [收藏]  

  我一直強調對象是自治的,這意味著它應該擁有能表達自身對象特性的數據與行為,對自己的數據與狀態負責,對于該數據而言,對象是自給自足的。對象的自治體現了OO最基本的原則,那就是“數據與行為應該封裝在一起”。擁有行為能力的對象,就好比擁有了意識,擁有了智能,它可以自行判斷,而無需別人通知。不錯,這事實上就是“好萊塢原則”的體現,但我更喜歡將其稱之為對象的“專家模式”。這種專家模式與現實社會何其相似,“專業的事情就交給專家去做吧!”軟件系統的對象應該各司其職,各盡其職,這樣才能產生合理的職責分配,從而形成完善的協作方式。

  還是用案例來說話。在我們的項目中,需要對客戶發出的Web請求進行處理,獲得我們需要的參數。參數的值放在Request中,而我們事先已經根據配置文件,獲得了參數的類型信息。根據項目需要,我們將參數劃分為三種:
  1、單一參數(SimpleParameter);
  2、元素項參數(ItemParameter);
  3、表參數(TableParameter);

  因為參數的屬性是在配置文件中已經配好,我定義了ParameterGraph對象。它能夠讀取參數的配置信息,并根據參數的類型創建不同的參數對象。這些參數類共同繼承了一個抽象的父類ParameterBase,并實現了Parameter接口,如下圖所示:

  由于參數的數據放在客戶端發出的Web請求中,因此,我們需要對Web請求進行解析。由于Web請求存儲的參數值事實上是存放在一個Map中,我們需要根據參數的name來甄別這些請求值,并根據一定的判斷邏輯,將Web請求傳來的值填充到Parameter對象中。這是一個收集參數的過程,它被定義在ReportParameterAction類中。最初,我是這樣實現的:

private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
for (Parameter para : parameterGraph.getParmaeters()) {
    Map<String,Parameter> paraMap = new HashTable<String, Parameter>();
        if (para instanceOf SimpleParameter) {
            String[] values = request.getParameterValues(para.getName());
            para.setValues(values);
            paraMap.put(para.getName(),para);
        } else {
            if (para instanceOf ItemParameter) {
                ItemParameter itemPara = (ItemParameter)para;
                for (Item item : itemPara.getItems()) {
                    String[] values = request.getParameterValues(item.getName());
                    item.setValues(values);
                }
                paraMap.put(itemPara.getName(),itemPara);
            }else {
                TableParameter tablePara = (TableParameter)para;
                String[] rows = request.getParameterValues(para.getRowName());
                String[] columns = request.getParameterValues(para.getColumnName());
                String[] dataCells = request.getParameterValues(para.getDataCellName());

                int columnSize = columns.size;
                for (int i=0; i < rows.size;i++) {
                for (int j=0;j < columns.size;j++) {
        TableParameterElement element = new TableParameterElement();
                    element.setRow(rows.get(i));
                    element.setColumn(columns.get(j);
                    element.setDataCell(dataCells[columnSize * i + j]);
                    tablePara.addElement(element);
                }
                paraMap.put(tablePara.getName(),tablePara);
            }
        }
    }
return parameterGraph.getParameters();
}

  對于不同類型的參數,有著不同的處理邏輯。例如,對于TableParameter而言,它在客戶端瀏覽器的表單中,事實上表現為一個表,然后以行、列以及數據單元格的名稱作為Map的key值,而value則是一個字符串數組。當我寫出這樣的代碼時,我的直覺告訴我,這樣的代碼是丑陋的,一定存在某些問題。那么問題出在哪里呢?首先,在這個方法中,存在分支語句,它會判斷parameter對象的運行時類型,以此決定不同的處理邏輯。為什么要這樣做,從上面的類圖可以看出,對于不同的Parameter對象,值是不相同的,即擁有各自不同的方法,例如getValue()、addItem()與addElement()等。而這些方法是不同抽象到它們共同的父類或接口中,因此必須轉換為具體類型,再進行處理。

  現在,讓我們從兩個角度來思考問題。首先,閱讀分支語句的執行邏輯,它所要處理的數據是屬于誰的?除了提供參數數據源的request對象,這些數據都是屬于各自參數對象的。例如,針對TableParameter,雖然看起來是在創建TableParameterElement對象,但實質上該對象本身就屬于TableParameter的一個組成元素。ItemParameter與SimpleParameter同樣如此。根據前面所述的“數據與行為應該封裝在一起”原則,我們似乎應該將各個分支的處理邏輯封裝到各自的Parameter類中。事實上,ReportParameterAction類只關心對參數的收集,并不需要知道參數收集的具體實現。因此,無論是從封裝還是職責分配的角度來看,這里的事先都存在問題。

  讓我們再站在抽象的角度思考問題。前面提到不同參數有不同的值,且這些值分別屬于不同的類型,操作方式也不相同,因而無法抽象。事實上,這里違背了抽象的原則,就是過于關注實現細節,而沒有從共性特征的角度去分析接口。在前面的分析中,實質上我已經隱隱約約提到了這個共同特征:即無論設置什么值,其本質都是將數據填充到參數中,至于如何填充,則屬于實現細節的范疇。因此,我們可以在Parameter接口中定義如下方法:

public interface Parameter {
    public void fillData(ParameterRequest request);
}

  注意,我在fillData()方法中玩了一個花招。我們可以比較之前的collectParameters()方法,它接收的參數為ServletRequest對象,現在fillData()方法的參數變成了ParameterRequest,發生了什么事?

  原因有二。其一,ServletRequest類是由Servlet提供的,Parameter要使用它,就必須依賴于Servlet包,這實在有些得不償失。其二,ServletRequest接口是一個龐大的接口,它很難被實現。設想我們如果要對Parameter進行單元測試,則很難模擬ServletRequest,而事實上,這里僅僅需要調用ServletRequest接口的getParameterValues()方法即可。正因為如此,我引入了ParameterRequest接口,它僅定義了一個getParameterValues()方法:

public interface ParameterRequest {
    public String[] getParameterValues(String name);
}

  對于這樣一個接口,我們很容易模擬它的實現,而對于調用者ReportParameterAction而言,也很容易將ServletRequest對象轉為ParameterRequest的實現。好了,這里的設計事實上體現了對象的“間接”以及單一職責原則,通過引入間接的接口來解除耦合。不過,這應該是另外一個故事了。讓我們繼續回過頭來看參數的處理。我們將之前collectParameters()方法的實現轉移到各個Parameter子類的fillData()實現中:

public class SimpleParameter extends ParameterBase {
    public void fillData(ParameterRequest request) {
        this.setValues(request.getParameterValues(this.getName()));
    }
}
public class ItemParameter extends ParameterBase {
    public void fillData(ParameterRequest request) {
        for (Item item : this.getItems()) {
            String[] values
request.getParameterValues(item.getName());
            item.setValues(values);
        }
    }
}
public class TableParameter extends ParameterBase {
    public void fillData(ParameterRequest request) {
        String[] rows =
            request.getParameterValues(this.getRowName());
        String[] columns =
            request.getParameterValues(this.getColumnName());
        String[] dataCells =
request.getParameterValues(this.getDataCellName());

        int columnSize = columns.size;
        for (int i=0; i < rows.size;i++) {
            for (int j=0;j < columns.size;j++) {
TableParameterElement element =
new TableParameterElement();
                element.setRow(rows.get(i));
                element.setColumn(columns.get(j);
                element.setDataCell(dataCells[columnSize * i + j]);
                this.addElement(element);
            }
        }
    }
}

  現在,這些Parameter的子類就是自治的對象,它能夠處理屬于自己的數據,而不需要外界(調用者)來告知它,它充分享有智能判斷的能力。讓我們看看修改后的客戶端代碼:

private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
    for (Parameter para : parameterGraph.getParmaeters()) {
        Map<String,Parameter> paraMap = new HashTable<String, Parameter>();
        para.fillData(new ParameterRequest() {
            public String[] getParameterValues(String name) {
                return request.getParameterValues(name);
            }
        );
    }
    return parameterGraph.getParameters();
}

  現在,Parameter對象能夠根據自己的類型對從Request中取出來的數據進行處理。ReportParameterAction的collectParameters()方法就不用操心這些煩人的處理邏輯,代碼結構變得更加清晰,尤其是職責的劃分更為準確。這種對參數職責有效的封裝方式,還有利于參數類型的擴展。倘若增加了新的參數類型,只需要定義繼承自ParameterBase類的子類即可,不會影響到任何客戶調用代碼。這就是自治對象的好處。記住,當我們需要分配職責時,就應該考慮自治原則與專家模式,并設身處地站在該對象的角度去思考問題,想一想,如果你就是這個對象,應該完成哪些職責,而哪些職責又應該委派給別的對象。

0
0
 
標簽:面向對象
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()