商務邏輯的強型別化

來源:互聯網
上載者:User
 

商務邏輯中,很多邏輯上不同類型的東西,到了程式設計語言中,時常會退化成一種類型。一個最簡單的例子就是貨幣。通常在我們編程時,採用一種類型,如double(有些系統中有專門的Currency類型,為了簡便起見,這裡使用double),來表示貨幣。但是,隨之而來的就是幣種的問題。不同的幣種間存在換算,也就是匯率的問題。比如我們有RMB和USD兩種貨幣,分別表示人民幣和美元。儘管都是貨幣(在代碼中有相同的類型),我們卻不能對他們直接賦值:double rmb_;double usd_=100;rmb_=usd_;     //絕對不行,100美元可相當於768元人民幣,儘管人民幣在升值必須進行匯率換算:rmb_=usd_*e_rate;e_rate就是匯率。這個誰都清楚。在邏輯上,100美元和768元人民幣是等價的(假設今天的匯率是7.68),是可以兌換的。但在軟體中,我們不能簡單的賦值了事,必須做換算。現在我們希望用代碼直接表現邏輯上的意義,也就是用賦值操作:=,實現貨幣間的換算,該怎麼做呢?啊對,沒錯,操作符重載。我們可以重載operator=操作符,使其具備匯率換算的功能。(或許有人會提出異議,改變一個操作符已有的語義,是否違背大師們的教誨。但我個人認為,語義應當遵從商務邏輯,既然按照邏輯含義進行重載,不應該引發什麼糾紛。否則還需要重載幹嗎?)但問題是,重載依賴於不同的類型,double operator=(double)的操作符定義是預設的,已經存在,無法以相同形式重載。再說,即便是可以,複製對象和被賦值對象的類型相同,如何區分兩種類型的轉換呢?很明顯,我們需要新的類型。typedef肯定是沒指望的,因為它僅僅為一個類型起別名,並沒有產生新的類型。所以,我們只能求助於類。我們可以以如下方式定義各種不同的貨幣類:class RMB{public:   double _val;};class USD{public:   double _val;};…這樣,便可以針對不同貨幣重載operator=:class RMB{public:   RMB operator=(const RMB& v) {       _val=v._val;   }   RMB operator=(const USD& v) {       _val=v._val*e_rate; //貨幣換算   }public:   double _val;};class USD{public:   USD operator=(const USD& v) {       _val=v._val;   }   USD operator=(const RMB & v) {       _val=v._val/e_rate; //貨幣換算   }public:   double _val;};這樣,我們便可以對兩種貨幣賦值了:RMB    rmb_;USD    usd_;rmb_=usd_;     //帶貨幣換算的賦值操作根據這個方法,我們一直往下推,可以構造出各種各樣的貨幣,並且定義它們之間的轉換:class UKP //英鎊{…}class JPD //日元{…}…不過有個問題,如果有10中貨幣,我們必須定義100個operator=的重載,而且都是些重複代碼。這樣太蠢了。得採用更好的方法才能實現我們的理想。注意觀察,每個貨幣類的代碼都符合約一種模式,有很強的規律性。看出來了吧,這種情況非常適合使用C++的超級武器——模板。沒錯,說做就做:template<int CurrType>class Currency{public:   double _val;};注意看,這裡非常規地使用了模板的一個特性:非類型模板參數,就是那個int CurrType。模板參數通常都是一個類型,比如int什麼的。但也可以是一個非類型的模板參數,就象這裡的CurrType。傳統上,非類型模板參數用於傳遞一個靜態值,用來構造模板類。但在這裡,這個模板參數並沒有被模板使用,也永遠不會被使用。這個模板參數的作用就是“製造類型”:typedef    Currency<0> RMB;    //人民幣typedef    Currency<1> USD;    //美元typedef    Currency<2> UKP;    //英鎊typedef    Currency<3> JPD;    //日元…typedef本身不會產生新的類型,但是這裡Currency< n>已經是完全不同的類型了。當一個模板被執行個體化成一個類的時候,只要模板參數的實參有所不同,便是一個不同的類型。我們利用了模板的這個特性,憑空製造出任意多個結構完全相同,但卻是完全獨立的類型。好,下一步,便是重載operator=操作符。當然不能再做為每一對貨幣類型重載operator=的蠢事了。用一個成員函數模板就可以解決問題:double e_rate[10][10];     //匯率表 template<int CurrType>class Currency{public:   template<int ct2>   Currency<CurrType>& operator=(count Currency<ct2>& v) {       _val=v._val * e_rate[ct2][CurrType];    //找出匯率表中相應的匯率,                                               // 計算並賦值   }public:   double _val;};操作符operator=的代碼中,賦值對象v的值乘上一個匯率,這個匯率存放在匯率表中,通過模板參數CurrType和ct2檢索(當然匯率表得足夠大)。這樣,我們便可以隨意地賦值,而無須關心貨幣轉換的問題了:///初始化匯率表e_rate[0][0]=1;e_rate[1][0]=7.68;…//使用貨幣USD    usd_;UKP    ukp_;JPD    jpd_; jpd_=usd_=ukp=rmb_;    //成功!一切順心。需要說明的是,匯率表並沒有在聲明時就初始化,是考慮到匯率經常變動,不應當作為常量寫死在代碼中。更進一步可以使用一個類封裝成可變大小的匯率表,甚至可以用某個檔案或資料庫對其初始化。問題當然還有,貨幣是要參與運算的,否則沒有什麼用處。所以,我們還得使這些貨幣具備基本的計算能力。貨幣的計算,根據商務邏輯大致應具備以下能力:1.       +、-:兩種貨幣的加法和減法,允許不同種貨幣參與計算,必須考慮轉換操作,返回做運算元類型;2.       *、/:貨幣乘上或除以一個標量值,這裡設定為double。但兩種貨幣不能相乘或相除。3.       ==、!=:比較兩種貨幣,允許不同種貨幣參與比較,但必須考慮轉換操作。還有其他的操作,暫不做考慮,畢竟這裡的目的不是開發一個完整的貨幣系統。為了編碼上的方便,這裡同時還定義了四則運算的賦值版本:+=、-=、*=、/=。為了節省篇幅,這裡只展示+、*和==的代碼,其他代碼類推:template<int ty, int tp>inline bool operator==(currency<ty>& c1, const currency<tp>& c2) {   return c1._val==c2._val*curr_rate[tp][ty];} template<int ty, int tp>inline currency<ty>& operator+=(currency<ty>& c1, const currency<tp>& c2) {   c1._val+=c2._val*curr_rate[tp][ty];   return c1;}template<int ty, int tp>inline currency<ty> operator+(currency<ty>& c1, const currency<tp>& c2) {   currency<ty> t(c1);   t+=c2;   return t;}請注意==和+操作符中的的貨幣轉換運算,每次都是將第二運算元貨幣轉換成第一運算元貨幣後再進行運算操作。第一參數和第二參數的類型不同,因此允許不同貨幣進行計算。這可以進一步簡化代碼,完全以邏輯的方式編程。template<int ty>inline currency<ty>& operator*=(currency<ty>& c1, const double q) {   c1._val*=q;   return c1;}template<int ty>inline currency<ty> operator*(currency<ty>& c1, const double q) {   currency<T, ty> t(c1);   t*=q;   return t;} template<int ty>inline currency<ty>& operator*=(const double q,currency<ty>& c1) {   return operator*=(c1, q);}template<int ty>inline currency<ty> operator*(const double q,currency<ty>& c1) {   return operator*(c1, q);}…*操作符的參數只有一個是貨幣類型,另一個是double類型,表示數量。只有貨幣乘上數量才有意義,不是嗎?*操作符包括兩個版本,一個貨幣在前,數量在後;另一個數量在前,貨幣在後。為的是適應rmb_*1.4和1.4*rmb_兩種不同的寫法,演算法是完全一樣的。現在,貨幣可以運算了:usd_=usd_*3;   //同種貨幣運算ukp_=rmb_*2.5;     ///計算後直接賦值給另一種貨幣jpd_=ukp_=rmb_+usd_;   ///同上,但有四種貨幣參與運算現在,貨幣運算非常方便了,不需要考慮貨幣種類,貨幣的轉換是自動的,無需額外代碼。在簡化代碼的同時,也提供了操作上的約束,比如:ukp_=rmb_*usd_;    ///編譯錯誤。貨幣乘上另一種貨幣無意義!!!這句代碼會引發編譯錯誤,因為我們沒有為兩種貨幣相乘提供*的重載。很明顯,一種貨幣與另一種貨幣相乘是根本沒有意義的。這裡通過靜態重載類型檢查,對施加在貨幣上的運算做出約束。促使違背邏輯的代碼在第一時間被攔截,避免出現執行階段錯誤。要知道,兩種貨幣相乘,賦給另一個貨幣的錯誤是非常隱形,只有盤庫或結賬的時候才會發現。很好,這裡我們利用了C++模板的一些特殊機制,以及操作符模板、操作符重載等技術,開發一個貨幣系統。這個系統可以用最簡潔的語句實現各種貨幣的計算和轉換功能。同時,還利用重載機制的強型別特性,提供了符合商務邏輯的操作約束。貨幣運算只是一個簡單的案例,但相關的技術可以進一步推廣到更複雜的領域中。而且業務越複雜,所得到的收益越多。因此,充分理解並運用C++所帶來的泛型程式設計功能,可以大大簡化軟體的開發、減少代碼的錯誤,降低開發的成本。這種技術適合用在一些邏輯上存在差異,但在物理上具備相同特徵的實體上。一方面使這些實體在代碼中強型別化,以獲得重載和類型檢測能力。由於代碼中邏輯實體的對應類型強型別化,是我們可以通過重載和靜態類型檢測等技術手段,實現僅使用語言提供的要素,在代碼中直接構造業務模型的能力。但手工對每一個邏輯實體進行強型別化,是費力的和瑣碎的,並且存在著大量的重複勞動。此時,我們可以利用模板的抽象能力,反過來利用邏輯實體在物理上的共同特性,一次性構建抽象的模板,並利用模板執行個體化的一些特性,很方便地構造新的類型(僅僅一個typedef)。

這種技術進一步擴充後,可以有更進階的應用。一個經典的範例就是實現編譯期的量綱分析。在Template Meta-programming一書中,對此有詳細的講解。 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.