WEB开发网      濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堝姛缂佺娀绠栭弻宥堫檨闁告挻姘ㄩ幑銏犫槈濞嗘劕顎撻梺鍛婂姇瀵爼骞栭幇顔炬/闁告挆鍕畬闂佸疇顫夐崹鍧楀箖閳哄啠鍋撻崷顓炐㈡い銉︾箞濮婂搫效閸パ€鍋撳Δ鍛;闁规崘顕ф闂佸憡娲﹂崹鎵不濞戙垺鐓曟い鎰剁稻缁€鍐┿亜鎼达紕效婵﹨娅g划娆忊枎閹冨闂備礁鎽滄慨鐢稿礉濞嗘劒绻嗛柣銏⑶圭粈瀣亜閺嶃劏澹橀柛鐐姂濮婃椽妫冨ù銈嗙⊕閹峰懘骞撻幒宥咁棜闂備礁婀遍崕銈夈€冮崱娑樼厱闁圭儤顨嗛悡鏇㈡煛閸ャ儱濡煎ù婊勭矋閵囧嫯绠涢敐鍛睄闂佸搫澶囬埀顒€纾弳鍡涙倵閿濆骸澧伴柡鍡欏█閺屟勫濞嗘垵鍩岄梺闈涙鐢帡锝炲┑瀣亗閹艰揪绲奸悽鑽ょ磽娴h娈曢柛銊ョ仢椤繒绱掑Ο璇差€撶紓浣圭☉椤戝懎鈻撻鐐╂斀妞ゆ梹鏋婚崗顒傜磼閻樿櫕宕岄柕鍡曠椤繈骞囨担鍏夋瀸濠电姷鏁告慨顓㈠磻閹捐秮褰掓晲閸モ斂鈧﹪鏌¢埀顒佺鐎n偆鍘藉┑鈽嗗灡椤戞瑩宕电€n兘鍋撶憴鍕仩闁稿氦绮鹃悘鍐⒑缂佹◤顏勵嚕閸洖鐤柣鎰暩绾惧ジ鏌涚仦鐐殤閺佸牓鎮楃憴鍕缂侇喖绻樿棟閻庨潧鎽滃Λ顖炴煙椤栧棔绀佹禒顕€鎮楀▓鍨灈闁绘牜鍘ч悾鐑芥偂鎼存ɑ顫嶅┑鈽嗗灟鐠€锕傛倵瀹曞洨纾介柛灞剧懅閸斿秵銇勯妸銉︻棞闁伙絾绻堥獮鏍ㄦ媴濮濆本鎲伴梻浣虹帛濡啴藟閹捐姹查悗锝庡枟閻撶喐淇婇妶鍌氫壕闂佺粯顨呭Λ妤呭煝閹炬緞鏃堝川椤旇瀚奸梺鑽ゅТ濞茬娀鍩€椤掑啯鐝柣蹇婂亾闂傚倷绀侀幖顐﹀箠閹邦厽鍙忛柟缁㈠枟閸嬧晠鏌i妶搴$仜濞存粌缍婇弻鐔兼倻濡偐鐣洪梺鍝勬噺缁诲牆顫忓ú顏咁棃婵炴垶鑹鹃。鍝勨攽閳藉棗浜濋柣鐔叉櫊閵嗕礁鈻庨幒鏃傛澑闂佸搫鍟崐濠氭儊閸儲鈷戞慨鐟版搐閻忓弶绻涙担鍐插椤╃兘鏌ㄩ弴鐐测偓褰掓偂閺囥垺鐓忓┑鐐茬仢閸斻倝鏌涢埡瀣ɑ妞ゃ劊鍎甸幃娆撳级閹存繍娼氭俊銈囧Х閸嬬偤鏁冮姀銈冣偓浣糕枎閹炬潙娈愰梺鍐叉惈椤戝洭鐛姀銈嗏拻闁稿本鐟︾粊鐗堛亜椤愩埄妲搁柣锝呭槻铻i柤娴嬫櫇閻撳顪冮妶鍡橆梿闁跨喆鍎茬粋宥堛亹閹烘挾鍘甸梺缁樺灦钃遍悘蹇e幖闇夋繝濠傚暟缁夌儤鎱ㄦ繝鍛仩缂佽鲸甯掕灒闁惧繘鈧稒顢橀梻鍌欑劍鐎笛兠哄澶婄柧婵炴垶绮庢禍閬嶆⒒娴e憡鍟炴繛璇х畵瀹曞綊鏌嗗鍛幈闂佺鎻梽鍕偂濞嗘挻鐓犳繛鏉戭儐濞呭懎霉閻樺磭鐭婇柍瑙勫灴閸ㄩ箖鎮欓挊澶夊垝闂備浇顕栭崰妤呫€冮崨鏉戠叀濠㈣埖鍔曠粻鎶芥煙閹屽殶鐟滄澘娲ㄧ槐鎾诲磼濞嗘垼绐楅梺鍝ュУ閻楃娀銆侀弽顓炲窛闁圭⒈鍘介弲锝夋⒑缁嬭法绠抽柛妯犲懏顐介柣鎰節缁诲棙銇勯弽銊х煂閻㈩垱绋掔换娑㈠川椤撶喎鏋犲┑顔硷功缁垶骞忛崨瀛樺仭闂侇叏绠戝▓婵堢磽閸屾瑦绁版い鏇嗗洤纾归柛顭戝櫘閸ゆ洜绱撴担璐細缂佲檧鍋撻梻浣规偠閸庮垶宕濆鍛瀺闁搞儺鍓氶埛鎴犵磼鐎n偄顕滄繝鈧幍顔剧<閻庯綆鍋呭畷宀€鈧娲忛崹浠嬪箖娴犲宸濆┑鐘插楠炴姊洪悷鏉挎倯闁伙綆浜畷瑙勭節濮橆剛鍘愰梺鍝勬储閸ㄦ椽鎮¢妷锔藉弿婵☆垰鐏濋悡鎰版煟閹捐泛鏋涢柣鎿冨亰瀹曞爼濡烽妷銉バ戠紓鍌欑椤戝牆鐣烽悽鍨潟闁圭儤姊荤壕鍏间繆椤栨繂浜归柣锝堟缁辨挻鎷呴搹鐟扮缂備浇顕ч悧鍡涙偩瀹勯偊娼ㄩ柍褜鍓氭穱濠傤潰瀹€濠冃ㄧ紓鍌欐祰妞村摜鎹㈤崼婵愭綎缂備焦蓱婵绱掑☉姗嗗剰婵炲牊鍔欏娲箹閻愭彃顬嗛梺鍛婎殔閸熷潡鎮鹃悜绛嬫晬闁绘劘灏欐鍥⒑閻熼偊鍤熷┑顕€娼ч埢鎾淬偅閸愨斁鎷虹紓鍌欑劍钃遍柍閿嬪浮閺屽秴鐣¢幍顔尖叺閻庢鍣崑濠傜暦閹烘鍊烽悗鐢登归獮鍫ユ⒒娴g懓鈻曢柡渚囧櫍瀹曟垿骞樼紒妯煎幐闂佸憡渚楅崰姘跺箠閸涱喕绻嗛柛娆忣槸婵洭鎽堕敐澶嬪仩婵炴垶甯掓晶鏌ユ煛閸屾浜鹃梻鍌氬€烽懗鍓佸垝椤栫偛绀夐柡鍥╁€i悢鍝ョ瘈闁搞儜鍐╁劒闂備胶绮弻銊╂儍濠靛缁╅柤鎭掑劘娴滄粓鏌¢崘銊﹀妞ゃ儱顦甸弻娑㈠棘鐠囨祴鍋撳┑瀣闁割偅娲橀崐鐑芥煟閹寸偍缂氶柛姗€浜跺娲传閸曨剙鍋嶉梺鍛婃煥閺堫剟寮查崼鏇ㄦ晬闁绘劕顕崢鍗炩攽閻愬弶顥滅紒缁樺笧缁粯绻濆顓犲幐闁诲繒鍋熼弲顐f櫏闁诲氦顫夊ú锕傚磻婵犲倻鏆﹂柣鏃傗拡閺佸棝鏌嶈閸撴瑩鍩㈠澶嬫櫜闁搞儮鏅濋敍婵囩箾鏉堝墽绋荤憸鏉垮暞缁傚秹鎮欓鍌滅槇闂侀潧楠忕徊鍓ф兜閻愵兙浜滈柟瀛樼箖瀹告繄绱掗鍓у笡闁靛牞缍佸畷姗€鍩¢崘銊ョ闂備浇顕х€涒晝绮欓幒鎴犲箵閻犳亽鍔庢稉宥嗘叏濡炶浜鹃梺鍝勮閸斿矂鍩ユ径濞㈢喐寰勯惂鍝ョɑ闁靛洤瀚版俊鎼佹晲閸涱厼袝闂備浇顕栭崰妤呮偡閳哄懌鈧線寮崼婵堫槹濡炪倖鎸荤换鍕矆閸曨垱鈷掗柛灞剧懄缁佺増銇勯弴鐔哄⒌鐎规洑鍗冲浠嬵敃閵堝浂妲稿┑鐘垫暩婵挳宕愭繝姘辈闁挎洖鍊归悡娆愩亜閺嶎偄浠滃ù婊呭娣囧﹪鎳犳0婵嗘闂佸疇顫夐崹鍧楀春閸曨垰绀冮柕濞у懌鍋″┑锛勫亼閸娿倝宕㈡ィ鍐ㄧ婵☆垯璀﹂崵鏇㈡偣閸ャ劎銈存俊鎻掔墦閺屾洝绠涢弴鐑嗘綌闂佸啿鎼幊蹇涙偂韫囨搩鐔嗛悹楦挎婢ф洟鏌涢弮鈧幐鎶藉蓟濞戙垹妫樻繛鍡欏亾妤旂紓鍌欐祰妞存悂骞愭繝姘闁告侗鍨抽惌娆撳箹鐎涙ɑ灏ù婊堢畺閺屾稑鈹戦崟顐㈠Б闂佹椿鍘介幐楣冨箟閹间焦鍋嬮柛顐g箘閻熴劑姊洪崫鍕靛剮缂佽埖宀稿濠氭偄閻撳海顦悷婊冪箳閺侇喖鈽夐姀锛勫幐闂佸憡渚楅崰妤呭磹閹扮増鐓涢悘鐐额嚙婵倿鏌熼鍝勨偓婵嗙暦閹烘垟妲堟慨妤€妫旂槐锟� ---闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌i幋锝呅撻柛濠傛健閺屻劑寮崼鐔告闂佺ǹ顑嗛幐鍓у垝椤撶偐妲堟俊顖氭惈缁犺鈹戦悙鍙夆枙濞存粍绮撻幃鈥斥槈閵忥紕鍘卞┑鐐村灥瀹曨剟鐛Ο姹囦簻闁哄倹瀵чˉ銏℃叏婵犲懏顏犻柟鐟板婵℃悂濡烽敂鎯х稈闂傚倷鑳堕幊鎾诲吹閺嶎厼绠柨鐕傛嫹
开发学院软件开发C++ 乌托邦式的接口和实现分离技术 阅读

