• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-341  評論-2670  文章-0  trackbacks-0

            在所有的文字之前,我需要強調(diào)一下,我本人對structure typing持反對態(tài)度,所以就算文中的內(nèi)容“看起來很像”go的interface,讀者們也最好不要覺得我是在贊揚go的interface。我比較喜歡的是haskell和rust的那種手法。可惜rust跟go一樣恨不得把所有的單詞都縮成最短,結(jié)果代碼寫出來連可讀性都沒有了,單詞都變成了符號。如果rust把那亂七八糟的指針設(shè)計和go的那種屎縮寫一起干掉的話,我一定會很喜歡rust的。同理,COM這個東西設(shè)計得真是太他媽正確了,簡直就是學習面向?qū)ο笫址ǖ淖罴逊独上OM在C++下面操作起來有點傻逼,于是很多人看見這個東西就呵呵呵了。

            上一篇文章說這次要寫類成員函數(shù)和lambda的東西,不過我想了想,還是先把OO放前面,這樣順序才對。

            我記得我在讀中學的時候經(jīng)常聽到的宣傳,是面向?qū)ο蟮淖龇ǚ浅7先祟惖乃季S習慣,所以人人喜歡,大行其道,有助于寫出魯棒性強的程序。如今已經(jīng)過了十幾年了,我發(fā)現(xiàn)網(wǎng)上再也沒有這樣的言論了,但是也沒有跟反C++的浪潮一樣拼命說面向?qū)ο筮@里不好那里不好要廢除——明顯人們還是覺得帶面向?qū)ο蟮恼Z言用起來還是比較爽的,不然也就沒有那么多人去研究,一個特別合用來寫functional programming的語言——javascript——是如何可以“模擬”面向?qū)ο笳Z言里面的常用操作——new、繼承和虛函數(shù)覆蓋了。

            所以像面向?qū)ο筮@種定義特別簡單的東西,語法上應(yīng)該做不出什么坑的了。那今天的坑是什么呢?答案:是人。

            動態(tài)類型語言里面的面向?qū)ο笳f實話我也不知道究竟好在哪里,對于這種語言那來講,只要做好functional programming的那部分,剩下的OO究竟要不要,純粹是一個語法糖的問題。在動態(tài)類型語言里面,一個類和一個lambda expression的差別其實不大。

            那么靜態(tài)類型語言里面的面向?qū)ο笠趺纯创兀渴紫任覀円氲降囊粋€是,凡是面向?qū)ο蟮恼Z言都支持interface。C++雖然沒有直接支持,但是他有多重繼承,我們只需要寫出一個純虛類出來,就可以當interface用了。

            在這里我不得不說一下C++的純虛類和interface的這個東西。假設(shè)一下我們有下面的C#代碼:

            interface IButton{}
            interface ISelectableButton : IButton{}
            interface IDropdownButton : IButton{}
            class CheckBox : ISelectableButton{}
            
            class MyPowerfulButton : CheckBox, IDropdownButton
            {
                // 在這里我們只需要實現(xiàn)IDropdownButton里面比IButton多出來的那部分函數(shù)就夠了。
            }

            我們先不管GUI是不是真的能這么寫,我們就看看這個繼承關(guān)系就好了。這是一個簡單到不能再簡單的例子。意思就是我有兩種button的接口,我從一個實現(xiàn)里面擴展出一個支持另一種button接口的東西。但是大家都知道,我那個完美的GacUI用的是C++,那么在C++下面會遇到什么問題呢:

            #region 抱怨

            一般來說在C++里面用純虛類來代替interface的時候,我們繼承一個interface用的都是virtual繼承。為什么呢?看上面那個例子,ISelectableButton繼承自IButton,IDropdownButton繼承自IButton。那么當你寫一個MyPowerfulButton的時候,你希望那兩個接口里面各自的IButton是不一樣的東西嗎?這當然不是。那如何讓兩個接口的IButton指向的是同一個東西呢?當然就是用virtual繼承了。

            好了,現(xiàn)在我們有CheckBox這個實現(xiàn)了ISelectableButton(帶IButton)的類了,然后我們開始寫MyPowerfulButton。會發(fā)生什么事情呢?

            猜錯了!答案是,其實我們可以寫,但是Visual C++(gcc什么的你們自己玩玩就好了)會給我們一個warning,大意就是你IDropdownButton里面的IButton被CheckBox給覆蓋了,再說抽象一點就是一個父類覆蓋了另一個父類的虛函數(shù)。這跟virtual繼承是沒關(guān)系的,你怎么繼承都會出這個問題。

            但這其實也怪不了編譯器,本來在其他情況下,虛函數(shù)這么覆蓋自然是不好的,誰讓C++沒有interface這個概念呢。但是GUI經(jīng)常會碰到這種東西,所以我只好無可奈何地在這些地方用#pragma來supress掉這個warning,反正我知道我自己在干什么。

            C++沒有interface的抱怨到這里就完了,但是virtual繼承的事情到這里還沒完。我再舉一個例子:

            class A
            {
            private:
                int i;
            public:
                A(int _i)i:(_i){}
            };
            
            class B : public virtual A
            {
            public:
                B(int _i):A(_i){}
            };
            
            class C : public virtual A
            {
            public:
                C(int _i):A(_i){}
            };
            
            class D : public B, public C
            {
            public:
                D():B(1), C(2){}
            };

            大家都是知道什么是virtual繼承的,就是像上面這個例子,D里面只有一個A對象,B和C在D里面共享了A。那么,我們給B和C用了不同的參數(shù)來構(gòu)造,難道一個A對象可以用不同的參數(shù)構(gòu)造兩次嗎,還是說編譯器幫我們隨便挑了一個?

            呵呵呵呵呵呵呵呵,我覺得C++的virtual繼承就是這里非常反直覺——但是它的解決方法是合理的。反正C++編譯器也不知道究竟要讓B還是C來初始化A,所以你為了讓Visual C++編譯通過,你需要做的事情是:

            D()
                : A(0)  // 參數(shù)當然是胡扯的,我只是想說,你在D里面需要顯式地給A構(gòu)造函數(shù)的參數(shù)
                , B(1)
                , C(2)
            {
            }

            #endregion

            大家估計就又開始吵了,C++干嘛要支持多重繼承和virtual繼承這兩個傻逼東西呢?我在想,對于一個沒有內(nèi)建interface機制的語言,你要是沒有多重繼承和virtual繼承,那用起來就跟傻逼一樣,根本發(fā)揮不了靜態(tài)類型語言的優(yōu)勢——讓interface當contract。當然,我基本上用多重繼承和virtual繼承也是用來代替interface的,不會用來做羞恥play的。

            當我們在程序里面拿到一個interface也好,拿到一個class也好,究竟這代表了一種什么樣的精神呢?interface和class的功能其實是很相似的

            interface IA:只要你拿到了一個IA,你就可以對她做很多很多的事情了,當然僅限大括號里面的!
            class C : IA, IB:只要你拿到了一個C——哦不,你只能拿到interface不能拿到class的——反正意思就是,你可以對她做對IA和IB都可以做的事情了!

            所以contract這個概念是很容易理解的,就是只要你跟她達成了contract,你就可以對她做這樣那樣的事情了。所以當一個函數(shù)返回給你一個interface的時候,他告訴你的是,函數(shù)運行完了你就可以做這樣那樣的事情。當一個函數(shù)需要一個interface的時候,他告訴你的是,你得想辦法讓我(函數(shù))干這樣那樣的事情,我才會干活。

            那class呢?class使用來實現(xiàn)interface的,不是給你直接用的。當然這是一個很理想的情況,可惜現(xiàn)在的語言糖不夠甜,堅持這么做的話實在是太麻煩了,所以只好把某些class也直接拿來用了,GUI的控件也只好叫Control而不是IControl了。

            其實說到底class和interface有什么區(qū)別呢?我們知道面向?qū)ο蟮囊淮筇卣骶褪欠庋b,封裝的意思就是封裝狀態(tài)。什么是狀態(tài)呢?反正云風一直在說的“類里面的數(shù)據(jù)”就不是狀態(tài)。我們先來看什么是數(shù)據(jù):

            struct Point
            {
                int x;
                int y;
            };

            這就是典型的數(shù)據(jù),你往x和y里面隨便寫什么東西都是沒問題的,反正那只是一個點。那什么是狀態(tài)呢:

            struct String
            {
                wchar_t* buffer;
                int length;
            };

            String和Point有什么不一樣呢?區(qū)別只有一個:String的成員變量之間是滿足一個不變量的:wcslen(buffer) == length;

            如果我們真的決定要給String加上這么個不變量的話,那這里面包含了兩點:
            1:buffer永遠不是nullptr,所以他總是可以被wcslen(buffer)
            2:length的值和buffer有直接的關(guān)系

            如果你要表達一個空字符串,你總是可以寫buffer=L””,不過這就要你給String再加上一些數(shù)據(jù)來指明這個buffer需要如何被釋放了,不過這是題外話了。我們可以假設(shè)buffer永遠是new[]出來的——反正這里不關(guān)心它怎么釋放。

            這個不變量代表什么呢?意思就是說,無論你怎么折騰String,無論你怎么創(chuàng)建釋放String,這個等式是一定要滿足的。也就是說,作為String外部的“操作人員”,你應(yīng)當沒機會“觀測”到這個String處于一個不滿足不變量的狀態(tài)。

            所以這兩個成員變量都不應(yīng)該是public的。因為哪怕你public了他們其中的一個,你也會因為外部可以隨意修改它而使他進入一個不滿足不變量的狀態(tài)。

            這代表了,為了操作這些成員變量,我們需要public一些函數(shù)來給大家用。其實這也是contract,String的成員函數(shù)告訴我們,你可以對我(String)做很多很多的事情哦!

            這同時也代表了,我們需要一個構(gòu)造函數(shù)。因為如果我們在創(chuàng)建一個String之后,實例沒有被正確初始化,那么他就處于了一個不滿足不變量的狀態(tài),這就不滿足上面說的東西了。有些人喜歡帶一個Init函數(shù)和一個基本不干什么事情的構(gòu)造函數(shù),我想說的是,反正你構(gòu)造完了不Init都不能用,你為什么非要我每次創(chuàng)建它的時候都立刻調(diào)用Init這么多次一舉呢?而且你這樣會使得我無法對于一個這樣的函數(shù)f(shared_ptr<ClassThatNeedsInit> x)直接寫f(make_shared(new ClassThatNeedInit))因為你的構(gòu)造函數(shù)是殘廢的!

            有些人會說,init沒有返回值,我不知道他犯了錯誤啊——你可以用Exception

            還有些人會說,exception safe的構(gòu)造函數(shù)好難寫啊——學啊我艸

            但是這樣仍然有些人會負隅頑抗,都這么麻煩了反正我可以用對Init和返回值就好了——你連exception safe的構(gòu)造函數(shù)都不知道怎么寫你怎么知道你可以“用對”它們

            #region 題外話展開

            但是有些人就喜歡返回error,怎么辦呢?其實我們都很討厭Java那個checked exception的對吧,要拋什么exception都得在函數(shù)簽名里面寫,多麻煩啊。其實這跟error是一樣的。一個exception是可以帶有很多豐富的信息的——譬如說他的callstack什么的,還可以根據(jù)需要有很多其他的信息,總之不是一個int可以表達的。這就是為什么exception【通常】都是一個類。那如果我們不能用exception,但是也要返回一樣多的信息怎么辦?你只好把函數(shù)的返回值寫得相當?shù)膹?fù)雜,譬如說:

            struct ErrorInfoForThisFunction
            {
                xxxxxxxx
            };
            
            template<typename R, typename E>
            struct ReturnValue // C++沒有好用的tuple就是臥槽
            {
                bool hasError;
                R returnValue;
                E errorInfo;
            };
            
            ReturnValue<ReturnType, ErrorInfoForThisFunction> ThisFunction( ... ); //我知道因為信息實在太多你們又要糾結(jié)返回struct還是它的指針還是ReturnValue里面的東西用指針還是用引用參數(shù)等等各種亂七八糟的事情了哈哈哈哈哈哈

            于是現(xiàn)在出問題了,我有一個ThatFunction調(diào)用ThisFunction,當錯誤是一種原因的時候我可以處理,當錯誤是另一種原因的時候我無法處理,所以在這種情況下我有兩個選擇:
            1:把錯誤信息原封不斷的返回
            2:把ThisFunction的錯誤信息包裝成ThatFunction的錯誤信息
            不過我們知道其實這兩種方法都一樣,所以我們采用第一種:

            struct ErrorInfoForThatFunction
            {
                yyyyyyyy
            };
            
            ReturnValue<ReturnType2, tuple<ErrorInfoForThisFunction, ErrorForThatFunctio, bool /*用來代表哪個是有效的*/> ThatFunction( ...  ); //數(shù)據(jù)越來越多我知道你們會對返回值糾結(jié)的越厲害

            你可能會說,為什么不把tuple包裝成另一個struct?其實都一樣,懶得寫了。

            我們知道,通常一個常見的幾百人一起寫的小軟件都會有幾百上千萬行甚至幾十G代碼的,函數(shù)的調(diào)用層次只有幾十層都已經(jīng)很不錯了。就算調(diào)用鏈里面只有10%的函數(shù)添加了自己的錯誤信息,那累積到最后肯定會很壯觀的。而且只要底層的一個函數(shù)修改了錯誤信息,所有直接間接調(diào)用它的函數(shù)都會受到影響。

            這簡直就跟Java的checked exception一樣嘛!

            有些人會說,我們有error code就夠了!我知道你們根本沒有好好想“怎么做error recovery”這件事情

            有些人還會說(就在我微博上看見的),用error code就是代表可以不處理,我干嘛要費那么多心思搞你這些返回值的東西?我對這種人只能呵呵呵了,轉(zhuǎn)行吧……

            這個時候我就會想,C++多好啊,我只要把ReturnValue<ReturnType, ErrorInfoForThisFunction>給改成ReturnType,然后在函數(shù)里面發(fā)生了錯誤還是構(gòu)造一個ErrorInfoForThisFunction,然后直接給throw出來就好了。throw一個值我們還不用關(guān)心怎么釋放它,多省事。對于ErrorInfoForThatFunction,我還可以讓這兩個struct都繼承自同一個基struct(就是你們經(jīng)常在別的語言里面看見的Exception類了),這樣我在外面還可以直接catch(const 基struct& ex)。

            有些人會說,為什么不強制所有繼承都繼承自Exception?我知道你們就只是想catch了之后不理罷了,反正C++也有catch(…)你們偷著樂就行了

            用Exception有性能問題?反正在不發(fā)生錯誤的情況下,寫了幾句try也就只是操作了寫在FS : [ 0 ]里面的一個鏈表而已,復(fù)制幾個指針根本就不算什么影響

            C++的catch不能抓到Access Violation(也就是segmant fault?)?現(xiàn)在連最新的.net你來寫catch(Exception ex)也抓不到AccessViolationException了。都AV了你的內(nèi)存都搞得一團糟了,如果你這個時候還不備份數(shù)據(jù)dump自己然后退出重啟(如果需要的話),那你接著執(zhí)行代碼,天知道會發(fā)生什么事情啊!連C#都覺得這么做危險了,C++只能更危險——所以用SEH抓下來dump自己然后進程自殺吧。Java還區(qū)分Error和Exception,雖然我不知道他具體代表什么,反正一般來說Exception有兩種
            1:可以預(yù)見的錯誤,譬如說Socket斷開了所以Write就是敗了給個Exception之類的
            2:必須修改代碼的錯誤,譬如說數(shù)組下標越界——這除了你寫錯以外根本沒有別的原因,就應(yīng)該掛掉,這時候你debug的時候才能立刻知道,然后改代碼

            所以有三個基類然后最嚴重的那種不能catch我覺得也是一種好的選擇。你可能會問,那C#在AV之后你又抓不到那怎么知道呢?答案:Application類有一個事件就是在發(fā)生這類事情的時候被調(diào)用的,在里面dump就好了。如果你非要抓AV,那也可以抓得到,就是有點麻煩……

            #endregion

            說了這么多,無非就是因為一個類的構(gòu)造函數(shù)——其實他真的是一個函數(shù),只是函數(shù)名和類名一樣了,這種事情在js里面反正經(jīng)常出現(xiàn)——強制了你只能返回正確的時候的結(jié)果,于是有些人沒辦法加入error code了,又不知道怎么正確使用exception,只好搞出個C語言的封建社會殘留思想Init函數(shù)來。其實我們知道,一旦有了Exception,函數(shù)簽名里面的返回值就是他正確執(zhí)行的時候返回的東西,這根構(gòu)造函數(shù)一樣。

            C++的exception在構(gòu)造函數(shù)里面不好,其實是因為一旦構(gòu)造函數(shù)發(fā)生了異常,那代表這個類沒構(gòu)造完,所以析構(gòu)函數(shù)是不會執(zhí)行的。這在一定程度上給你寫一個正確的構(gòu)造函數(shù)(這也是“如何寫一個正確的類”的其中一個方面)帶來了麻煩,所以很多人到這里就呵呵呵了。

            這就跟很多人學習SQL語言結(jié)果到group by這里就怎樣也跨不過去了一樣——人和人之間說沒有差距這個不符合客觀現(xiàn)實啊……

            不過我不否認,想寫一個正確的C++程序是一件非常困難的事情,以至于連我在【只有我自己用的那部分library】里面都不是總是遵守各種各樣的規(guī)則,反正我寫的代碼,我知道怎么用。不過公司的代碼都是一大堆人一起寫的,就像sqlserver一個組有一千多人一樣(oracle是我們的十幾倍,我們還能活下來真是太不容易了)——你能每個人都溝通到嗎?撐死了就幾十個吧,才不到10%。天知道別人會在代碼里面干什么。所以寫代碼是不能太隨便的。同理,招人也不能太隨便,特別是你們這些連code review都不做的公司,你平時都不能阻止他checkin垃圾代碼,你還敢招不合格的人嗎

            現(xiàn)在我們回到面向?qū)ο蟮臇|西。Exception其實也應(yīng)該算在contract里面,所以其實interface里面的函數(shù)會拋什么異常是需要明確的表達出來的。但是checked exception這個東西實在是太蠢了,因為這個規(guī)則是不能組合的,會導致上面說的error返回值一樣的“接口信息大爆炸”。

            所有不能組合的東西都是很難用的,譬如checked exception,譬如鎖,譬如第一篇文章說的C語言那個碉堡了的函數(shù)指針數(shù)組作為參數(shù)的一個成員函數(shù)指針類型的聲明什么的。

            如果你不直接寫出這個函數(shù)會拋exception,那要怎么辦呢?方法有兩個:
            1:你給我把文檔寫好了,而且你,還有你,用這個library之前,給我RTFM!
            2:就跟VisualStudio一樣支持xml注釋,這樣VS就可以在你調(diào)用這個函數(shù)的時候用tooltip的形式提示你,你需要注意這些那些事情……

            什么?你不用IDE?給我RTFM!你連文檔都不看?滾!明天不要來上班了!

            突然發(fā)現(xiàn)本來要寫面向?qū)ο蟮模Y(jié)果Exception也寫了相當長的一段。這件故事告訴我們,就算你不知道interface as contract是什么意思,你還能湊合寫點能維護的代碼。但是你Exception用得不好,程序就寫成了渣,這個問題比較嚴重,所以寫的也就比較多了。所以下面我們真正來談contract的事情。需要注意的是,C++對這種東西是用你們很討厭的東西來支持的——模板和偏特化。

            contract的概念是很廣泛的。對于面向?qū)ο笳Z言來說,int這種東西其實也可以是一個類。你們不要老是想著編譯后生成什么代碼的事情,語法這種東西只是障眼法而已,編譯出來的東西跟你們看到的可以是完全不同的。一個典型的例子就是尾遞歸優(yōu)化了。還有C#的int雖然繼承自object但是你直接用他的話生成出來的代碼跟C++是沒什么區(qū)別的——因為編譯器什么后門都可以開!

            那我們就拿int來說吧。int有一個很重要的特征,就是可以用來比較。C++怎么表達這個事情的呢?

            struct int
            {
                ......
                bool operator<(int i);
                ......
            };

            如果你想寫一個排序函數(shù),內(nèi)部想用<來排序的話,你不需要在接口上寫任何東西,你只需要假設(shè)那個typename T的T可以被<就好了。所有帶有<的類型都可以被這個函數(shù)使用。這特別的structure typing,而且C++沒有concept mapping,導致了你無法在接口上表達“這個類必須帶<”的這件事情,所以一旦你用錯了,這錯誤信息只能跟煙霧一般繚繞了……

            concept mapping其實也是一個面向?qū)ο蟮奶貏e好用特征不過這太高級了估計很多人都沒用過——你們又不喜歡haskell和rust——那對于我們熟悉的面向?qū)ο蟮奶匦詠碇v,這樣的事情要怎么表達呢?

            于是我們偉大的先知Anders Hejlsberg菊苣就做了這么個決定(不是他干的也是他手下干的!)

            interface IComparable // .net 1.0沒有模板,只好搞了這么個傻逼東西出來
            {
                int CompareTo(object o); //具體的忘了,這是我根據(jù)記憶隨便寫的
            }
            
            interface IComparable<T>
            {
                int CompareTo(T o);
            }
            
            struct Int32 : IComparable, IComarable<T> ...
            {
                ......
            }

            所以你的排序函數(shù)只需要寫成:

            void Sort<T>(T[] data)
                where T : IComparable<T>
            { ...... }

            看看IComparable<int>這個傻逼。我為什么要創(chuàng)建一個對象(IComparable<int>),他的職責就是跟另一個int作比較?這實在是太蠢了,無論怎么想都不能想出這種對象到底有什么存在的意義。不過因為C#沒有concept mapping,于是看在interface as contract的份上,讓interface來干這種事情其實也是很合理的。

            所以contract這個東西又賦予了一個更加清晰的意義了。我們其實想要表達的事情是“我們可以對這個參數(shù)做什么事情”,而不是“這個參數(shù)是什么類型”。所以在這個Sort函數(shù)里面,這個T其實不代表任何事情,真正起到聲明的作用的是where T : IComparable<T>這一句,指明了data數(shù)組里面的所有東西都是可以被排序的。那你可能會問,為什么不把IComparable<T>改成IComparable然后干脆把參數(shù)改成IComparable[] data呢?雖然說這樣做的確更加“面向?qū)ο?#8221;,但是實在是太不現(xiàn)實了……

            本來面向?qū)ο筮@種概念就不是特別的可以表達客觀現(xiàn)實,所以出現(xiàn)一些這種狀況,也是正常的。

            #region 題外話

            看看這兩個函數(shù):

            void Sort<T>(T[] data) where T:IComparable<T>;
            void Sort(IComparable[] data);

            他們互相之間存在了一個特別優(yōu)美的(數(shù)學意義上的)變換,發(fā)現(xiàn)沒有,發(fā)現(xiàn)沒有!所以對于動態(tài)類型語言(interface可以從代碼里面得到)做一些“靜態(tài)化”的優(yōu)化的時候,就可以利用類似的技巧——咳咳,說太遠了,趕緊打住。誰說懂點代數(shù)對編程沒用的?哼!

            #endregion

            在這里我們終于看到了contract在帶泛型的純潔的面向?qū)ο笳Z言里面的兩種表達方法。你可能會想,我想在java里面干這種事情怎么辦?還是換C#吧。那我們拿到一個class的時候,這代表什么呢?其實我們應(yīng)該看成,其實我們拿到的是一個interface,只是他恰好只有一個實現(xiàn)。所以在這種時候,你最好不要依賴于“這個interface恰好只有一種實現(xiàn)而且我知道他是什么”的這個事情,否則程序?qū)懘罅耍銜l(fā)現(xiàn)你越來越不滿足“面向interface編程”的這個原則,代碼越來越難處理了。

            我們可能會想到另一件事情,先知們跟我們說,當你設(shè)計函數(shù)參數(shù)的類型的時候,這個類型越基,哦不,越是在繼承鏈里面距離object靠得越近越好,這是為什么呢?這其實也是一個interface as contract的問題。舉個例子,我們需要對一個數(shù)組求和:

            T Sum<T>(T[] data, Func<T, T, T> 加法函數(shù)); // C#真的可以用中文變量的哦!

            費盡心思終于寫好了,然后我們第二天發(fā)現(xiàn),我們對List<T>也要做一樣的事情。那怎么辦呢?

            在這里,Sum究竟對data有什么需求呢?其實研究一下就會發(fā)現(xiàn),我們需要的只是想遍歷data里面的所有內(nèi)容而已。那data是不是一個數(shù)組,還是列表,還是一顆二叉樹,還是你垃圾ipad里面的一些莫名其妙的數(shù)據(jù)也好,其實都一樣。那C#里面什么interface代表“遍歷”這個contract呢?當然是IEnumerable<T>了:

            T Sum<T>(IEnumerable<T> data, Func<T, T, T> 加法函數(shù)); // C#真的可以用中文變量的哦!

            這樣你的容器只要能夠被foreach,也就是繼承自IEnumearble<T>,就可以用這個函數(shù)了。這也是為什么”linq to 容器”基本上都是在IEnumerable上做的,因為他們只需要遍歷。

            哦,我們還說到了foreach。foreach是一個語句,用來遍歷一個容器。那你如何表達一個容器可以被foreach拿來遍歷的這個contract呢?還是IEnumerable<T>。interface拿來做contract的確是最佳匹配呀。

            其實說了這么多,我只想表達一個東西。不要太在意“這個變量的類型是什么”,你要關(guān)心的是“我可以對這個變量做這樣那樣的事情”。這就是為什么我們會推薦“面向接口編程”,因為人總是懶散的,需要一些約束。interface不能用來new,不包含成員變量,剛好是contract的一種很好地表達方法。C++表達contract其實還可以用模板,不過這個就下次再說了。如果你們非得現(xiàn)在就知道到底怎么用的話,我就告訴你們,只要把C#的(i as IComparable<int>).CompareTo(j)換成Comparable<int>::Compare(i, j)就好了。

            所以在可能的情況下,我們設(shè)計函數(shù)的參數(shù)和返回值的類型,也盡量用更基的那些類型。因為如果你跟上面的Sum一樣,只關(guān)心遍歷,那么你根本不應(yīng)該要求你的參數(shù)可以被隨機訪問(數(shù)組就是這個意思)。

            希望大家看完這篇文章之后可以明白很多我們在面向?qū)ο缶幊痰臅r候,先知們建議的那些條款。當然這里還不涉及設(shè)計模式的東西。其實設(shè)計模式說白了是語法的補丁。設(shè)計模式這種東西用的最多,C#略少,你們會發(fā)現(xiàn)像listener模式這種東西C#就不常用,因為它有更好的東西——event。

            posted on 2013-05-04 19:29 陳梓瀚(vczh) 閱讀(15565) 評論(16)  編輯 收藏 引用 所屬分類: 啟示

            評論:
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚?[未登錄] 2013-05-04 19:56 | raof01
            我覺得contract并不僅僅指能做什么事情,還包括做事情前后的狀態(tài)應(yīng)該是什么樣子  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-04 21:03 | silverbullettt
            寫得太好了臥槽  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-04 23:17 | 陳梓瀚(vczh)
            @raof01
            嗯,這個我把它用不變量放在了class的封裝里面去了——但是interface沒有“數(shù)據(jù)”,所以就不強調(diào)這個。  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚?[未登錄] 2013-05-05 00:07 | raof01
            @陳梓瀚(vczh)
            用不變量來表示狀態(tài)碉堡了,呵呵,我以前怎么沒想到。這樣能區(qū)分POD和class。云風說沒有狀態(tài)估計說的是POD。  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 00:35 | 陳梓瀚(vczh)
            @raof01
            游戲嘛,除了script寫邏輯以外,其他的部分業(yè)務(wù)室是簡單的,云風喜歡那樣也是情有可原。  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 02:36 | ccsdu2009
            寫的不錯 支持你  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 03:14 | godzza
            1:把錯誤信息原封不斷的返回
            2:把ThisFunction的錯誤信息包裝成ThatFunction的錯誤信息
            不過我們知道其實這兩種方法都一樣,所以我們采用第一種:
            ---------------------------------------
            1:把錯誤信息原封不動的返回
            2:把ThisFunction的錯誤信息包裝成ThatFunction的錯誤信息
            不過我們知道其實這兩種方法都一樣,所以我們采用第二種:

            貌似是這樣?  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 03:45 | sorra
            void Sort<T>(T[] data) where T:IComparable<T>;

            Java: <T extends Comparable<T>> void sort(T[] data);

            感覺差不多吧  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 05:46 | 陳梓瀚(vczh)
            @godzza
            2:是第一種,原封不斷的返回(到了tuple里面)  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 07:10 | 溪流
            學習了  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 07:34 | yrj
            From wikipedia

            Concept (generic programming)
            http://en.wikipedia.org/wiki/Concept_(generic_programming)

            As generics in Java and C# have some similarities to C++'s templates, the role of concepts there is played by interfaces. However there is one important difference between concepts and interfaces: when a template parameter is required to implement a particular interface, the matching type can only be a class that implements (explicitly) that interface. Concepts bring more flexibility because they can be satisfied by two ways:
            - explicitly defined as satisfied by using a concept map (defined separately to the type itself, unlike interfaces)
            - implicitly defined for "auto concepts", which can be used also for built in types and other types that were not predestined for this use  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-05 19:27 | 永遇樂
            等C++語法支持concept了,就能更自然地造出interface了  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚?[未登錄] 2013-05-07 05:57 | 唐風
            異常處理這段深有同感。

            現(xiàn)在做的系統(tǒng)中流用了大量的C代碼,全部是error-check方式。

            只能說,用error-check大部情況下只是“掩耳盜鈴”而已,Check一下函數(shù)出錯了就打個屏,完全沒有想過是否可能恢復(fù),如何恢復(fù),也沒有辦法想,error-code根本就傳不出去。

            所以軟件“品質(zhì)”就成了“測出來的”,而不是設(shè)計出來的了,因為設(shè)計的時候根本就無法處理這么復(fù)雜的層次的函數(shù)調(diào)用帶來的錯誤碼,而且中間只要有人不傳遞就完蛋了。
              回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚?[未登錄] 2013-05-07 06:01 | 唐風
            另,正在做提案去error-code化,加exception的體系設(shè)計。
            但是做嵌入式,大家都很敏感,所以還是要先把現(xiàn)在所用的平臺和編譯環(huán)境中,exception空間和時間開銷分析清楚給客戶。目前還在整,感覺時間開銷上完全沒有問題(特別是normal case中,異常時其實慢一點也無所謂了,因為設(shè)計中的異常真的是非常非常地異常!),但代碼膨脹還是有點點擔心(因為存儲空間比較有限)。不知道 MS 是不是有這方面的數(shù)據(jù)?特別是WinCE500方面……  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-05-12 00:35 | 陳梓瀚(vczh)
            @唐風
            WinCE的我還真不知道,不過我想這應(yīng)該是編譯器的問題吧。  回復(fù)  更多評論
              
            # re: 如何設(shè)計一門語言(三)&mdash;&mdash;什么是坑(面向?qū)ο蠛彤惓L幚? 2013-10-25 21:24 | lichray
            // C++沒有好用的tuple就是臥槽

            Try Boost.Fusion.  回復(fù)  更多評論
              
            久久精品国产亚洲AV电影 | 精品久久久久久无码不卡| 久久综合久久性久99毛片| 亚洲午夜无码久久久久小说| 亚洲一区精品伊人久久伊人| 久久精品亚洲AV久久久无码| 精品久久久久久国产潘金莲| 亚洲国产二区三区久久| 久久久久无码精品| 亚洲va国产va天堂va久久| 久久国产热精品波多野结衣AV| 国产高清国内精品福利99久久| 伊人久久大香线蕉无码麻豆| 97久久香蕉国产线看观看| 久久久久国产视频电影| 亚洲国产精品无码久久SM| 777米奇久久最新地址| 久久亚洲国产精品123区| 99精品久久精品一区二区| 狠色狠色狠狠色综合久久| 欧美精品福利视频一区二区三区久久久精品 | 久久99国产精品99久久| 久久久久亚洲精品无码网址| 久久99精品久久久久久动态图| 日本久久久精品中文字幕| 久久久久久曰本AV免费免费| 色综合久久中文综合网| 亚洲∧v久久久无码精品| 国产精品九九久久免费视频 | 成人久久免费网站| 一本大道加勒比久久综合| 狠狠综合久久综合88亚洲| 国产叼嘿久久精品久久| 亚洲AV日韩精品久久久久久久| 99热热久久这里只有精品68| 久久中文骚妇内射| 久久久无码精品亚洲日韩蜜臀浪潮| 国内精品久久久久影院网站| www.久久99| 国产精品99久久免费观看| 午夜久久久久久禁播电影|