The Programming Language Idioms
Click below to go directly to a specific section:
重載與重寫 | 靜態(tài)構(gòu)造函數(shù) | 只讀代理 | 同步代理 | 資源管理 | 構(gòu)造函數(shù)中的虛函數(shù)
?強(qiáng)制針對接口編程 | 抗變與協(xié)變 | friend interface | ctor vs. setter | 雜項(xiàng)
重載與重寫
問題
日常討論中,術(shù)語的不統(tǒng)一帶來些許混亂
慣用的表達(dá)
overload |
重載 |
函數(shù)名稱相同,參數(shù)不同(嚴(yán)格的定義還有其它一些限制) |
靜態(tài)決議 |
override |
重寫(覆寫,覆蓋,改寫) |
子類重新定義父類定義過的虛函數(shù)(個(gè)別語言允許返回值,訪問級別可以不同) |
動(dòng)態(tài)決議 |
示例
???? class Base { ???? } ???? class Derived:Base { ???? } ???? class Client { ???????? void Test(Base obj){ ????????????? Console.WriteLine("base"); ???????? } ???????? void Test(Derived obj){ ????????????? Console.WriteLine("derived"); ???????? } ???????? staticvoid Main(string[] args) { ????????????? Base obj = new Derived(); ????????????? new Client().Test(obj);??? //輸出“base” ???????? } ???? } |
靜態(tài)構(gòu)造函數(shù)
問題
1,在工具類中,通常有一些初始化需要在任何靜態(tài)方法被調(diào)用前進(jìn)行,如配置信息的讀取
2,普通類中的復(fù)雜的靜態(tài)信息,需要在任何實(shí)例方法被調(diào)用前初始化
我見過的解決方法
1,在每個(gè)靜態(tài)方法中都調(diào)用必需的初始化步驟 ???? publicclass SomeUtilClass { ???????? private SomeUtilClass(){ ???????? } ???????? privatestaticvoid Init(){ ????????????? //.... ???????? } ???????? publicstaticstring GetUID(){ ????????????? Init(); ????????????? return uid; ???????? } ????????? publicstaticstring GetConnectionString(){ ????????????? Init(); ????????????? return connString; ???????? } ???? } |
2,在普通構(gòu)造函數(shù)中初始化 ???? publicclass SomeMapperClass{ ???????? privatestatic Hashtable types; ???????? public SomeMapperClass(){ ????????????? if(types == null){ ?????????????????? types = new Hashtable(); ?????????????????? types.Add("RED", Color.Red); ?????????????????? types.Add("GREEN", Color.Green); ?????????????????? types.Add("BLUE", Color.Blue); ????????????? } ???????? } ???????? public Color GetColor(string color){ ????????????? return (Color)types[color]; ???????? } ???? } ? |
我推薦的解決方法
使用靜態(tài)構(gòu)造函數(shù)(C#),或靜態(tài)初始化塊(Java)
[C#] ???? publicclass SomeClass { ???????? static SomeClass(){ ????????????? Init(); ????????????? types = new Hashtable(); ????????????? types.Add(...); ????????????? types.Add(...); ???????? } ???? } |
[Java] ???? publicclass SomeClass { ???????? static{ ????????????? Init(); ????????????? types = new HashMap(); ????????????? types.put("", ""); ????????????? types.put("", ""); ???????? } ???? } |
效果
1,Once,only once
2,定義中對異常處理等有要求,可參考規(guī)范
2,多線程時(shí)是否有問題,我不清楚,討論一下
只讀代理
問題
對象內(nèi)部有一個(gè)集合,由這個(gè)對象來控制其元素的增加刪除,但客戶需要訪問該集合取得自己想要的信息,而對象不可能為所有的客戶都提供對應(yīng)的方法,因此需要返回內(nèi)部的這個(gè)集合,但不允許客戶增加或刪除其元素
我見過的解決方法
直接返回代表集合的成員引用,僅在文檔中要求客戶不能增刪集合中的元素 ???? publicclass SomeClass { ???????? private List attrs; ???????? public List GetAttributes(){ ????????????? return attrs; ???????? } ???? } |
我推薦的解決方法
1,首選語言提供的功能
2,次選類庫提供的功能
3,自己包裝代理類,或返回深度拷貝,或使用AOP
[C++] class config { public :
???? const list ???????? return attrs; ???? } private : ???? list attrs; }; |
[C#] ???? publicclass SomeClass { ???????? private IList attrs; ???????? public IList GetAttributes(){ ????????????? return ArrayList.ReadOnly(attrs); ???????? } ???? } |
[Java] ???? publicclass SomeClass { ???????? private List attrs; ???????? public List getAttributes(){ ????????????? return Collections.unmodifiableList(attrs); ???????? } ???? } |
效果
1, 語言提供的功能可幫助在編譯期進(jìn)行檢查,確保程序中連試圖增刪元素的代碼都不存在;但對有意無意的const轉(zhuǎn)型無能為力
2,類庫 提供的功能可幫助在運(yùn)行期進(jìn)行檢查,確保程序中試圖增刪元素的操作都拋出異常
同步代理
問題
為了對象的線程安全引入了同步機(jī)制,卻使對象在單線程環(huán)境下付出了不必要的性能上的代價(jià),曾經(jīng)的例子如寫時(shí)拷貝COW
我見過的解決方法
就是視而不見,不做任何處理,使用同步原語
[C#] ???? publicclass SomeClass { ???????? [MethodImplAttribute(MethodImplOptions.Synchronized)] ???????? publicvoid Add(string name){ ????????????? attrs.Add(name); ???????? } ???? } |
[Java] ???? publicclass SomeClass { ???????? publicsynchronizedvoid Add(string name){ ????????????? attrs.add(name); ???????? } ???? } ? |
我推薦的解決方法
參考類庫的實(shí)現(xiàn),提供沒有同步的原始類,及有同步的代理類;早期的JDK中Vector及HashTable都是同步的類,新的ArrayList及 HashMap都不是同步的,Collections提供了靜態(tài)方法返回同步代理;當(dāng)在多線程環(huán)境中需要更改集合時(shí),使用代理類
[C#,多線程環(huán)境中使用同步代理的客戶類代碼] ???? publicclass SomeClass { ???????? public SomeClass(IList source){ ????????????? attrs = ArrayList.Synchronized(source); ???????? } ???????? publicvoid Add(string name){ ????????????? attrs.Add(name); ???????? } ???????? publicvoid Remove(string name){ ????????????? attrs.Remove(name); ???????? } ???? } [C#,單線程環(huán)境中使用同步代理的客戶類代碼] ???? publicclass OtherClass{ ???????? public OtherClass(IList source){ ????????????? attrs = source; ???????? } ???????? publicvoid Add(string name){ ????????????? attrs.Add(name); ???????? } ???????? publicvoid Remove(string name){ ????????????? attrs.Remove(name); ???????? } ???? } |
[Java,多線程環(huán)境中使用同步代理的客戶類代碼] ???? publicclass SomeClass { ???????? public SomeClass (List source){ ????????????? attrs = Collections.synchronizedList(source); ???????? } ???????? publicvoid add(string name){ ????????????? attrs.add(name); ???????? } ???? } [Java,單線程環(huán)境中使用同步代理的客戶類代碼] ???? publicclass OtherClass{ ???????? public OtherClass(List source){ ????????????? attrs = source; ???????? } ???????? publicvoid add(string name){ ????????????? attrs.add(name); ???????? } ???? } |
效果
不必為不需要的功能付出額外的代價(jià)
資源管理
問題
有時(shí)需要精確的控制資源分配和釋放的時(shí)機(jī),保證資源的異常安全,避免資源泄漏,導(dǎo)致死鎖,文件丟失,數(shù)據(jù)庫連接過多等
我見過的解決方法
在缺乏真正的局部對象和析構(gòu)函數(shù)的語言中,try/catch/finally充斥在代碼中
使用中間件可幫助解決部分資源管理,如數(shù)據(jù)庫連接等
可能會(huì)出現(xiàn)基于AOP的資源管理框架
我推薦的解決方法
在C++中,自動(dòng)化的資源管理是與生俱來的,即B.S.提出的“資源管理即初始化”(RAII)
在C#中,可使用using+IDispose取得近似RAII的效果
在Java中,我不知道,討論一下
[C++,RAII,僅僅示例,操作文件應(yīng)首選std::fstream等] class File { public : ???? explicit File(string path){ ???????? pf = fopen(path.c_str(), "rwb"); ???? } ???? ~File(){ ???????? fclose(pf); ???? } ???? operator FILE* (){ ???????? return pf; ???? } private : ???? FILE* pf; }; [C++,RAII的客戶代碼,僅僅示例,操作文件應(yīng)首選std::fstream等] void test () { ???? File file("auto.txt"); ???? char buf[256]; ???? fread(buf, 0, 256, file);// 即使這個(gè)操作會(huì)拋出異常,文件依然會(huì)被關(guān)閉 } |
[C#,僅僅示例] ???? publicclass File:IDisposable { ???????? private FileStream fs; ???????? public File(string path){ ????????????? fs = new FileStream(path, FileMode.Create); ???????? } ???????? publicstaticimplicitoperator FileStream(File file) { ????????????? return file.fs; ???????? } ???????? publicvoid Dispose() { ????????????? fs.Close(); ???????? } ???? } [C#,僅僅示例] ???? publicclass Test{ ???????? void test(){ ????????????? using(File file = new File("auto.txt")){ ?????????????????? //some read, write, etc. ????????????? } ????????????? // 文件已經(jīng)被關(guān)閉,即使某步操作拋出異常 ???????? } ???? } |
效果
1,資源管理自動(dòng)化,不局限于內(nèi)存
2,C++中使用模板,可統(tǒng)一定義大部分資源的包裝類,目前的C#只能為每種資源定義單獨(dú)的類,或者使用AOP
構(gòu)造函數(shù)中的虛函數(shù)
語言特性
[C++] 虛函數(shù)與對象狀態(tài)有關(guān),與訪問權(quán)限(public/protected/private)無關(guān) 只要子類對象構(gòu)造出來了,就可以調(diào)用重寫的方法,不管訪問權(quán)限 |
[Java, C#] 虛函數(shù)與對象狀態(tài)無關(guān),與訪問權(quán)限(public/protected/private/default/internal)有關(guān) 只要訪問權(quán)限允許,就可以調(diào)用重寫的方法,不管子類對象構(gòu)造出來沒有 |
后果
[C++] 在基類構(gòu)造函數(shù)/析構(gòu)函數(shù)里調(diào)用的方法永遠(yuǎn)都是基類的實(shí)現(xiàn),不會(huì)調(diào)到子類;在其它方法里面虛函數(shù)永遠(yuǎn)都是調(diào)到子類的覆寫實(shí)現(xiàn),不管是不是private |
[Java, C#] 在基類構(gòu)造函數(shù)里調(diào)用方法,只要子類覆寫了該方法,就會(huì)調(diào)到子類的實(shí)現(xiàn) |
解決方法
慎重的在構(gòu)造函數(shù)中調(diào)用虛函數(shù),尤其是在Java和C#中,至少應(yīng)該在注釋中說明理由
強(qiáng)制針對接口編程
問題
盡管“針對接口編程”做為一條原則已經(jīng)廣為流傳,但實(shí)際應(yīng)用中仍然隨處可見HashMap,Vector等做為接口參數(shù)、返回值傳來傳去
我見過的解決方法
使用Factory Method返回接口,并最小化具體類構(gòu)造函數(shù)的訪問權(quán)限,或類本身的訪問權(quán)限
我推薦的解決方法
Factory Method依然值得推薦,另外可以利用語言本身的特性來避免多寫一個(gè)Factory Method
在C++中,override一個(gè)虛函數(shù)時(shí)可以任意改變它的訪問權(quán)限,包括將它由public變?yōu)閜rivate;有人說這樣會(huì)破壞封裝,但只要語義正確,有意為之,也沒什么問題
在C#中,可使用“顯式接口成員實(shí)現(xiàn)”
在Java中,我不知道,討論一下,或者用Spring吧
[C++] class ISomeInterface { public : ???? virtualvoid SomeMethod() = 0; }; ? class SomeClass : public ISomeInterface { private : ???? void SomeMethod(){ ???????? std::cout << "Subclass\n"; ???? } }; ? int main(int argc, _TCHAR* argv[]) { ???? SomeClass obj; ???? obj.SomeMethod();??? //Error ???? ISomeInterface& iobj = obj; ???? iobj.SomeMethod();?? //Ok ???? return 0; } |
[C#] ???? publicinterface ISomeInterface { ???????? void SomeMethod(); ???? } ???? publicclass SomeClass:ISomeInterface { ???????? //1 ,不要寫訪問修飾符;2,使用方法全名 ???????? void ISomeInterface.SomeMethod(){ ????????????? System.Console.WriteLine("Subclass"); ???????? } } ???? publicclass Test{ ???????? void test(){ ????????????? SomeClass obj = new SomeClass(); ????????????? obj.SomeMethod();??? //Error; ????????????? ISomeInterface iobj = obj; ????????????? iobj.SomeMethod();?? //Ok ???????? } ???? } |
效果
1,少寫一個(gè)Factory Method
2,不需要控制構(gòu)造函數(shù)的訪問權(quán)限
抗變與協(xié)變
問題
在override 虛函數(shù)時(shí),子類有時(shí)想要返回或處理與父類函數(shù)參數(shù)和返回值略微不同的類型,比如假設(shè)“動(dòng)物類”有一個(gè)“伴侶”的虛函數(shù),其返回值類型為“動(dòng)物類”,但子類 “兔子”override“伴侶”時(shí),需要把返回值改為“兔子”;假設(shè)“鳥類”有一個(gè)“進(jìn)食”的虛函數(shù),其參數(shù)類型為“谷類”,但子類“食鐵鳥” override“進(jìn)食”時(shí),需要把參數(shù)改為“堿性食物”;這時(shí),除了使用泛型可以解決外,就需要用到抗變與協(xié)變
定義
抗變:向父類的方向變化
協(xié)變:向子類的方向變化
語言支持
返回值抗變與參數(shù)協(xié)變會(huì)帶來明顯的類型安全問題,因此,常用的基本是返回值協(xié)變與參數(shù)抗變;對抗變與協(xié)變支持的最全面的是Eiffel,它同時(shí)提供了受束泛型來解決返回值抗變與參數(shù)協(xié)變帶來的類型安全問題
[C++] 只支持返回值協(xié)變 class Animal{ public : ???? virtual Animal const& Spouse() = 0; }; class Rabbit : public Animal{ public : ???? Rabbit const& Spouse(){ ???????? return Rabbit(); //return local reference, don't follow me. ???? } }; |
[C#] 不支持 [Java] 1.5之前不支持,1.5有限度的支持返回值協(xié)變; 另外迫于checked exception的蹩腳實(shí)現(xiàn),Java支持異常聲明的協(xié)變 ? |
?
friend interface
問題
C#和Java取消了friend關(guān)鍵字,增加了“assembly/package”訪問權(quán)限;然而,出于代碼樹合理組織的需要,整個(gè)project常常根據(jù)功能模塊被分解成一些小 project,而一些較大的功能模塊,通常包含幾個(gè)相互協(xié)作的project,導(dǎo)致了不同的assembly或package;有一些操作,這些相互協(xié)作的assemly或package需要能夠訪問,而不允許其它的assembly或package訪問,如各種setter;但現(xiàn)在的 assembly/package訪問權(quán)限無法做到這一點(diǎn),如果使用public,則所有客戶代碼均可訪問,喪失了安全性
解決方案
根據(jù)接口細(xì)分的原則,增加一個(gè)friend interface,將部分操作轉(zhuǎn)移到friend interface中,對內(nèi)聚的幾個(gè)project/assembly/package,使用friend interface作為接口參數(shù)或返回值,對其它的project/assembly/package則使用安全的接口
1), 安全接口,供所有客戶使用 public interface ActivityInstanceSafeInterface { ??? /** ???? * 獲取分配時(shí)間 ???? */ ??? Date getAssignTime();? ??? /** ???? * 獲取提交時(shí)間 ???? */ ??? Date getCommitTime();? ??? /** ???? * 獲取激活時(shí)間 ???? */ ??? Date getActivatedTime();??? ??? /** ???? * 獲取最后一次更新時(shí)間 ???? */ ??? Date getUpdateTime();??? } |
2),friend interface,供朋友使用(注意,繼承了安全接口) public interface ActivityInstanceFriendInterface extends ActivityInstanceSafeInterface { ??? /** ???? * 設(shè)置激活時(shí)間 ???? */ ??? void setActivatedTime(Date date);??? ??? /** ???? * 設(shè)置提交時(shí)間 ???? */ ??? void setCommitTime(Date date);??? ??? /** ???? * 設(shè)置分配時(shí)間 ???? */?? ? ??? void setAssignTime(Date date);??? ??? /** ???? * 設(shè)置最后一次更新時(shí)間 ???? */ ??? void setUpdateTime(Date date);? ??? /** ???? * 清除收到的 token ???? */ ??? void clearTokens(); ??? boolean canStart(); ??? boolean canFinish(); } |
3),實(shí)現(xiàn)類,實(shí)現(xiàn)所有接口 public class ActivityInstance implements ActivityInstanceFriendInterface { //…… } |
?
ctor vs. setter
二者之間的使用場景基本已有定論,這里概述一下
01# |
決定對象的有效性(或合法性)的資源(即任何業(yè)務(wù)邏輯方法第一次調(diào)用前必須初始化的屬性),應(yīng)該使用ctor獲取 |
02# |
對象生命期內(nèi)不變的屬性,應(yīng)該使用ctor獲取 |
03# |
實(shí)際應(yīng)用中,推薦盡量使用上述兩條規(guī)則,除非方便性比正確性重要的場合 |
雜項(xiàng)
問題
在代碼中發(fā)現(xiàn)了一些書籍中曾經(jīng)提到的小問題,列在這里,提醒一下
條款
01# |
請私有化singleton類的構(gòu)造函數(shù) |
02# |
如果不用子類化代替flag,至少使用enum代替接口常量來表示flag |
03# |
訪問控制(public,private等)是針對類型的,不是針對對象的,同一種類型的對象可以任意訪問彼此的私有成員 |
?
【推薦參考資料】
1.C#標(biāo)準(zhǔn):ECMA-334 : C# Language Specification
2.Java標(biāo)準(zhǔn):The Java? Language Specification Second Edition
3.C++標(biāo)準(zhǔn):ISO/IEC 14882:2003 Programming Languages - C++
4.The C# Programming Language
5.The Java Programming Language
6.The C++ Programming Language