乌托邦式的接口和实现分离技术

 2008-03-08 12:55:18 来源:WEB开发网 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亜顒㈡い鎰Г閹便劌顫滈崱妤€骞婄紓鍌氬€瑰銊╁箟缁嬫鍚嬮柛顐線缂冩洟姊婚崒娆戭槮婵犫偓闁秵鎯為幖娣妼缁愭鏌″搴′簽濞戞挸绉甸妵鍕冀椤愵澀娌梺缁樻尪閸庣敻寮婚敐澶婂嵆闁绘劖绁撮崑鎾诲捶椤撴稑浜炬慨妯煎亾鐎氾拷闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亝鎹i柣顓炴閵嗘帒顫濋敐鍛婵°倗濮烽崑娑⑺囬悽绋挎瀬闁瑰墽绮崑鎰版煙缂佹ê绗ч柍褜鍓﹂崣鍐潖閸濆嫅褔宕惰娴犲ジ姊虹拠鑼闁煎綊绠栭幃楣冩倻閽樺鎽曢梺闈涱檧婵″洭宕㈤悽鍛娾拺閻熸瑥瀚烽崯蹇涙煕閻樺磭澧甸柕鍡楀€圭缓浠嬪川婵犲嫬骞堥梺纭呭閹活亞妲愰弴鐔哄ⅰ闂傚倷绶氬ḿ褍煤閵堝洠鍋撳顐㈠祮闁绘侗鍣i獮鎺懳旈埀顒傜不閿濆棛绡€闂傚牊绋戦弳娆徝瑰⿰鍫㈢暫闁哄矉缍佹慨鈧柍鎯版硾濠€杈ㄧ珶閺囩喓绡€婵﹩鍘鹃崢鐢告⒑缂佹ê濮﹂柛鎾村哺閹ɑ娼忛妸銈囩畾闂佸湱绮敮鐐存櫠濞戞氨纾肩紓浣贯缚濞插鈧娲栧畷顒冪亙闂佸憡鍔曢崯鐘诲礈濠靛牊宕叉繛鎴炨缚閺嗗棗鈹戦悩杈厡闁轰焦鐗滅槐鎾存媴娴犲鎽甸梺鍦嚀濞层倝鎮鹃悜钘夌闁规惌鍘介崓鐢告⒑閻熸澘鎮侀柣鎺炵畵閹骞栨担鍏夋嫽婵炶揪绲块崕銈夊吹閳ь剟姊洪幖鐐测偓鏍偋閻樿崵宓侀煫鍥ㄧ⊕閺呮悂鏌ㄩ悤鍌涘濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堟敾闁告瑥绻戦妵鍕箻閸楃偟浠肩紓浣哄閸ㄥ爼寮诲☉銏犵疀闂傚牊绋掗悘鍫ユ倵閻熺増鍟炵紒璇插暣婵$敻宕熼姘鳖啋闁诲酣娼ч幗婊堟偩婵傚憡鈷戠痪顓炴媼濞兼劖绻涢懠顒€鏋庢い顐㈢箳缁辨帒螣閼测晜鍤岄梻渚€鈧偛鑻晶顔肩暆閿濆牆鍔垫い锔界叀閹繝濡舵径瀣帾闂佸壊鍋呯换鍐磻椤忓懐绠剧€瑰壊鍠曠花濠氬箚閻斿吋鈷戦悗鍦У閵嗗啴鏌ら崘鑼煟鐎规洘绻堥弫鍐焵椤掑嫧鈧棃宕橀鍢壯囨煕閳╁喚娈橀柣鐔稿姍濮婃椽鎮℃惔鈩冩瘣闂佺粯鐗曢妶绋跨暦閻戞ḿ绡€闁搞儜鍐ㄧギ闂備線娼ф蹇曟閺囥垹鍌ㄦい蹇撶墛閳锋垿鏌熼懖鈺佷粶闁告梹顨婇弻锟犲川椤旈敮濮囩紓浣稿€圭敮鐔妓囩€靛摜纾奸弶鍫涘妼缁楁碍绻涢悡搴g闁糕斁鍓濋幏鍛存煥鐎e灚缍楅梻鍌氬€峰ù鍥ь浖閵娾晜鍊块柨鏇炲€哥粻鏌ユ煕閵夘喖澧柡瀣╃窔閺岀喖宕滆鐢盯鏌¢崨顔藉€愰柡灞诲姂閹倝宕掑☉姗嗕紦闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亜顒㈡い鎰Г閹便劌顫滈崱妤€骞婄紓鍌氬€瑰銊╁箟缁嬫鍚嬮柛顐線缂冩洟姊婚崒娆戭槮婵犫偓闁秵鎯為幖娣妼缁愭鏌″搴′簽濞戞挸绉甸妵鍕冀椤愵澀娌梺缁樻尪閸庣敻寮婚敐澶婂嵆闁绘劖绁撮崑鎾诲捶椤撴稑浜炬慨妯煎亾鐎氾拷  闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌i幋锝呅撻柛銈呭閺屻倝宕妷锔芥瘎婵炲濮靛銊ф閹捐纾兼繛鍡樺笒閸橈紕绱撴笟鍥ф珮闁搞劌鐖兼俊鎾礃椤旂厧绐涢梺鍝勵槹閸ㄥ綊宕㈠ú顏呭€垫鐐茬仢閸旀碍銇勯敂璇茬仸鐎规洩绻濋獮搴ㄦ嚍閵壯冨妇闂傚⿴鍋勫ú锕€煤閺嶃劎澧¢梻鍌欐祰椤曆呪偓鍨浮瀹曟粓鎮㈡總澶嬬稁闂佹儳绻愬﹢杈╁閸忛棿绻嗘い鏍ㄧ閹牊銇勯銏㈢劯婵﹥妞藉畷鐑筋敇濞戞瑥鐝遍梻浣呵归鍛涘┑瀣畾闁逞屽墯閵囧嫯绠涢幘瀵搞偐濠碘槅鍨扮€氭澘顫忓ú顏勪紶闁告洦鍓氶幏鍗炩攽閻愭彃绾у畝锝呮健楠炴垿濮€閻橆偅鏂€婵犵數濮寸€氼噣寮堕幖浣光拺闁告繂瀚婵嗏攽椤旀儳鍘撮柟顔诲嵆婵$兘鍩¢崒妤佸濠电偠鎻徊浠嬪箹椤愩倖鏆滈悹杞拌閻斿棝鏌i悢宄扮盎闁衡偓閼姐倗纾奸柛灞炬皑瀛濆Δ妤婁簷閸楀啿鐣烽悡搴僵闁挎繂鎳嶆竟鏇㈡⒑閸濆嫬鏆欓柣妤€妫涚划鍫ュ礃閳瑰じ绨婚棅顐㈡搐濞寸兘藝閿曗偓闇夋繝濠傜墢閻f椽鏌$仦璇插闁宠鍨垮畷鍗炩槈閹典礁浜炬俊銈傚亾妞ゎ叀鍎婚¨渚€鏌涢悩宕囧⒌婵犫偓娓氣偓濮婅櫣绱掑Ο鏇熷灴閹兘濡疯閸嬫挸顫濋悡搴㈢亾缂備緡鍠氱划顖炲Χ閿濆绀冮柍鍝勫暙楠炲牊淇婇悙顏勨偓鏍礉閹达箑纾归柡鍥ュ灩閸戠娀骞栧ǎ顒€濡介柣鎾跺枛閻擃偊宕惰閸庡繘鏌涢弮鈧划鎾诲蓟閺囥垹鐐婄憸宥夘敂椤撶姭鍋撳▓鍨灍婵炲吋鐟ㄩ悘鎺楁⒑閸涘﹦绠撻悗姘煎墲椤ゅ倹绻濈喊澶岀?闁稿鍨垮畷鎰板箣閿曗偓閸ㄥ倹绻涘顔荤凹闁稿绻濋弻宥夊传閸曨剙娅g紓浣哄У閻楃娀寮诲澶婄厸濞达絽鎲″▓鏌ユ⒑缁嬫寧鎹i柛鐘崇墵瀵寮撮姀鐘靛€為悷婊冪Ф閼鸿鲸绻濆顓犲幍闂佸憡鍨崐鏍偓姘炬嫹
