你在用什么思想编码:事务脚本 OR 面向对象?
最近在公司內(nèi)部做技術(shù)交流的時(shí)候,說(shuō)起技能提升的問(wèn)題,調(diào)研大家想要培訓(xùn)什么,結(jié)果大出我意料,很多人想要培訓(xùn):面向?qū)ο缶幋a。于是我拋出一個(gè)問(wèn)題:你覺(jué)得我們現(xiàn)在的代碼是面向?qū)ο蟮膯?#xff1f;有人回答:是,有人回答否。我對(duì)這個(gè)問(wèn)題的回答是:語(yǔ)法上,是了,但是架構(gòu)上或者思想上,不是。我們現(xiàn)在的大部分代碼,如果要死扣一個(gè)名詞的話,那就是:事務(wù)腳本。
?
1:最開始的事務(wù)腳本
在 Martin Fowler 的書中,存在一個(gè)典型的 應(yīng)用場(chǎng)景,即“收入確認(rèn)”(Revenue Recognition)。該“收入確認(rèn)”的描述:
一家軟件公司有3種產(chǎn)品,其售價(jià)策略分別為,第一種:交全款才能賣給你;第二種,付三分之一,就給你,60天后,再給1/3,90天后給完全部;第三種,付1/3,就給你,30天后給1/3,60天后給完。
但是,關(guān)于這個(gè)描述,我打算多啰嗦幾句,而且個(gè)人覺(jué)的這個(gè)啰嗦非常之緊要,因?yàn)樗绊懙搅宋覀兊脑O(shè)計(jì)。以下是啰嗦的部分:
“收入確認(rèn)”,在概念上,確實(shí)是產(chǎn)品的入賬策略,實(shí)際上,Martin 的代碼,也是這么去實(shí)現(xiàn)的,不同的產(chǎn)品有不同的入賬策略。不過(guò),數(shù)據(jù)庫(kù)實(shí)現(xiàn),RevenueRecognition 這個(gè)表記錄的是“產(chǎn)品的某個(gè)合同根據(jù)產(chǎn)品類型所計(jì)算出來(lái)的:應(yīng)該執(zhí)行的入賬日及金額”,即策略是跟著合同走的,而不是跟著產(chǎn)品走的。這很有意思,如果你精讀此部分,這種矛盾就會(huì)一直糾結(jié)在你心頭。同時(shí),我們又不得不時(shí)刻提醒自己存在的這個(gè)需求。
現(xiàn)在,關(guān)于這個(gè)場(chǎng)景,如果我們理解了 產(chǎn)品 合同 RevenueRecognition 之間的關(guān)系,我們就很能理解了數(shù)據(jù)庫(kù)是被設(shè)計(jì)成這樣的:
其概念模型為如下:
好了,現(xiàn)在我們來(lái)看看什么是事務(wù)腳本,對(duì)的,就用代碼來(lái)說(shuō)話。在原文中, Martin 舉了兩個(gè)例子,但是精讀之后,我打算將其顛個(gè)倒,把原文中的示例2講在前頭。因?yàn)槭纠?,很好的表達(dá)了什么才是作者或者譯者眼中的“收入確認(rèn)”,以及我眼中的“收入策略”。
第一個(gè)要實(shí)現(xiàn)的功能,即第一個(gè)事務(wù)腳本描述如下:
根據(jù)合同 ID,找到該合同,并根據(jù)合同類型得到應(yīng)該在哪天收入多少錢,并插入數(shù)據(jù)庫(kù)。
從該描述中,我們知道,這個(gè)腳本最應(yīng)該發(fā)生在簽訂合同時(shí)。因?yàn)楹贤坏┖炗?#xff0c;就應(yīng)該記錄什么時(shí)候應(yīng)該收到客戶端多少錢。代碼如下:
class RecognitionService
{
??? dynamic dal = null;
???
??? // 計(jì)算哪天該入賬多少并插入
??? 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")??? // 數(shù)據(jù)庫(kù)
??????? {???
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(30));
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(60));
??????? }
??? }???
}
?
第二個(gè)需求是:計(jì)算某合同在某個(gè)日期前的應(yīng)該有的入賬。
class RecognitionService
{
??? dynamic dal = null;
????????
??? // 得到哪天前入賬了多少
??? 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;
??? }
}
從上面的代碼,我們可以看出什么才是 事務(wù)腳本:
1:采用面向過(guò)程的方式組織業(yè)務(wù)邏輯;
2:沒(méi)有或盡量少的實(shí)體類;
3:一個(gè)方法一件事情,故有大量業(yè)務(wù)類或方法;
4:能與行數(shù)據(jù)入口和表數(shù)據(jù)入口很好協(xié)作;
?
2:事務(wù)腳本之變體
也許上面的代碼多多少少讓大家嗤之以鼻,認(rèn)為現(xiàn)在很少會(huì)這樣來(lái)寫代碼了。那么,我們來(lái)看看下面這段代碼:
class RecognitionBll
{
??? dynamic dal = null;
???
??? // 計(jì)算哪天該入賬多少并插入
??? public void CalculateRevenueRecognitions(long contactNumber)
??? {
??????? List<Contact> contracts = dal.FindContract(contactNumber);
??????? double totalRevenue = (double)contracts[0].Id;
??????? DateTime dateSigned = (DateTime)contracts[0].DateSigned;
??????? string type = (string)dal.FindContractType(contactNumber);
??????? // 上面這行代碼你還可能會(huì)寫成
??????? // string type = (string)dal.contracts[0].ProductType;
??????? // 或者
??????? // string type = (string)dal.contracts[0].Product.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")??? // 數(shù)據(jù)庫(kù)
??????? {???
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned);
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(30));
??????????? dal.InsertRecognition(contactNumber, totalRevenue / 3, dateSigned.AddDays(60));
??????? }
??? }
???
??? // 得到哪天前入賬了多少
??? public double RecognizedRevenue(long contractNumber, DateTime asOf)
??? {
??????? // the sql "SELECT AMOUNT FROM REVENUECONGNITIONS WHERE CONTRACT=? AND RECOGNIZEDON <=?";
??????? List<RevenueRecognition> revenueRecognitions = dal.FindRecognitionsFor(contractNumber, asOf);
??????? double r = 0.0;
??????? foreach(RevenueRecognition rr in revenueRecognitions)
??????? {
??????????? r += rr.Amount;
??????? }
???????
??????? return r;
??? }
}
public class Product
{
??? public long Id;
??? public string Name;
??? public string Type;
}
public class Contact
{
??? public long Id;
??? 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;
}
在這個(gè)事務(wù)腳本的變種中,我們看到了所有人寫過(guò)代碼的影子:
1:有了實(shí)體類了,所以看上去貌似是面向?qū)ο缶幋a了;
2:看到了 “三層架構(gòu)” 了,即:實(shí)體層、DAL層、業(yè)務(wù)邏輯層等;
但是,它仍舊是 事務(wù)腳本 的!唯一不同的是,它光鮮的把 DataSet 變成了 List<Model> 了!
?
3:什么是面向?qū)ο蟮?#xff1f;
那么,什么是面向?qū)ο蟮木幋a,面向?qū)ο蟮囊粋€(gè)很重要的點(diǎn)就是:“把事情交給最適合的類去做”,并且“你得在一個(gè)個(gè)業(yè)務(wù)類之間跳轉(zhuǎn),才能找出他們?nèi)绾谓换?/strong>”。這確實(shí)是個(gè)不那么簡(jiǎn)單的話題,而本文的主旨也僅在于指出,如果我們的代碼中還沒(méi)有 工作單元 映射 緩存 延遲加載 等等概念,即便我們編碼再熟練,也僅僅是在熟練的 面向過(guò)程編碼。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的你在用什么思想编码:事务脚本 OR 面向对象?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: DataGridView的DataGri
- 下一篇: SSH 配置文件