商務邏輯的強型別化(續)

來源:互聯網
上載者:User

商務邏輯的強型別化(續)
作為一個好事者,我希望能夠給我周邊的人講解這種技術。他們對C++很不熟悉,但熟悉C#。於是,我打算把這種技術移植到C#中,以便於講解。說做就做。

我建了一個C#項目,把代碼拷貝過去,然後著手修改,這樣可以省些事。我立刻便遇到了問題。C#有泛型,相當於模板,但不支援非類型泛型參數,即int CurrType,只允許用一個類型作為泛型參數。這樣我們就不能使用C++中耍的手法了(typedef currency<n>)。退而求其次,直接用類實現貨幣類型:

class RMB

{

public double _val;

}

class USD

{

public double _val;

}

這樣太繁瑣了,很多重複。我們可以用一個基類封裝_val,貨幣類從基類上繼承獲得:

class CurrBase

{

public double _val;

}

class RMB : CurrBase

{

}

class USD : CurrBase

{

}

貨幣類都是空的,它們的存在只是為了建立一個新的類型。

現在處理貨幣轉換問題。C#不能重載operator=,所以只能使用一個helper函數泛型asign代替:

class utility

{

     public static void asign<T1, T2>(T1 c1, T2 c2)

         where T1 : CurrBase

         where T2 : CurrBase

     {

         c1._val = c2._val * utility.e_rate[c2.CurID(),c1.CurID()];

     }

}

這個asign函數是個泛型,泛型參數分別代表了兩個運算元,函數中執行了貨幣轉換。為了能夠在匯率表中檢索到相應的匯率,我們必須為基類和貨幣類定義抽象函數:

    public abstract class CurrBase

    {

        public double _val=0;

        public abstract int CurID();

    }

    public class RMB : CurrBase

    {

        public override int CurID()

        {

            return 0;

        }

}

基類中聲明了CurID()抽象方法,並在貨幣類中定義。這樣,便可以用統一的方式進行貨幣轉換了:

asign(rmb_, usd_);

還行,儘管不那麼漂亮,但也還算實用。不過,當我多看了幾眼代碼後,便發現這雷根本沒有必要使用泛型。完全可以利用OOP的多態實現同樣的功能:

     public static void asign(CurrBase c1, CurrBase c2)

     {

         c1._val = c2._val * utility.e_rate[c2.CurID(),c1.CurID()];

     }

不過也沒關係,使用方式還沒有變,代碼反而更簡單了。使用泛型畢竟不是我們的根本目的,對吧?

現在輪到運算子了。不過我不知該把泛型運算子定義在哪裡。按C#文檔裡的要求,運算子必須是類的static成員。但我的泛型運算子是針對許多個貨幣類的,定義在任何一個中,對其他類似乎不太公平。於是,我決定嘗試將其定義在基類裡:

    public abstract class CurrBase

{

   …

        public static CurrBase operator+<T1, T2>(T1 c1, T2 c2)

           where T1 : CurrBase

           where T2 : CurrBase

        {

            …

        }

}

編譯器立刻還我以顏色:操作符根本不能是泛型!好吧,不能就不能吧,繼續退而求其次,用OOP:

    public abstract class CurrBase

{

   …

        public static CurrBase operator+(CurrBase c1, CurrBase c2)

        {

            …

        }

}

不過,這次讓步讓得有點離譜。當我寫下這樣的代碼時,編譯器居然不認賬:

rmb_=rmb_+usd_;

錯誤資訊是:錯誤 CS0266: 無法將類型“st_in_cs.CurrBase”隱式轉換為“st_in_cs.RMB”。存在一個顯式轉換(是否缺少強制轉換?)。

我非得採用強制類型轉換,才能過關:

rmb_=(RMB)(rmb_+usd_);

太誇張了,這樣肯定不行。於是,我被迫在每個貨幣類中定義operator+:

class RMB : CurrBase

{

   public RMB operator+(RMB c1, USD c2)

   {

       …

   }

   public RMB operator+(RMB c1, UKP c2)

   {

       …

   }

}

這可不得了,我必須為每對貨幣類定義一個+操作符,+操作符的總數將會是貨幣類數量的平方!其他的操作符每個都是貨幣類數的平方。我可受不了!

好在,可愛的OOP為我們提供了一根稻草,使得每個貨幣類的每個操作符只需定義一個:

class RMB : CurrBase

{

   public RMB operator+(RMB c1, CurrBase c2)

   {

       …

   }

}

這樣,任何貨幣類都可以作為第二運算元參與運算,而操作符只需定義一個。這樣的工作量,對於一個逆來順受的程式員而言,還是可以接受的。很好,代碼不出錯了:

rmb_=rmb_+usd_;

但當我寫下如下代碼時,編譯器又開始抱怨了:

ukp_ = rmb_ + usd_;

還是要求顯示轉換,除非我們為UKP定義隱式類型轉換操作符:

class UKP

{

    public static implicit operator UKP(RMB v)

    {

        …

    }

}