核心提示:《Imperfect C++》中展示了一种叫“螺栓”的技术,然而,乌托邦式的接口和实现分离技术,这本书中的讨论并不足够深入,当然,假如我们愿意,也可以把接口的实现分拆到多个具体的实现类当中,我也相信Matthew是故意的,从而让我们这些“三道贩子”(Matthew自称是二道贩子)也能够获得一点点成就感

  《Imperfect C++》中展示了一种叫“螺栓”的技术,然而,这本书中的讨论并不足够深入。当然,我也相信Matthew是故意的,从而让我们这些“三道贩子”(Matthew自称是二道贩子)也能够获得一点点成就感。

  考虑这样一个接口设计:

strUCt IRefCount;
struct IReader : public IRefCount;
  在Reader中实现接口:

<!--[if !supportEmptyParas]--> class Reader : public IReader;

  在上述的继续结构中,IRefCount是一个结构性的类,用来实现引用计数,实际上它和领域逻辑部分IReader没有什么关系。我们打算在IRefCount的基础上,建立了一套工具来治理对象生命周期和帮助实现异常安全的代码 (例如,smart pointer) 。现在来考虑Reader的实现,Reader除了需要实现IReader的接口,还必须实现IRefCount的接口。这一切看起来似乎顺理成章,让我们继续看下面的设计<!--[if !supportEmptyParas]-->:

