天天看點

你在用什麼思想編碼:事務腳本 OR 面向對象?

最近在公司内部做技術交流的時候,說起技能提升的問題,調研大家想要教育訓練什麼,結果大出我意料,很多人想要教育訓練:面向對象編碼。于是我抛出一個問題:你覺得我們現在的代碼是面向對象的嗎?有人回答:是,有人回答否。我對這個問題的回答是:文法上,是了,但是架構上或者思想上,不是。我們現在的大部分代碼,如果要死扣一個名詞的話,那就是:事務腳本。

1:最開始的事務腳本

在 Martin Fowler 的書中,存在一個典型的 應用場景,即“收入确認”(Revenue Recognition)。該“收入确認”的描述:

一家軟體公司有3種産品,其售價政策分别為,第一種:交全款才能賣給你;第二種,付三分之一,就給你,60天後,再給1/3,90天後給完全部;第三種,付1/3,就給你,30天後給1/3,60天後給完。

但是,關于這個描述,我打算多啰嗦幾句,而且個人覺的這個啰嗦非常之緊要,因為它影響到了我們的設計。以下是啰嗦的部分:

“收入确認”,在概念上,确實是産品的入賬政策,實際上,Martin 的代碼,也是這麼去實作的,不同的産品有不同的入賬政策。不過,資料庫實作,RevenueRecognition 這個表記錄的是“産品的某個合同根據産品類型所計算出來的:應該執行的入賬日及金額”,即政策是跟着合同走的,而不是跟着産品走的。這很有意思,如果你精讀此部分,這種沖突就會一直糾結在你心頭。同時,我們又不得不時刻提醒自己存在的這個需求。

現在,關于這個場景,如果我們了解了 産品 合同 RevenueRecognition 之間的關系,我們就很能了解了資料庫是被設計成這樣的:

你在用什麼思想編碼:事務腳本 OR 面向對象?

其概念模型為如下:

你在用什麼思想編碼:事務腳本 OR 面向對象?

好了,現在我們來看看什麼是事務腳本,對的,就用代碼來說話。在原文中, Martin 舉了兩個例子,但是精讀之後,我打算将其颠個倒,把原文中的示例2講在前頭。因為示例2,很好的表達了什麼才是作者或者譯者眼中的“收入确認”,以及我眼中的“收入政策”。

第一個要實作的功能,即第一個事務腳本描述如下:

根據合同 ID,找到該合同,并根據合同類型得到應該在哪天收入多少錢,并插入資料庫。

從該描述中,我們知道,這個腳本最應該發生在簽訂合同時。因為合同一旦簽訂,就應該記錄什麼時候應該收到用戶端多少錢。代碼如下:

class RecognitionService

{

    dynamic dal = null;

    // 計算哪天該入賬多少并插入

    public void CalculateRevenueRecognitions(long contactNumber)

    {

        DataSet contractDs = dal.FindContract(contactNumber);

        double totalRevenue = (double)contractDs.Tables[0].Rows[0]["ID"];

        DateTime dateSigned = (DateTime)contractDs.Tables[0].Rows[0]["DateSigned"];

        string type = (string)contractDs.Tables[0].Rows[0]["Type"];

        if(type == "S")    // 電子表格類

        {

            // the sql "INSERT INTO REVENUECONGNITIONS (CONTRACT,AMOUNT,RECOGNIZEDON) VALUES (?,?,?)"

            dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);

            dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(60));

            dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(90));

        }else if(type == "W")    // 文字處理

        {   

            dal.InsertRecognition(contactNumber, totalRevenue, dateSigned);

        }else if(type == "D")    // 資料庫

            dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(30));

        }

    }   

}

第二個需求是:計算某合同在某個日期前的應該有的入賬。

    // 得到哪天前入賬了多少

    public double RecognizedRevenue(long contractNumber, DateTime asOf)

        // the sql "SELECT AMOUNT FROM REVENUECONGNITIONS WHERE CONTRACT=? AND RECOGNIZEDON <=?";

        DataSet ds = dal.FindRecognitionsFor(contractNumber, asOf);

        double r = 0.0;

        foreach(DataRow dr in ds.Tables[0].Rows)

            r += (double)dr["AMOUNT"];

        return r;

    }

從上面的代碼,我們可以看出什麼才是 事務腳本:

1:采用面向過程的方式組織業務邏輯;

2:沒有或盡量少的實體類;

3:一個方法一件事情,故有大量業務類或方法;

4:能與行資料入口和表資料入口很好協作;

2:事務腳本之變體

也許上面的代碼多多少少讓大家嗤之以鼻,認為現在很少會這樣來寫代碼了。那麼,我們來看看下面這段代碼:

class RecognitionBll

        List<Contact> contracts = dal.FindContract(contactNumber);

        double totalRevenue = (double)contracts[0].Id;

        DateTime dateSigned = (DateTime)contracts[0].DateSigned;

        string type = (string)dal.FindContractType(contactNumber);

        // 上面這行代碼你還可能會寫成

        // string type = (string)dal.contracts[0].ProductType;

        // 或者

        // string type = (string)dal.contracts[0].Product.Type;

        List<RevenueRecognition> revenueRecognitions = dal.FindRecognitionsFor(contractNumber, asOf);

        foreach(RevenueRecognition rr in revenueRecognitions)

            r += rr.Amount;

public class Product

    public long Id;

    public string Name;

    public string Type;

public class Contact

    public long ProductId;

    public string ProductType;

    public Product Product;

    public double Revenue;

    public DateTime DateSigned;

public class RevenueRecognition

    public long ContactId;

    public double Amount;

    public double RevenuedOn;

在這個事務腳本的變種中,我們看到了所有人寫過代碼的影子:

1:有了實體類了,是以看上去貌似是面向對象編碼了;

2:看到了 “三層架構” 了,即:實體層、DAL層、業務邏輯層等;

但是,它仍舊是 事務腳本 的!唯一不同的是,它光鮮的把 DataSet 變成了 List<Model> 了!

3:什麼是面向對象的?

那麼,什麼是面向對象的編碼,面向對象的一個很重要的點就是:“把事情交給最适合的類去做”,并且“你得在一個個業務類之間跳轉,才能找出他們如何互動”。這确實是個不那麼簡單的話題,而本文的主旨也僅在于指出,如果我們的代碼中還沒有 工作單元 映射 緩存 延遲加載 等等概念,即便我們編碼再熟練,也僅僅是在熟練的 面向過程編碼。

你在用什麼思想編碼:事務腳本 OR 面向對象?

本文基于

Creative Commons Attribution 2.5 China Mainland License

釋出,歡迎轉載,演繹或用于商業目的,但是必須保留本文的署名

http://www.cnblogs.com/luminji

(包含連結)。如您有任何疑問或者授權方面的協商,請給我留言。