光有RMB的不行啊,還得有USD的、JPD…。不過這樣的話,我們必須為每一個貨幣類定義所有其它貨幣類的類型轉換操作符。又是一個組合爆炸。到這裡,我已經黔驢技窮了。誰讓C#不支援=操作符重載和操作符模板化呢。沒辦法,只能忍著點了。

不過,如果我們能夠降低點要求,事情還是有轉機的。如果我們不通過操作符,而是採用static成員方法,進行貨幣的運算的話,就可以省去很多代碼了:

    public class utility

    {

        public static T1 asign<T1, T2>(T1 c1, T2 c2)

            where T1 : CurrBase, new()

            where T2 : CurrBase

        {

            c1._val = c2._val * utility.curr_rate[c2.CurID(),c1.CurID()];

            return c1;

        }

        public static T1 add<T1, T2>(T1 c1, T2 c2)

            where T1 : CurrBase, new()

            where T2 : CurrBase

        {

            T1 t=new T1();

            t._val=c1._val + c2._val *

               utility.curr_rate[c2.CurID(),c1.CurID()];

            return t;

        }

       …

}

這裡,我還是使用了泛型,因為這些函數需要返回一個值,只有使用泛型,才能返回一個明確的類型,以避免強制轉換的要求。於是,賦值和計算的代碼就成了:

asign(jpd_, asign(ukp_, add(rmb_, usd_)));//也就是jpd_=ukp_=rmb_+usd_

的確是難看了點,但是為了能夠少寫點代碼,這也只能將就了。

好了,我盡了最大的努力,試圖在C#中實現強型別、可計算的貨幣系統。儘管最終我可以在C#中開發出一組與C++具有相同效果的貨幣類(除了賦值操作以外),但需要我編寫大量的代碼,實現各種計算操作,以及貨幣類之間的類型轉換操作(組合爆炸)。相比C++中總共200來行代碼,的確複雜、臃腫得多。

我並不希望把這篇文章寫成“C++ vs C#”,(儘管我很高興看到C++比C#強J)。我希望通過對這樣一個代碼最佳化任務,顯示不同技術運用產生的結果。同時,也可以通過這兩種實現嘗試的對比,瞭解泛型程式設計的作用,以及泛型程式設計對語言提出的要求。

毋庸置疑,C++採用了純粹的泛型程式設計,因此可以對問題進行高度抽象。並利用問題所提供的每一點有助於抽象的資訊,以最簡的形式對問題建模。而作為以OOP為核心的語言C#,對泛型的支援很弱。更重要的是,C#的泛型對泛型參數的運用嚴重依賴於泛型參數的約束(where)。如果沒有where,C#將泛型參數作為Object類型處理,此時泛型參數沒有意義(我無法訪問該類型的成員)。如果有了where,C#要求泛型參數必須同where中指定的類型有繼承關係(如asign中的T1必須是CurrBase的繼承類)。而泛型函數中對泛型參數的使用也局限在約束類型(即CurrBase)上。於是,我們可以直接用以基類(CurrBase)為參數的asign函數代替泛型版本的asign。由於C#對泛型參數的繼承性要求,使得泛型被困住了手腳,無法發揮應用的作用。正由於這些問題,C++才採用了現在模板的形式,而沒有採用同C#一樣的泛型模式。

或許有人會說,既然OOP能解決問題(asign最初不需要泛型也行,但最終還需要泛型來控制傳回值的類型),為什麼還要GP呢?

對於這個問題,前面也給出了答案。由於C#的泛型不支援非類型泛型參數,因此迫使我們使用傳統OOP的手段:利用基類實現貨幣類的實現,定義貨幣類來建立新類型,使貨幣強型別化,利用虛函數提供貨幣專屬資訊。僅這一層面,OOP方式已經大大不如GP方式了,GP僅定義了一個模板,所有的貨幣類型都是通過typedef一句語句產生,無需更多的代碼。而OOP方式要求必須為每一個貨幣編寫一個類,代碼量明顯多於GP方式。

此後,C++通過重載一組操作符模板,實現貨幣的運算。而貨幣模板以及產生貨幣類型的typedef都無須任何改變。而在C#中,由於不支援泛型操作符,被迫定義大量的特定於類型的操作符。所有運算操作符,在每個貨幣類中都必須重載一次。而轉型操作符,則必須在每個貨幣類中重載n-1次。

換句話說,有n種貨幣,有m個操作符(包括轉型操作符),那麼就需要定義n+1個類(包括基類),n×m+n×(n-1)個操作符。假設n=10,m=10,那麼總共需要11個類定義,190個操作符重載!如果每個類定義需要20行代碼,而每個操作符重載需要5行代碼,那麼總共需要1170行代碼。如果貨幣數量增加,總的代碼數將以幾何級數的方式增長。

上面的計算表明,儘管OOP可以解決問題,實現我們的目標,但所帶來的開發量和維護量卻是難以承受的。而且,OOP的方式擴充非常困難,隨著系統規模的擴大,擴充將越來越困難。所有這些都表明一點,儘管OOP是軟體工程的明星,但在實際情況下,很多地方存在著OOP無法解決或難以解決的問題。這也就是為什麼業界的先鋒人物不斷拓展和強化泛型程式設計的原因。
 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.