struct IWriter : public IRefCount;
<!--[if !supportEmptyParas]--> class Writer : public IWriter;
  现在来考虑Writer的实现,和Reader一样,Writer除了要实现IWriter的接口外,同时还需要实现IRefCount的接口。现在,我们来看看IRefCount是如何定义的:

struct IRefCount {
virtual void add() = 0;
virtual void release() = 0;
virtual int count() const = 0;
virtual void dispose() = 0;
virtual ~IRefCount(){}
};
  在Reader中的IRefCount的实现:

virtual void add() { ++m_ref_count;}
virtual void release() {--m_ref_count;}
virtual int count() const{return m_ref_count;}
virtual void dispose() { delete this;}

int m_ref_count;
  同样,在Writer的实现中,也包含了一模一样的代码,这违反了DRY原则(Don’t Repeat Yourself)。况且,随着系统中的类增加,大家都意识到,需要将这部分代码复用。一个能够工作的做法是把IRefCount的实现代码直接放到IRefCount中去实现,通过继续,派生类就不必再次实现IRefCount了。我们来看一下dispose的实现:

virtual void dispose() { delete this;}
  这里,采用了delete来销毁对象,这就意味着Reader必须在堆上分配,才可能透过IRefCount正确治理对象的生命周期,没关系,我们还可以override dispose方法,在Reader如下实现dispose:

virtual void dispose() { }
  但是,这样又带来一个问题,Reader不能被分配在堆上了!假如你够狠,当然,你也可以这么解决问题:

class HeaPReader : IReader;
class StackReader : HeapReader{ virtual void dispose() { } };
  问题是,StackReader 是一个HeapReader吗?为了代码复用,我们完全不管什么概念了。当然,假如你和我一样,看重维护概念,那么这么实现吧:

class HeapReader : IReader;
class StackReader : IReader;
  这样一来,IReader的实现将被重复,又违反了DRY原则,等着被将来维护的工程师诅咒吧!或许,那个维护工程师就是3个月后的你自己。假如这样真的能够解决问题,那么也还是可以接受的,很快,我们有了一个新的接口:

struct IRWiter : IReader, IWriter;
class RWiter : public IRWiter;
  考虑一下IRefCount的语义:它用来记录对所在对象的引用计数。很显然,我从IReader和IWriter中的任意一个分支获得的IRefCount应该都是获得一样的引用计数效果。但是现在,这个继续树存在两个IRefCount的实例,我们不得不在RWiter当中重新重载一遍。这样,从IReader和IWriter继续来的两个实例就作废了,而且,我们可能还浪费了8个字节。为了解决这个问题,我们还可以在另一条危险的道路上继续前进,那就是虚拟继续:

struct IReader : virtual public IRefCount;
struct IWriter : virtual public IRefCount;
  还记得大师们给予的忠告吗--“不要在虚基类中存放数据成员”。“这样有什么问题吗,我们不必对大师盲目崇拜”,你一定也听过这样的建议。假如大师们不能说服这些人,那么我也不能。于是,我们进一步在所有的接口中提供默认实现,包括IReader和IWriter.

  现在的问题是:

struct IRWiter : IReader, IWriter;
  还是

struct IRWiter : virtual IReader, virtual IWriter ?
  假如你没有选择virtual,那么IRWiter被派生后,那么派生类的继续树中可能存在多个IReader实现,假如这个派生类要求只能提供一份IReader的语义怎么办?除了重新实现接口还能怎样?反过来,假如我们选择了virtual继续,那么派生类需要多个实现怎么办?真是个麻烦事。“这是典型的过度设计,我们为什么要考虑这么多?”你可以这么说,但事实上,即使是一个数百文件的小型系统,也完全可能迫使你作出选择。虽然,我们仍然有办法作出拯救措施,但是也只是苟延残喘而已。正如我前面所说,这是一个危险的道路,聪明如你,是断然不会让自己陷入这样的泥潭的。

  让我们离开虚拟继续,先回到重复代码的问题上来。有没有更好的解决办法呢?还好,在C++的世界里,我们有神奇的template,让我们来消除重复的代码:


template<typename Base>
class ImpReader : public Base{
constraint(is_base_derive(IReader, Base))
Implementation IReader
<!--[if !supportEmptyParas]--> };
class HeapReader : ImpReader<IReader>{};
class StackReader : ImpReader <IReader>{
virtual void dispose() {};
<!--[if !supportEmptyParas]--> };
  请注重,我们还是假设IRefCount已经提供了一个默认实现。现在,情况好了很多,所有的代码都只有一份,而且,概念也没有被破坏。假设,Writer也同样需要类似的能力,那么,我们又多了StackWriter和HeapWriter.事实上,真的有人用到了StackWriter吗?我不知道,只是,提供了StackReader,没有理由不提供StackWriter啊。让我们继续。

  现在,我们发现,需要改进内存分配的性能问题,于是,我们希望通过内存池来分配对象,相应的dispose也需要修改:

virtual void dispose(){ distory(this);}
  于是,我们又多出两个类,PoolReader和PoolWriter。这真是糟糕,组合爆炸可不是什么好兆头。

  从我们前述的变化来看,都是IRefCount在变化,为什么不把这种变化分离出来呢?不必为IRefCount提供默认实现,借鉴ImpReader的手法:

template<typename Base>
class ImpHeapRefCount : public Base{
constraint(is_base_derive(IRefCount, Base));
..};
  类似的:

template<typename Base> class ImpStackRefCount : public Base;
<!--[if !supportEmptyParas]--> template<typename Base> class ImpPoolRefCount : public Base; <!--[endif]-->
  再看看,我们如何实现所有的Reader.

typedef ImpReader< ImpHeapRefCount<IReader> > HeapReader;
typedef ImpReader< ImpStackRefCount<IReader> > StackReader;
typedef ImpReader< ImpPoolRefCount<IReader> > PoolReader;
  以HeapReader为例,实际的继续关系是这样的:

ImpReaderàImpHeapRefCountàIReaderàIRefCount;
  对于Writer,我们完全可以采取同样的手法来实现。对于上述的typedef可以预先定义,也完全可以不定义,交给最终用户去组装吧。现在,类的设计者再也不必为选择实现而痛苦了,你只要提供不同的砖头,客户程序员可以轻而易举的建立起大厦。还有比这更让一个设计师幸福的吗?

  继续深入,考察ImpHeapRefCount和ImpStackRefCount的实现,我们提到,dispose方法的实现是不一样的,但是,其他部分:add,releasee和count的实现完全可以相同。然而我们现在又分别实现了一遍,为了不违反DRY原则,我们如下处理:

template<typename Base>
class ImpPartialRefCount : public Base{
//实现add, release和count.
};

template<typename Base>
class ImpHeapRefCount : public Base{
virtual void dispose() { delete this;}
};

template<typename Base>
class ImpStackRefCount : public Base{
virtual void dispose() { }
};
  然后,我们可以这样定义Reader:

typedef ImpReader<ImpHeapRefCount<ImpPartialRefCount<Ireader> > > HeapReader;
  请注重,我们在这里展示了一种能力,不必在一个实现当中完整的实现整个接口,可以把一个接口的实现分拆到多个实现当中。这个能力是非凡的,借助于此,我们可以提供更小粒度的实现单位,给最终用户来组装。具体拆分到什么样的程度完全取决于代码复用的需求,以及概念维护的需要。

  我们提供了高度的复用能力,同时避免了继续带来的强耦合,以及对推迟设计决策的支持,这些能力对于软件设计师而言,正如Matthew在《Imperfect C++》中所说的,这简直就是现实中的乌托邦!

  现在我们把这种手法首先针对单继续做一个小结。对于任意的接口IInterface,我们提供如下的实现:

template<typename Base>
class ImpInterface : public Base{
constraint(is_base_derive(IInterface, Base));
};
  请注重,一个接口可以有任意多个实现,并且可以是任意的部分实现。<!--[if !supportEmptyParas]--> <!--[endif]-->

  假设我们有如下接口继续树:

InterfaceN àInterfaceN_1àInterfaceN_2à…àInterface0
  并且提供了实现类ImpInterface0 ~ ImpInterfaceN.

  那么,InterfaceN的实例类型就是:


typedef ImpInterfaceN<
ImpInterfaceN_1<
ImpInterfaceN_2<

ImpInterface0<InterfaceN> …> > > ConcreteClassN;
  我们注重到,定义ConcreteClassN的时候,我们的ImpInterface是按照顺序来的,我认为这是合适的做法。当然了,最后组装的权力已经交给客户了,客户爱怎么组装就怎么组装吧。然而我们还是有一些需要注重的问题。

  1.假定,我需要在ImpInterfaceI中引用基类的方法,记住,不要使用这样的手法:

ImpInterfaceI_K::SomeMethod();
  这样调用不具有多态性,而应该这样:

this-> SomeMethod();
  2.不要在自己的ImpInterfaceI实现中覆盖基类接口的其他已经实现的方法,假如你一定要这么做,那么务必在文档中说明,因为在组装的时候,顺序将是极其要害的了。

  3.这个方法和设计模式中的Template Pattern目的是不一样的。Template Pattern是在基类中定义了一个算法,让派生类定制算法的某些步骤。这里的方法针对的是接口模型的概念,提供接口和实现分离的技术。

  关于第二条,应该尽量避免发生。这里说的覆盖是指基类实现已经实现了该方法,而后继实现又覆盖该方法。基类实现可以是一个部分实现,对于没有实现的那些方法,在派生接口的实现类中实现则是常见的。一方面,我们尽量合理分解层次之间的功能,另一个方面,可以通过定制实现模板类,来保证顺序。尽可能的让语言本身来保证正确性,而不是依靠文档。我们可以像这样预先装配一些东西:

template<typename Base>
class SomeComponent : public ImpPartA < ImpPartB <Base> >{};
  可惜,C++暂时还不支持模板的不完全typedef,否则,我们还可以如下定以:

template<typename Base>
typedef ImpPartA< ImpPartB<Base> > SomeComponent;
  不过,C0x很可能会支持类似的语法。这样,我们使用SomeComponent来作为一个预制品,来保证一些安全性,而不必完全依靠文档了。 <!--[endif]-->

  看看ConcreteClassN的定义,也许你和我一样,并不喜欢这种嵌套的、递归的定义方式,太难看了。让世界稍微美好一点吧!于是我们提供一个辅助类:

<!--[if !supportEmptyParas]--> template<typename T>struct Empty{};

template<typename I, typename template<class> class B>
struct Merge{ <!--[if !supportEmptyParas]-->typedef B<I> type;};

template<typename I >
struct Merge<I, Empty >{
typedef I type;
<!--[if !supportEmptyParas]--> }; <!--[endif]-->

template

typename I,
typename template<class> class B1,
typename template<class> class B2 = Empty,

typename template<class> class Bn = Empty,

struct Reform{
typedef typename Merge<
typename Merge<
typename Merge<I, B1>::type
, B2>::type , …,Bn>::type type;
};
  现在,我们可以这样定义ConcreteClassN了:

Typedef Reform<InterfaceN, ImpInterface0, ImpInterface1,
…ImpInterfaceN>::type ConcreteClassN;
  是不是清爽了很多?

  在继续下面内容以前,请回味一下这个不是问题的问题:

  假设IReader有3种实现,IRefCount有3种实现,我们将如何漂亮地解决掉他们。 <!--[endif]-->

  现实世界总是要复杂得多,让我们进入真实的世界。回顾这个接口:

struct IRWiter : IReader, IWriter;
  假设我们确实需要IReader, IWriter,但是并不需要IRWrite,可不可以让一个对象同时支持这两个接口呢,就像COM一样?当然可以,我们借助于这样一个辅助模版:

template<typename B1, typename B2>
struct Combine : B1, B2{
typedef B1 type1;
typedef B1 type2;
<!--[if !supportEmptyParas]--> }; <!--[endif]-->

typedef Reform< Combine<IReader, IWriter>, ImpRefCount, ImpWriter, ImpReader >::type ConcreteRWiter
  为了现实需要,我们可以提供Combine的多个特化版本以支持任意数量的接口组合。假如仅仅是为了去掉一个IRWiter就引入一个Combine,虽有好处,但是意义也不大。那么,考虑这样一个例子。

struct IHttpReader : IReader;
struct IFileReader : IReader;
  我们需要一个对象,同时支持从网络和从文件读取的能力。先看不引入Combine的做法:

struct IFileHttpReader : IFileReader , IHttpReader;
typedef Reform<IFileHttpReader, ImpRefCount, ImpHttpReader,
ImpFileReader>::type ConcreteRWiter;
  觉得有什么问题吗?ImpReader同时实现了IFileReader分支和IHttpReader分支中的IReader,但是,和IRefCount不同的是,我们完全有理由相信,这两个分支其实需要不同的IReader的实现。即使IReader确实可以是同样的实现,另一个严重的问题是,ImpReader是一个不完整的实现,ImpFileReader和ImpHttpReader都分别重载了IReader中的一部分方法,例如,两者都实现了如下方法:


virtual bool open(const char* url);
  如何解决这个问题?让我们回顾一下IFileHttpReader,首先这个接口就是个问题产物:

  open到底open什么?文件,还是HTTP连接,还是两个都打开?也就是说,从概念上来讲,IFileHttpReader就存在矛盾,这样的概念很显然是难以维护的。其次,我们完全没有办法为两个分支提供不同的实现,当然,其根源是IFileHttpReader的错误设计导致的,不采用我们这里提到的技术,问题依然存在。现在引入一个结论:假如某个接口的基类树中多次出现同一个接口,我们的技术无法为这些接口分别提供不同的实现。这里的解决方案是抛弃IFileHttpReader,引入Combine, 我们可以这样解决问题:

typedef Reform<
Combine< ImpFileReader <IFileReader>, ImpHttpReader <IHttpReader> >,
ImpRefCount, ImpReader
>::type ConcreteFileHttpReader;
  假设,ImpReader不能同时满足两个分支的要求,我们可以这么做:

typedef Reform <
Combine< ImpFileReader < ImpReaderA<IFileReader> >,
ImpHttpReader < ImpReaderB <IHttpReader> >
>,
ImpRefCount
>::type ConcreteFileHttpReader;
  利用Combine,我们可以充分发挥多重继续的组合能力,我们既享受了接口设计和实现分离的好处—更轻易维护概念了,也充分享有代码复用的能力。并且,将设计决策充分推迟:甚至客户程序员完全可以定制自己的接口实现从而和现有系统结合,这是一个完美的Open-Close的设计手段。

<!--[if !supportLists]--><!--[if !supportLists]--><!--[if !supportLists]--><!--[if !supportLists]--> 现在,总结一下在多重继续中的注重事项。

  1.接口尽量是单继续的。

  2.多重继续的接口必须意识到,所有继续树的相同接口只能共享同一份实现。

  3.严苛地去维护接口的概念,不要为了实现问题定义中间的接口(就象那个IFileHttpReader)

  4.合理地利用多重继续的组合能力。<!--[if !supportEmptyParas]-->

  关于最后一条,您可以做一些有趣的探索。给出一个空基类:

struct Over{};
  当然,也可以是其它非模板类。把所有的类都实现成模版形式:ImpClassA<T>, ImpClassB<T>,借助于Combine,我们可能给出这样的定义:

typedef Combine<ImpClassA< Combine<ImpClassB< Over >, ImpClassC< Over > > >,Combine<ImpClassF<Over>, ImpClassB<ImpClassD< Over > >>,ImpClassE<Over>>::type ConcreteSomeClass;
  我们注重于将这些ImpClasses拆成尽可能小的正交模块。那么借助组合技术,可能获得很高的复用性。但是,有句老话,不要为了复用而复用,反正,这里的探索我也是浅尝辄止,出了什么事情和我无关。非凡提醒一下,上面代码中Combine里面出现了一个type,你可以尝试在上面施加你喜欢的TMP手法嘛。

  把那些有趣的探索先放在一边。现在,我已经把这种技术完整地呈现出来了。然而,没有一项技术是完美的,这里也不例外。这个技术有两个小小的缺陷。

  第一个缺陷则是构造函数的问题。回顾Combine的实现,我们无法为Combine额外提供合适的构造函数。不过,这个问题并不是非凡严重,我们完全可以定制自己的Combine。并且,这些不同的Combine可以混合使用。另外,在组装的时候需要小心的维护构造函数的调用链,这可能伤害到复用性。Assignment中也存在类似的问题。运算符重载也可能导致混乱,不过,我一直认为,在值语义之外的类当中重载运算符可是要非常谨慎的,很显然,我们这里展示的技术并不适合值语义的类型设计。

  另一个缺陷是工程上的。因为上述的实现都是模板类,所以,我们通常需要将实现在头文件里面提供,这个可能是有些人不愿意的。我将展现一种解决的方法,这就是另一个利器:Pimpl惯用法。
以IReader为例,假设如下接口:

struct IReader : IRefCount
{
virtual bool open(const char* url) = 0;
virtual void read(Buffer& buf, size_t size) = 0;
};
  现在,我们只实现read方法:

class ConcreteImpReader;//前置申明
template<typename Base>
class ImpPartialReader : public Base
{
Pimpl<ConcreteImpReader> m_reader;
public:
ImpPartialReader() : m_reader(this), Base(){}
virtual void read(Buffer& buf, size_t size) { m_reader->read(buf, size);}
};
  现在,给出一个原始的Pimpl实现:

template<typename T>
struct Pimpl
{
T* imp;
template<typename Host>
eXPlicit Pimpl(Host* host) : imp(new T(host)){}
T* Operator->() const{return imp;}
~Pimpl(){delete imp;}

};
  在单独的文件中实现:


class ConcreteImpReader
{
ConcreteImpReader(IReader * host) : m_host(host){}
void read(Buffer& buf, size_t size) { …}

};
  ConcreteImpReader中可以引用所在的host对象的其他方法,但是自己实现的方法除外。假如我们愿意,也可以把接口的实现分拆到多个具体的实现类当中,只是我们无法获得象多重继续那样强大的组合能力。

Tags:乌托邦 接口 实现

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接