歡迎來到裝配圖網(wǎng)! | 幫助中心 裝配圖網(wǎng)zhuangpeitu.com!
裝配圖網(wǎng)
ImageVerifierCode 換一換
首頁 裝配圖網(wǎng) > 資源分類 > DOC文檔下載  

PetShop詳解[共56頁]

  • 資源ID:59352714       資源大?。?span id="xdtxqsd" class="font-tahoma">640.73KB        全文頁數(shù):56頁
  • 資源格式: DOC        下載積分:12積分
快捷下載 游客一鍵下載
會員登錄下載
微信登錄下載
三方登錄下載: 微信開放平臺登錄 支付寶登錄   QQ登錄   微博登錄  
二維碼
微信掃一掃登錄
下載資源需要12積分
郵箱/手機:
溫馨提示:
用戶名和密碼都是您填寫的郵箱或者手機號,方便查詢和重復(fù)下載(系統(tǒng)自動生成)
支付方式: 支付寶    微信支付   
驗證碼:   換一換

 
賬號:
密碼:
驗證碼:   換一換
  忘記密碼?
    
友情提示
2、PDF文件下載后,可能會被瀏覽器默認打開,此種情況可以點擊瀏覽器菜單,保存網(wǎng)頁到桌面,就可以正常下載了。
3、本站不支持迅雷下載,請使用電腦自帶的IE瀏覽器,或者360瀏覽器、谷歌瀏覽器下載即可。
4、本站資源下載后的文檔和圖紙-無水印,預(yù)覽文檔經(jīng)過壓縮,下載后原文更清晰。
5、試題試卷類文檔,如果標題沒有明確說明有答案則都視為沒有答案,請知曉。

PetShop詳解[共56頁]

petshop4.0 詳解之一(系統(tǒng)架構(gòu)設(shè)計)前言:PetShop 是一個范例,微軟用它來展示.Net企業(yè)系統(tǒng)開發(fā)的能力。業(yè)界有許多.Net與J2EE之爭,許多數(shù)據(jù)是從微軟的PetShop和Sun的 PetStore而來。這種爭論不可避免帶有濃厚的商業(yè)色彩,對于我們開發(fā)人員而言,沒有必要過多關(guān)注。然而PetShop隨著版本的不斷更新,至現(xiàn)在基于.Net 2.0的PetShop4.0為止,整個設(shè)計逐漸變得成熟而優(yōu)雅,卻又很多可以借鑒之處。PetShop是一個小型的項目,系統(tǒng)架構(gòu)與代碼都比較簡單,卻 也凸現(xiàn)了許多頗有價值的設(shè)計與開發(fā)理念。本系列試圖對PetShop作一個全方位的解剖,依據(jù)的代碼是PetShop4.0,可以從鏈接 一、PetShop的系統(tǒng)架構(gòu)設(shè)計在軟件體系架構(gòu)設(shè)計中,分層式結(jié)構(gòu)是最常見,也是最重要的一種結(jié)構(gòu)。微軟推薦的分層式結(jié)構(gòu)一般分為三層,從下至上分別為:數(shù)據(jù)訪問層、業(yè)務(wù)邏輯層(又或成為領(lǐng)域?qū)樱⒈硎緦?,如圖所示:圖一:三層的分層式結(jié)構(gòu)數(shù)據(jù)訪問層:有時候也稱為是持久層,其功能主要是負責(zé)數(shù)據(jù)庫的訪問。簡單的說法就是實現(xiàn)對數(shù)據(jù)表的Select,Insert,Update, Delete的操作。如果要加入ORM的元素,那么就會包括對象和數(shù)據(jù)表之間的mapping,以及對象實體的持久化。在PetShop的數(shù)據(jù)訪問層中, 并沒有使用ORM,從而導(dǎo)致了代碼量的增加,可以看作是整個設(shè)計實現(xiàn)中的一大敗筆。業(yè)務(wù)邏輯層:是整個系統(tǒng)的核心,它與這個系統(tǒng)的業(yè)務(wù)(領(lǐng)域)有關(guān)。以PetShop為例,業(yè)務(wù)邏輯層的相關(guān)設(shè)計,均和網(wǎng)上寵物店特有的邏輯相關(guān),例如查詢寵物,下訂單,添加寵物到購物車等等。如果涉及到數(shù)據(jù)庫的訪問,則調(diào)用數(shù)據(jù)訪問層。表示層:是系統(tǒng)的UI部分,負責(zé)使用者與整個系統(tǒng)的交互。在這一層中,理想的狀態(tài)是不應(yīng)包括系統(tǒng)的業(yè)務(wù)邏輯。表示層中的邏輯代碼,僅與界面元素有關(guān)。在PetShop中,是利用ASP.Net來設(shè)計的,因此包含了許多Web控件和相關(guān)邏輯。分層式結(jié)構(gòu)究竟其優(yōu)勢何在?Martin Fowler在Patterns of Enterprise Application Architecture一書中給出了答案:1、開發(fā)人員可以只關(guān)注整個結(jié)構(gòu)中的其中某一層;2、可以很容易的用新的實現(xiàn)來替換原有層次的實現(xiàn);3、可以降低層與層之間的依賴;4、有利于標準化;5、利于各層邏輯的復(fù)用。概括來說,分層式設(shè)計可以達至如下目的:分散關(guān)注、松散耦合、邏輯復(fù)用、標準定義。一個好的分層式結(jié)構(gòu),可以使得開發(fā)人員的分工更加明確。一旦定義好各層次之間的接口,負責(zé)不同邏輯設(shè)計的開發(fā)人員就可以分散關(guān)注,齊頭并進。例如 UI人員只需考慮用戶界面的體驗與操作,領(lǐng)域的設(shè)計人員可以僅關(guān)注業(yè)務(wù)邏輯的設(shè)計,而數(shù)據(jù)庫設(shè)計人員也不必為繁瑣的用戶交互而頭疼了。每個開發(fā)人員的任務(wù)得到了確認,開發(fā)進度就可以迅速的提高。松 散耦合的好處是顯而易見的。如果一個系統(tǒng)沒有分層,那么各自的邏輯都緊緊糾纏在一起,彼此間相互依賴,誰都是不可替換的。一旦發(fā)生改變,則牽一發(fā)而動全 身,對項目的影響極為嚴重。降低層與層間的依賴性,既可以良好地保證未來的可擴展,在復(fù)用性上也是優(yōu)勢明顯。每個功能模塊一旦定義好統(tǒng)一的接口,就 可以被各個模塊所調(diào)用,而不用為相同的功能進行重復(fù)地開發(fā)。進行好的分層式結(jié)構(gòu)設(shè)計,標準也是必不可少的。只有在一定程度的標準化基礎(chǔ)上,這個系統(tǒng)才是可擴展的,可替換的。而層與層之間的通信也必然保證了接口的標準化?!敖馃o足赤,人無完人”,分層式結(jié)構(gòu)也不可避免具有一些缺陷:1、降低了系統(tǒng)的性能。這是不言而喻的。如果不采用分層式結(jié)構(gòu),很多業(yè)務(wù)可以直接造訪數(shù)據(jù)庫,以此獲取相應(yīng)的數(shù)據(jù),如今卻必須通過中間層來完成。2、有時會導(dǎo)致級聯(lián)的修改。這種修改尤其體現(xiàn)在自上而下的方向。如果在表示層中需要增加一個功能,為保證其設(shè)計符合分層式結(jié)構(gòu),可能需要在相應(yīng)的業(yè)務(wù)邏輯層和數(shù)據(jù)訪問層中都增加相應(yīng)的代碼。前面提到,PetShop的表示層是用ASP.Net設(shè)計的,也就是說,它應(yīng)是一個BS系統(tǒng)。在.Net中,標準的BS分層式結(jié)構(gòu)如下圖所示:圖二:.Net中標準的BS分層式結(jié)構(gòu)隨著PetShop版本的更新,其分層式結(jié)構(gòu)也在不斷的完善,例如PetShop2.0,就沒有采用標準的三層式結(jié)構(gòu),如圖三:圖三:PetShop 2.0的體系架構(gòu)從 圖中我們可以看到,并沒有明顯的數(shù)據(jù)訪問層設(shè)計。這樣的設(shè)計雖然提高了數(shù)據(jù)訪問的性能,但也同時導(dǎo)致了業(yè)務(wù)邏輯層與數(shù)據(jù)訪問的職責(zé)混亂。一旦要求支持的數(shù) 據(jù)庫發(fā)生變化,或者需要修改數(shù)據(jù)訪問的邏輯,由于沒有清晰的分層,會導(dǎo)致項目作大的修改。而隨著硬件系統(tǒng)性能的提高,以及充分利用緩存、異步處理 等機制,分層式結(jié)構(gòu)所帶來的性能影響幾乎可以忽略不計。PetShop3.0糾正了此前層次不明的問題,將數(shù)據(jù)訪問邏輯作為單獨的一層獨立出來:圖四:PetShop 3.0的體系架構(gòu)PetShop4.0基本上延續(xù)了3.0的結(jié)構(gòu),但在性能上作了一定的改進,引入了緩存和異步處理機制,同時又充分利用了ASP.Net 2.0的新功能MemberShip,因此PetShop4.0的系統(tǒng)架構(gòu)圖如下所示:圖五:PetShop 4.0的體系架構(gòu)比較3.0和4.0的系統(tǒng)架構(gòu)圖,其核心的內(nèi)容并沒有發(fā)生變化。在數(shù)據(jù)訪問層(DAL)中,仍然采用DAL Interface抽象出數(shù)據(jù)訪問邏輯,并以DAL Factory作為數(shù)據(jù)訪問層對象的工廠模塊。對于DAL Interface而言,分別有支持MS-SQL的SQL Server DAL和支持Oracle的Oracle DAL具體實現(xiàn)。而Model模塊則包含了數(shù)據(jù)實體對象。其詳細的模塊結(jié)構(gòu)圖如下所示:圖六:數(shù)據(jù)訪問層的模塊結(jié)構(gòu)圖可以看到,在數(shù)據(jù)訪問層中,完全采用了“面向接口編程”思想。抽象出來的IDAL模塊,脫離了與具體數(shù)據(jù)庫的依賴,從而使得整個數(shù)據(jù)訪問層利于數(shù)據(jù)庫遷移。DALFactory模塊專門管理DAL對象的創(chuàng)建,便于業(yè)務(wù)邏輯層訪問。SQLServerDAL和OracleDAL模塊均實現(xiàn)IDAL模塊 的接口,其中包含的邏輯就是對數(shù)據(jù)庫的Select,Insert,Update和Delete操作。因為數(shù)據(jù)庫類型的不同,對數(shù)據(jù)庫的操作也有所不同,代碼也會因此有所區(qū)別。此外,抽象出來的IDAL模塊,除了解除了向下的依賴之外,對于其上的業(yè)務(wù)邏輯層,同樣僅存在弱依賴關(guān)系,如下圖所示:圖七:業(yè)務(wù)邏輯層的模塊結(jié)構(gòu)圖圖七中BLL是業(yè)務(wù)邏輯層的核心模塊,它包含了整個系統(tǒng)的核心業(yè)務(wù)。在業(yè)務(wù)邏輯層中,不能直接訪問數(shù)據(jù)庫,而必須通過數(shù)據(jù)訪問層。注意圖中對數(shù)據(jù)訪問業(yè)務(wù)的調(diào)用,是通過接口模塊IDAL來完成的。既然與具體的數(shù)據(jù)訪問邏輯無關(guān),則層與層之間的關(guān)系就是松散耦合的。如果此時需要修改數(shù)據(jù)訪問層的具體實現(xiàn),只要不涉及到IDAL的接口定義,那么業(yè)務(wù)邏輯層就不會受到任何影響。畢竟,具體實現(xiàn)的SQLServerDAL和OracalDAL根本就與業(yè)務(wù)邏 輯層沒有半點關(guān)系。因為在PetShop 4.0中引入了異步處理機制。插入訂單的策略可以分為同步和異步,兩者的插入策略明顯不同,但對于調(diào)用者而言,插入訂單的接口是完全一樣的,所以 PetShop 4.0中設(shè)計了IBLLStrategy模塊。雖然在IBLLStrategy模塊中,僅僅是簡單的IOrderStategy,但同時也給出了一個范例和信息,那就是在業(yè)務(wù)邏輯的處理中,如果存在業(yè)務(wù)操作的多樣化,或者是今后可能的變化,均應(yīng)利用抽象的原理?;蛘呤褂媒涌?,或者使用抽象類,從而脫離對具 體業(yè)務(wù)的依賴。不過在PetShop中,由于業(yè)務(wù)邏輯相對簡單,這種思想體現(xiàn)得不夠明顯。也正因為此,PetShop將核心的業(yè)務(wù)邏輯都放到了一個模塊 BLL中,并沒有將具體的實現(xiàn)和抽象嚴格的按照模塊分開。所以表示層和業(yè)務(wù)邏輯層之間的調(diào)用關(guān)系,其耦合度相對較高:圖八:表示層的模塊結(jié)構(gòu)圖在圖五中,各個層次中還引入了輔助的模塊,如數(shù)據(jù)訪問層的Messaging模塊,是為異步插入訂單的功能提供,采用了MSMQ (Microsoft Messaging Queue)技術(shù)。而表示層的CacheDependency則提供緩存功能。這些特殊的模塊,我會在此后的文章中詳細介紹。petshop4.0 詳解之二(數(shù)據(jù)訪問層之數(shù)據(jù)庫訪問設(shè)計)在系列一中,我從整體上分析了PetShop的架構(gòu)設(shè)計,并提及了分層的概念。從本部分開始,我將依次對各層進行代碼級的分析,以求獲得更加細致而 深入的理解。在PetShop 4.0中,由于引入了ASP.Net 2.0的一些新特色,所以數(shù)據(jù)層的內(nèi)容也更加的廣泛和復(fù)雜,包括:數(shù)據(jù)庫訪問、Messaging、MemberShip、Profile四部分。在系列 二中,我將介紹有關(guān)數(shù)據(jù)庫訪問的設(shè)計。在PetShop中,系統(tǒng)需要處理的數(shù)據(jù)庫對象分為兩類:一是數(shù)據(jù)實體,對應(yīng)數(shù)據(jù)庫中相應(yīng)的數(shù)據(jù)表。它們沒有行為,僅用于表現(xiàn)對象的數(shù)據(jù)。這些實體類都被放到Model程序集中,例如數(shù)據(jù)表Order對應(yīng)的實體類OrderInfo,其類圖如下: 這些對象并不具有持久化的功能,簡單地說,它們是作為數(shù)據(jù)的載體,便于業(yè)務(wù)邏輯針對相應(yīng)數(shù)據(jù)表進行讀/寫操作。雖然這些類的屬性分別映射了數(shù)據(jù)表的列,而每一個對象實例也恰恰對應(yīng)于數(shù)據(jù)表的每一行,但這些實體類卻并不具備對應(yīng)的數(shù)據(jù)庫訪問能力。由于數(shù)據(jù)訪問層和業(yè)務(wù)邏輯層都將對這些數(shù)據(jù)實體進行操作,因此程序集Model會被這兩層的模塊所引用。第二類數(shù)據(jù)庫對象則是數(shù)據(jù)的業(yè)務(wù)邏輯對象。這里所指的業(yè)務(wù)邏輯,并非業(yè)務(wù)邏輯層意義上的領(lǐng)域(domain)業(yè)務(wù)邏輯(從這個意義上,我更傾向于將 業(yè)務(wù)邏輯層稱為“領(lǐng)域邏輯層”),一般意義上說,這些業(yè)務(wù)邏輯即為基本的數(shù)據(jù)庫操作,包括Select,Insert,Update和Delete。由于 這些業(yè)務(wù)邏輯對象,僅具有行為而與數(shù)據(jù)無關(guān),因此它們均被抽象為一個單獨的接口模塊IDAL,例如數(shù)據(jù)表Order對應(yīng)的接口IOrder: 將數(shù)據(jù)實體與相關(guān)的數(shù)據(jù)庫操作分離出來,符合面向?qū)ο蟮木?。首先,它體現(xiàn)了“職責(zé)分離”的原則。將數(shù)據(jù)實體與其行為分開,使得兩者之間依賴減弱, 當數(shù)據(jù)行為發(fā)生改變時,并不影響Model模塊中的數(shù)據(jù)實體對象,避免了因一個類職責(zé)過多、過大,從而導(dǎo)致該類的引用者發(fā)生“災(zāi)難性”的影響。其次,它體 現(xiàn)了“抽象”的精神,或者說是“面向接口編程”的最佳體現(xiàn)。抽象的接口模塊IDAL,與具體的數(shù)據(jù)庫訪問實現(xiàn)完全隔離。這種與實現(xiàn)無關(guān)的設(shè)計,保證了系統(tǒng) 的可擴展性,同時也保證了數(shù)據(jù)庫的可移植性。在PetShop中,可以支持SQL Server和Oracle,那么它們具體的實現(xiàn)就分別放在兩個不同的模塊SQLServerDAL、OracleDAL中。以O(shè)rder為例,在SQLServerDAL、OracleDAL兩個模塊中,有不同的實現(xiàn),但它們同時又都實現(xiàn)了IOrder接口,如圖: 從數(shù)據(jù)庫的實現(xiàn)來看,PetShop體現(xiàn)出了沒有ORM框架的臃腫與丑陋。由于要對數(shù)據(jù)表進行Insert和Select操作,以SQL Server為例,就使用了SqlCommand,SqlParameter,SqlDataReader等對象,以完成這些操作。尤其復(fù)雜的是 Parameter的傳遞,在PetShop中,使用了大量的字符串常量來保存參數(shù)的名稱。此外,PetShop還專門為SQL Server和Oracle提供了抽象的Helper類,包裝了一些常用的操作,如ExecuteNonQuery、ExecuteReader等方法。在沒有ORM的情況下,使用Helper類是一個比較好的策略,利用它來完成數(shù)據(jù)庫基本操作的封裝,可以減少很多和數(shù)據(jù)庫操作有關(guān)的代碼,這體現(xiàn)了 對象復(fù)用的原則。PetShop將這些Helper類統(tǒng)一放到DBUtility模塊中,不同數(shù)據(jù)庫的Helper類暴露的方法基本相同,只除了一些特殊 的要求,例如Oracle中處理bool類型的方式就和SQL Server不同,從而專門提供了OraBit和OraBool方法。此外,Helper類中的方法均為static方法,以利于調(diào)用。 OracleHelper的類圖如下: 對于數(shù)據(jù)訪問層來說,最頭疼的是SQL語句的處理。在早期的CS結(jié)構(gòu)中,由于未采用三層式架構(gòu)設(shè)計,數(shù)據(jù)訪問層和業(yè)務(wù)邏輯層是緊密糅合在一起的,因 此,SQL語句遍布與系統(tǒng)的每一個角落。這給程序的維護帶來極大的困難。此外,由于Oracle使用的是PL-SQL,而SQL Server和Sybase等使用的是T-SQL,兩者雖然都遵循了標準SQL的語法,但在很多細節(jié)上仍有區(qū)別,如果將SQL語句大量的使用到程序中,無 疑為可能的數(shù)據(jù)庫移植也帶來了困難。最好的方法是采用存儲過程。這種方法使得程序更加整潔,此外,由于存儲過程可以以數(shù)據(jù)庫腳本的形式存在,也便于移植和修改。但這種方式仍然有缺陷。 一是存儲過程的測試相對困難。雖然有相應(yīng)的調(diào)試工具,但比起對代碼的調(diào)試而言,仍然比較復(fù)雜且不方便。二是對系統(tǒng)的更新帶來障礙。如果數(shù)據(jù)庫訪問是由程序 完成,在.Net平臺下,我們僅需要在修改程序后,將重新編譯的程序集xcopy到部署的服務(wù)器上即可。如果使用了存儲過程,出于安全的考慮,必須有專門 的DBA重新運行存儲過程的腳本,部署的方式受到了限制。我曾經(jīng)在一個項目中,利用一個專門的表來存放SQL語句。如要使用相關(guān)的SQL語句,就利用關(guān)鍵字搜索獲得對應(yīng)語句。這種做法近似于存儲過程的調(diào) 用,但卻避免了部署上的問題。然而這種方式卻在性能上無法得到保證。它僅適合于SQL語句較少的場景。不過,利用良好的設(shè)計,我們可以為各種業(yè)務(wù)提供不同 的表來存放SQL語句。同樣的道理,這些SQL語句也可以存放到XML文件中,更有利于系統(tǒng)的擴展或修改。不過前提是,我們需要為它提供專門的SQL語句 管理工具。SQL語句的使用無法避免,如何更好的應(yīng)用SQL語句也無定論,但有一個原則值得我們遵守,就是“應(yīng)該盡量讓SQL語句盡存在于數(shù)據(jù)訪問層的具體實現(xiàn)中”。當然,如果應(yīng)用ORM,那么一切就變得不同了。因為ORM框架已經(jīng)為數(shù)據(jù)訪問提供了基本的Select,Insert,Update和Delete 操作了。例如在NHibernate中,我們可以直接調(diào)用ISession對象的Save方法,來Insert(或者說是Create)一個數(shù)據(jù)實體對 象:public void Insert(OrderInfo order)    ISession s = Sessions.GetSession();    ITransaction trans = null;    try        trans = s.BeginTransaction();      s.Save( order);      trans.Commit();        finally          s.Close();    沒有SQL語句,也沒有那些煩人的Parameters,甚至不需要專門去考慮事務(wù)。此外,這樣的設(shè)計,也是與數(shù)據(jù)庫無關(guān)的,NHibernate可以通過Dialect(方言)的機制支持不同的數(shù)據(jù)庫。唯一要做的是,我們需要為OrderInfo定義hbm文件。當然,ORM框架并非是萬能的,面對紛繁復(fù)雜的業(yè)務(wù)邏輯,它并不能完全消滅SQL語句,以及替代復(fù)雜的數(shù)據(jù)庫訪問邏輯,但它卻很好的體現(xiàn)了 “80/20(或90/10)法則”(也被稱為“帕累托法則”),也就是說:花比較少(10%-20%)的力氣就可以解決大部分(80%-90%)的問 題,而要解決剩下的少部分問題則需要多得多的努力。至少,那些在數(shù)據(jù)訪問層中占據(jù)了絕大部分的CRUD操作,通過利用ORM框架,我們就僅需要付出極少數(shù) 時間和精力來解決它們了。這無疑縮短了整個項目開發(fā)的周期。還是回到對PetShop的討論上來?,F(xiàn)在我們已經(jīng)有了數(shù)據(jù)實體,數(shù)據(jù)對象的抽象接口和實現(xiàn),可以說有關(guān)數(shù)據(jù)庫訪問的主體就已經(jīng)完成了。留待我們的還有兩個問題需要解決:1、數(shù)據(jù)對象創(chuàng)建的管理2、利于數(shù)據(jù)庫的移植在PetShop中,要創(chuàng)建的數(shù)據(jù)對象包括Order,Product,Category,Inventory,Item。在前面的設(shè)計中,這些對 象已經(jīng)被抽象為對應(yīng)的接口,而其實現(xiàn)則根據(jù)數(shù)據(jù)庫的不同而有所不同。也就是說,創(chuàng)建的對象有多種類別,而每種類別又有不同的實現(xiàn),這是典型的抽象工廠模式 的應(yīng)用場景。而上面所述的兩個問題,也都可以通過抽象工廠模式來解決。標準的抽象工廠模式類圖如下: 例如,創(chuàng)建SQL Server的Order對象如下:PetShopFactory factory = new SQLServerFactory();IOrder = factory.CreateOrder();要考慮到數(shù)據(jù)庫的可移植性,則factory必須作為一個全局變量,并在主程序運行時被實例化。但這樣的設(shè)計雖然已經(jīng)達到了“封裝變化”的目的,但 在創(chuàng)建PetShopFactory對象時,仍不可避免的出現(xiàn)了具體的類SQLServerFactory,也即是說,程序在這個層面上產(chǎn)生了與 SQLServerFactory的強依賴。一旦整個系統(tǒng)要求支持Oracle,那么還需要修改這行代碼為:PetShopFactory factory = new oracleFactory();修改代碼的這種行為顯然是不可接受的。解決的辦法是“依賴注入”。“依賴注入”的功能通常是用專門的IoC容器提供的,在Java平臺下,這樣的容 器包括Spring,PicoContainer等。而在.Net平臺下,最常見的則是Spring.Net。不過,在PetShop系統(tǒng)中,并不需要專 門的容器來實現(xiàn)“依賴注入”,簡單的做法還是利用配置文件和反射功能來實現(xiàn)。也就是說,我們可以在web.config文件中,配置好具體的 Factory對象的完整的類名。然而,當我們利用配置文件和反射功能時,具體工廠的創(chuàng)建就顯得有些“畫蛇添足”了,我們完全可以在配置文件中,直接指向 具體的數(shù)據(jù)庫對象實現(xiàn)類,例如PetShop.SQLServerDAL.IOrder。那么,抽象工廠模式中的相關(guān)工廠就可以簡化為一個工廠類了,所以 我將這種模式稱之為“具有簡單工廠特質(zhì)的抽象工廠模式”,其類圖如下: DataAccess類完全取代了前面創(chuàng)建的工廠類體系,它是一個sealed類,其中創(chuàng)建各種數(shù)據(jù)對象的方法,均為靜態(tài)方法。之所以能用這個類達到抽象工廠的目的,是因為配置文件和反射的運用,如下的代碼片斷所示:public sealed class DataAccess / Look up the DAL implementation we should be using    private static readonly string path = ConfigurationManager.AppSettings”WebDAL”;    private static readonly string orderPath = ConfigurationManager.AppSettings”O(jiān)rdersDAL”; public static PetShop.IDAL.IOrder CreateOrder()          string className = orderPath + “.Order”;         return (PetShop.IDAL.IOrder)Assembly.Load(orderPath).CreateInstance(className);    在PetShop中,這種依賴配置文件和反射創(chuàng)建對象的方式極其常見,包括IBLLStategy、CacheDependencyFactory 等等。這些實現(xiàn)邏輯散布于整個PetShop系統(tǒng)中,在我看來,是可以在此基礎(chǔ)上進行重構(gòu)的。也就是說,我們可以為整個系統(tǒng)提供類似于“Service Locator”的實現(xiàn):public static class ServiceLocator private static readonly string dalPath = ConfigurationManager.AppSettings”WebDAL”;    private static readonly string orderPath = ConfigurationManager.AppSettings”O(jiān)rdersDAL”; / private static readonly string orderStategyPath = ConfigurationManager.AppSettings”O(jiān)rderStrategyAssembly”; public static object LocateDALObject(string className)   string fullPath = dalPath + “.” + className;  return Assembly.Load(dalPath).CreateInstance(fullPath); public static object LocateDALOrderObject(string className)   string fullPath = orderPath + “.” + className;  return Assembly.Load(orderPath).CreateInstance(fullPath); public static object LocateOrderStrategyObject(string className)   string fullPath = orderStategyPath + “.” + className;  return Assembly.Load(orderStategyPath).CreateInstance(fullPath);  /那么和所謂“依賴注入”相關(guān)的代碼都可以利用ServiceLocator來完成。例如類DataAccess就可以簡化為:public sealed class DataAccess public static PetShop.IDAL.IOrder CreateOrder()          return (PetShop.IDAL.IOrder)ServiceLocator. LocateDALOrderObject(”O(jiān)rder”);    通過ServiceLocator,將所有與配置文件相關(guān)的namespace值統(tǒng)一管理起來,這有利于各種動態(tài)創(chuàng)建對象的管理和未來的維護。petshop4.0 詳解之三(PetShop數(shù)據(jù)訪問層之消息處理)PetShop數(shù)據(jù)訪問層之消息處理。MSMQMessaging模塊中,Order對象實現(xiàn)了IMessaging模塊中定義的接口 IOrder,同時它還繼承了基類PetShopQueue,其定義如下:public class order:PetShopQueue, PetShop.IMessaging.IOrder方法的實現(xiàn)代碼如下: public new orderInfo Receive() / This method involves in distributed transaction and need Automatic Transaction type base.transactionType = MessageQueueTransactionType.Automatic; return (OrderInfo)(Message)base.Receive().Body; petshop4.0 詳解之四(PetShop之ASP.NET緩存)如果對微型計算機硬件系統(tǒng)有足夠的了解,那么我們對于Cache這個名詞一定是耳熟能詳?shù)?。在CPU以及主板的芯片中,都引入了這種名為高速緩沖存 儲器(Cache)的技術(shù)。因為Cache的存取速度比內(nèi)存快,因而引入Cache能夠有效的解決CPU與內(nèi)存之間的速度不匹配問題。硬件系統(tǒng)可以利用 Cache存儲CPU訪問概率高的那些數(shù)據(jù),當CPU需要訪問這些數(shù)據(jù)時,可以直接從Cache中讀取,而不必訪問存取速度相對較慢的內(nèi)存,從而提高了 CPU的工作效率。軟件設(shè)計借鑒了硬件設(shè)計中引入緩存的機制以改善整個系統(tǒng)的性能,尤其是對于一個數(shù)據(jù)庫驅(qū)動的Web應(yīng)用程序而言,緩存的利用是不可或缺 的,畢竟,數(shù)據(jù)庫查詢可能是整個Web站點中調(diào)用最頻繁但同時又是執(zhí)行最緩慢的操作之一,我們不能被它老邁的雙腿拖緩我們前進的征程。緩存機制正是解決這 一缺陷的加速器。4.1    ASP.NET緩存概述作為.Net框架下開發(fā)Web應(yīng)用程序的主打產(chǎn)品,ASP.NET充分考慮了緩存機制。通過某種方法,將系統(tǒng)需要的數(shù)據(jù)對象、Web頁面存儲在內(nèi)存 中,使得Web站點在需要獲取這些數(shù)據(jù)時,不需要經(jīng)過繁瑣的數(shù)據(jù)庫連接、查詢和復(fù)雜的邏輯運算,就可以“觸手可及”,如“探囊取物”般容易而快速,從而提 高整個Web系統(tǒng)的性能。ASP.NET提供了兩種基本的緩存機制來提供緩存功能。一種是應(yīng)用程序緩存,它允許開發(fā)者將程序生成的數(shù)據(jù)或報表業(yè)務(wù)對象放入緩存中。另外一種緩存機制是頁輸出緩存,利用它,可以直接獲取存放在緩存中的頁面,而不需要經(jīng)過繁雜的對該頁面的再次處理。應(yīng)用程序緩存其實現(xiàn)原理說來平淡無奇,僅僅是通過ASP.NET管理內(nèi)存中的緩存空間。放入緩存中的應(yīng)用程序數(shù)據(jù)對象,以鍵/值對的方式存儲,這便于用戶在訪問緩存中的數(shù)據(jù)項時,可以根據(jù)key值判斷該項是否存在緩存中。放入在緩存中的數(shù)據(jù)對象其生命周期是受到限制的,即使在整個應(yīng)用程序的生命周期里,也不能保證該數(shù)據(jù)對象一直有效。ASP.NET可以對應(yīng)用程序緩 存進行管理,例如當數(shù)據(jù)項無效、過期或內(nèi)存不足時移除它們。此外,調(diào)用者還可以通過CacheItemRemovedCallback委托,定義回調(diào)方法 使得數(shù)據(jù)項被移除時能夠通知用戶。在.Net Framework中,應(yīng)用程序緩存通過System.Web.Caching.Cache類實現(xiàn)。它是一個密封類,不能被繼承。對于每一個應(yīng)用程序域, 都要創(chuàng)建一個Cache類的實例,其生命周期與應(yīng)用程序域的生命周期保持一致。我們可以利用Add或Insert方法,將數(shù)據(jù)項添加到應(yīng)用程序緩存中,如 下所示:Cache"First" = "First Item"Cache.Insert("Second", "Second Item");我們還可以為應(yīng)用程序緩存添加依賴項,使得依賴項發(fā)生更改時,該數(shù)據(jù)項能夠從緩存中移除:string dependencies = "Second"Cache.Insert("Third", "Third Item",new System.Web.Caching.CacheDependency(null, dependencies);與之對應(yīng)的是緩存中數(shù)據(jù)項的移除。前面提到ASP.NET可以自動管理緩存中項的移除,但我們也可以通過代碼編寫的方式顯式的移除相關(guān)的數(shù)據(jù)項:Cache.Remove("First");相對于應(yīng)用程序緩存而言,頁輸出緩存的應(yīng)用更為廣泛。它可以通過內(nèi)存將處理后的ASP.NET頁面存儲起來,當客戶端再一次訪問該頁面時,可以省去 頁面處理的過程,從而提高頁面訪問的性能,以及Web服務(wù)器的吞吐量。例如,在一個電子商務(wù)網(wǎng)站里,用戶需要經(jīng)常查詢商品信息,這個過程會涉及到數(shù)據(jù)庫訪 問以及搜索條件的匹配,在數(shù)據(jù)量較大的情況下,如此的搜索過程是較為耗時的。此時,利用頁輸出緩存就可以將第一次搜索得到的查詢結(jié)果頁存儲在緩存中。當用 戶第二次查詢時,就可以省去數(shù)據(jù)查詢的過程,減少頁面的響應(yīng)時間。頁輸出緩存分為整頁緩存和部分頁緩存。我們可以通過OutputCache指令完成對Web頁面的輸出緩存。它主要包含兩個參數(shù): Duration和VaryByParam。Duration參數(shù)用于設(shè)置頁面或控件進行緩存的時間,其單位為秒。如下的設(shè)置表示緩存在60秒內(nèi)有效:<% OutputCache Duration=“60“ VaryByParam=“none“ %>只要沒有超過Duration設(shè)置的期限值,當用戶訪問相同的頁面或控件時,就可以直接在緩存中獲取。使用VaryByParam參數(shù)可以根據(jù)設(shè)置的參數(shù)值建立不同的緩存。例如在一個輸出天氣預(yù)報結(jié)果的頁面中,如果需要為一個ID為txtCity的TextBox控件建立緩存,其值將顯示某城市的氣溫,那么我們可以進行如下的設(shè)置:<% OutputCache Duration=”60” VaryByParam=”txtCity” %>如此一來,ASP.NET會對txtCity控件的值進行判斷,只有輸入的值與緩存值相同,才從緩存中取出相應(yīng)的值。這就有效地避免了因為值的不同而導(dǎo)致輸出錯誤的數(shù)據(jù)。利用緩存的機制對性能的提升非常明顯。通過ACT(Application Center Test)的測試,可以發(fā)現(xiàn)設(shè)置緩存后執(zhí)行的性能比未設(shè)置緩存時的性能足足提高三倍多。引入緩存看來是提高性能的“完美”解決方案,然而“金無足赤,人無完人”,緩存機制也有缺點,那就是數(shù)據(jù)過期的問題。一旦應(yīng)用程序數(shù)據(jù)或者頁面結(jié)果 值發(fā)生的改變,那么在緩存有效期范圍內(nèi),你所獲得的結(jié)果將是過期的、不準確的數(shù)據(jù)。我們可以想一想股票系統(tǒng)利用緩存所帶來的災(zāi)難,當你利用錯誤過期的數(shù)據(jù) 去分析股市的風(fēng)云變幻時,你會發(fā)現(xiàn)獲得的結(jié)果真可以說是“失之毫厘,謬以千里”,看似大好的局面就會像美麗的泡沫一樣,用針一戳,轉(zhuǎn)眼就消失得無影無蹤。那么我們是否應(yīng)該為了追求高性能,而不顧所謂“數(shù)據(jù)過期”所帶來的隱患呢?顯然,在類似于股票系統(tǒng)這種數(shù)據(jù)更新頻繁的特定場景下,數(shù)據(jù)過期的糟糕表 現(xiàn)甚至比低效的性能更讓人難以接受。故而,我們需要在性能與數(shù)據(jù)正確性間作出權(quán)衡。所幸的是,.Net Framework 2.0引入了一種新的緩存機制,它為我們的“魚與熊掌兼得”帶來了技術(shù)上的可行性。.Net 2.0引入的自定義緩存依賴項,特別是基于MS-SQL Server的SqlCacheDependency特性,使得我們可以避免“數(shù)據(jù)過期”的問題,它能夠根據(jù)數(shù)據(jù)庫中相應(yīng)數(shù)據(jù)的變化,通知緩存,并移除那 些過期的數(shù)據(jù)。事實上,在PetShop 4.0中,就充分地利用了SqlCacheDependency特性。4.2 SqlCacheDependency特性SqlCacheDependency特性實際上是通過System.Web.Caching.SqlCacheDependency類來體現(xiàn)的。 通過該類,可以在所有支持的SQL Server版本(7.0,2000,2005)上監(jiān)視特定的SQL Server數(shù)據(jù)庫表,并創(chuàng)建依賴于該表以及表中數(shù)據(jù)行的緩存項。當數(shù)據(jù)表或表中特定行的數(shù)據(jù)發(fā)生更改時,具有依賴項的數(shù)據(jù)項就會失效,并自動從 Cache中刪除該項,從而保證了緩存中不再保留過期的數(shù)據(jù)。由于版本的原因,SQL Server 2005完全支持SqlCacheDependency特性,但對于SQL Server 7.0和SQL Server 2000而言,就沒有如此幸運了。畢竟這些產(chǎn)品出現(xiàn)在.Net Framework 2.0之前,因此它并沒有實現(xiàn)自動監(jiān)視數(shù)據(jù)表數(shù)據(jù)變化,通知ASP.NET的功能。解決的辦法就是利用輪詢機制,通過ASP.NET進程內(nèi)的一個線程以指 定的時間間隔輪詢SQL Server數(shù)據(jù)庫,以跟蹤數(shù)據(jù)的變化情況。要使得7.0或者2000版本的SQL Server支持SqlCacheDependency特性,需要對數(shù)據(jù)庫服務(wù)器執(zhí)行相關(guān)的配置步驟。有兩種方法配置SQL Server:使用aspnet_regsql命令行工具,或者使用SqlCacheDependencyAdmin類。4.2.1    利用aspnet_regsql工具aspnet_regsql工具位于WindowsMicrosoft.NETFramework版本文件夾中。如果直接雙擊該工具的執(zhí)行文件,會彈出一個向?qū)υ捒?,提示我們完成相?yīng)的操作:圖4-1 aspnet_regsql工具如圖4-1所示中的提示信息,說明該向?qū)е饕糜谂渲肧QL Server數(shù)據(jù)庫,如membership,profiles等信息,如果要配置SqlCacheDependency,則需要以命令行的方式執(zhí)行。以 PetShop 4.0為例,數(shù)據(jù)庫名為MSPetShop4,則命令為:aspnet_regsql -S localhost -E -d MSPetShop4 -ed以下是該工具的命令參數(shù)說明:-?    顯示該工具的幫助功能;-S    后接的參數(shù)為數(shù)據(jù)庫服務(wù)器的名稱或者IP地址;-U    后接的參數(shù)為數(shù)據(jù)庫的登陸用戶名;-P    后接的參數(shù)為數(shù)據(jù)庫的登陸密碼;-E    當使用windows集成驗證時,使用該功能;-d    后接參數(shù)為對哪一個數(shù)據(jù)庫采用SqlCacheDependency功能;-t    后接參數(shù)為對哪一個表采用SqlCacheDependency功能;-ed    允許對數(shù)據(jù)庫使用SqlCacheDependency功能;-dd    禁止對數(shù)據(jù)庫采用SqlCacheDependency功能;-et    允許對數(shù)據(jù)表采用SqlCacheDependency功能;-dt    禁止對數(shù)據(jù)表采用SqlCacheDependency功能;-lt    列出當前數(shù)據(jù)庫中有哪些表已經(jīng)采用sqlcachedependency功能。以上面的命令為例,說明將對名為MSPetShop4的數(shù)據(jù)庫采用SqlCacheDependency功能,且SQL Server采用了windows集成驗證方式。我們還可以對相關(guān)的數(shù)據(jù)表執(zhí)行aspnet_regsql命令,如:aspnet_regsql -S localhost -E -d MSPetShop4 -t Item -etaspnet_regsql -S localhost -E -d MSPetShop4 -t Product -etaspnet_regsql -S localhost -E -d MSPetShop4 -t Category -et當執(zhí)行上述的四條命令后,aspnet_regsql工具會在MSPetShop4數(shù)據(jù)庫中建立一個名為 AspNet_SqlCacheTablesForChangeNotification的新數(shù)據(jù)庫表。該數(shù)據(jù)表包含三個字段。字段tableName記 錄要追蹤的數(shù)據(jù)表的名稱,例如在PetShop 4.0中,要記錄的數(shù)據(jù)表就包括Category、Item和Product。notificationCreated字段記錄開始追蹤的時間。 changeId作為一個類型為int的字段,用于記錄數(shù)據(jù)表數(shù)據(jù)發(fā)生變化的次數(shù)。如圖4-2所示:圖4-2 AspNet_SqlCacheTablesForChangeNotification數(shù)據(jù)表除此之外,執(zhí)行該命令還會為MSPetShop4數(shù)據(jù)庫添加一組存儲過程,為ASP.NET提供查詢追蹤的數(shù)據(jù)表的情況,同時還將為使用了 SqlCacheDependency的表添加觸發(fā)器,分別對應(yīng)Insert、Update、Delete等與數(shù)據(jù)更改相關(guān)的操作。例如Product數(shù) 據(jù)表的觸發(fā)器:Create TRIGGER dbo.Product_AspNet_SqlCacheNotification_Trigger ON Product      FOR Insert, Update, Delete AS BEGIN      SET NOCOUNT ON      EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'Product'END其中,AspNet_SqlCacheUpdateChangeIdStoredProcedure即是工具添加的一組存儲過程中的一個。當對 Product數(shù)據(jù)表執(zhí)行Insert、Update或Delete等操作時,就會激活觸發(fā)器,然后執(zhí)行 AspNet_SqlCacheUpdateChangeIdStoredProcedure存儲過程。其執(zhí)行的過程就是修改 AspNet_SqlCacheTablesForChangeNotification數(shù)據(jù)表的changeId字段值:Create PROCEDURE dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure               tableName NVARCHAR(450)           AS           BEGIN               Update dbo.AspNet_SqlCacheTablesForChangeNotification WITH (ROWLOCK) SET changeId = changeId + 1               Where tableName = tableName           END  GO4.2.2    利用SqlCacheDependencyAdmin類我們也可以利用編程的方式來來管理數(shù)據(jù)庫對SqlCacheDependency特性的使用。該類包含了五個重要的方法:DisableNotifications為特定數(shù)據(jù)庫禁用 SqlCacheDependency對象更改通知DisableTableForNotifications為數(shù)據(jù)庫中的特定表禁用SqlCacheDependency對象更改通知EnableNotifications為特定數(shù)據(jù)庫啟用SqlCacheDependency對象更改通知EnableTableForNotifications為數(shù)據(jù)庫中的特定表啟用SqlCacheDependency對象更改通知GetTablesEnabledForNotifications返回啟用了SqlCacheDependency對象更改通知的所有表的列表表4-1 SqlCacheDependencyAdmin類的主要方法假設(shè)我們定義了如下的數(shù)據(jù)庫連接字符串:const string connectionStr = "Server=localhost;Database=MSPetShop4"那么為數(shù)據(jù)庫MSPetShop4啟用SqlCacheDependency對象更改通知的實現(xiàn)為:protected void Page_Load(object sender, EventArgs e)      if (!IsPostBack)              SqlCacheDependencyAdmin.EnableNotifications(connectionStr);     為數(shù)據(jù)表Product啟用SqlCacheDependency對象更改通知的實現(xiàn)則為:SqlCacheDependencyAdmin.EnableTableForNotifications(connectionStr, "Product");如果要調(diào)用表4-1中所示的相關(guān)方法,需要注意的是訪問SQL Server數(shù)據(jù)庫的帳戶必須具有創(chuàng)建表和存儲過程的權(quán)限。如果要調(diào)用EnableTableForNotifications方法,還需要具有在該表上創(chuàng)建SQL Server觸發(fā)器的權(quán)限。雖然說編程方式賦予了程序員更大的靈活性,但aspnet_regsql工具卻提供了更簡單的方法實現(xiàn)對SqlCacheDependency的配 置與管理。PetShop 4.0采用的正是aspnet_regsql工具的辦法,它編寫了一個文件名為InstallDatabases.cmd的批處理文件,其中包含了對 aspnet_regsql工具的執(zhí)行,并通過安裝程序去調(diào)用該文件,實現(xiàn)對SQL Server的配置。4.3 在PetShop 4.0中ASP.NET緩存的實現(xiàn)PetShop作為一個B2C的寵物網(wǎng)上商店,需要充分考慮訪客的用戶體驗,如果因為數(shù)據(jù)量大而導(dǎo)致Web服務(wù)器的響應(yīng)不及時,頁面和查詢數(shù)據(jù)遲遲 得不到結(jié)果,會因此而破壞客戶訪問網(wǎng)站的心情,在耗盡耐心的等待后,可能會失去這一部分客戶。無疑,這是非常糟糕的結(jié)果。因而在對其進行體系架構(gòu)設(shè)計時, 整個系統(tǒng)的性能就顯得殊為重要。然而,我們不能因噎廢食,因為專注于性能而忽略數(shù)據(jù)的正確性。在PetShop 3.0版本以及之前的版本,因為ASP.NET緩存的局限性,這一問題并沒有得到很好的解決。PetShop 4.0則引入了SqlCacheDependency特性,使得系統(tǒng)對緩存的處理較之以前大為改觀。4.3.1    CacheDependency接口PetShop 4.0引入了SqlCacheDependency特性,對Category、Product和Item數(shù)據(jù)表對應(yīng)的緩存實施了SQL Cache Invalidation技術(shù)。當對應(yīng)的數(shù)據(jù)表數(shù)據(jù)發(fā)生更改后,該技術(shù)能夠?qū)⑾嚓P(guān)項從緩存中移除。實現(xiàn)這一技術(shù)的核心是 SqlCacheDependency類,它繼承了CacheDependency類。然而為了保證整個架構(gòu)的可擴展性,我們也允許設(shè)計者建立自定義的 CacheDependency類,用以擴展緩存依賴。這就有必要為CacheDependency建立抽象接口,并在web.config文件中進行配 置。在PetShop 4.0的命名空間PetShop.ICacheDependency中,定義了名為IPetShopCacheDependency接口,它僅包含了一個接口方法:public interface IPetShopCacheDependency            AggregateCacheDependency GetDependency();AggregateCacheDependency是.Net Framework 2.0新增的一個類,它負責(zé)監(jiān)視依賴項對象的集合。當這個集合中的任意一個依賴項對象發(fā)生改變時,該依賴項對象對應(yīng)的緩存對象都將被自動移除。AggregateCacheDependency類起到了組合CacheDependency對象的作用,它可以將多個CacheDependency 對象甚至于不同類型的CacheDependency對象與緩存項建立關(guān)聯(lián)。由于PetShop需要為Category、Product和Item數(shù)據(jù)表 建立依賴項,因而IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了這些依賴項的 AggregateCacheDependency對象。4.3.2    CacheDependency實現(xiàn)CacheDependency的實現(xiàn)正是為Category、Product和Item數(shù)據(jù)表建立了對應(yīng)的SqlCacheDependency類型的依賴項,如代碼所示:public abstract class TableDependency : IPetShopCacheDependency      / This is the separator that's used in web.config      protected char configurationSeparator = new char ',' ;      protected AggregateCacheDependency dependency = new AggregateCacheDependency();      protected TableDependency(string configKey)                string dbName = ConfigurationManager.AppSettings"CacheDatabaseName"          string tableConfig = ConfigurationManager.AppSettingsconfigKey;          string tables = tableConfig.Split(configurationSeparator);          foreach (string tableName in tables)              dependency.Add(new SqlCacheDependency(dbName, tableName);            public AggregateCacheDependency GetDependency()               return dependency;      需要建立依賴項的數(shù)據(jù)庫與數(shù)據(jù)表都配置在web.config文件中,其設(shè)置如下:<add key="CacheDatabaseName" value="MSPetShop4"/><add key="CategoryTableDependency" value="Category"/><add key="ProductTableDependency" value="Product,Category"/><add key="ItemTableDependency" value="Product,Category,Item"/>根據(jù)各個數(shù)據(jù)表間的依賴關(guān)系,因而不同的數(shù)據(jù)表需要建立的依賴項也是不相同的,從配置文件中的value值可以看出。然而不管建立依賴項的多寡,其 創(chuàng)建的行為邏輯都是相似的,因而在設(shè)計時,抽象了一個共同的類TableDependency,并通過建立帶參數(shù)的構(gòu)造函數(shù),完成對依賴項的建立。由于接 口方法GetDependency()的實現(xiàn)中,返回的對象dependency是在受保護的構(gòu)造函數(shù)創(chuàng)建的,因此這里的實現(xiàn)方式也可以看作是 Template Method模式的靈活運用。例如TableDependency的子類Product,就是利用父類的構(gòu)造函數(shù)建立了Product、Category 數(shù)據(jù)表的SqlCacheDependency依賴:public class Product : TableDependency      public Product() : base("ProductTableDepende

注意事項

本文(PetShop詳解[共56頁])為本站會員(1528****253)主動上傳,裝配圖網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對上載內(nèi)容本身不做任何修改或編輯。 若此文所含內(nèi)容侵犯了您的版權(quán)或隱私,請立即通知裝配圖網(wǎng)(點擊聯(lián)系客服),我們立即給予刪除!

溫馨提示:如果因為網(wǎng)速或其他原因下載失敗請重新下載,重復(fù)下載不扣分。




關(guān)于我們 - 網(wǎng)站聲明 - 網(wǎng)站地圖 - 資源地圖 - 友情鏈接 - 網(wǎng)站客服 - 聯(lián)系我們

copyright@ 2023-2025  zhuangpeitu.com 裝配圖網(wǎng)版權(quán)所有   聯(lián)系電話:18123376007

備案號:ICP2024067431-1 川公網(wǎng)安備51140202000466號


本站為文檔C2C交易模式,即用戶上傳的文檔直接被用戶下載,本站只是中間服務(wù)平臺,本站所有文檔下載所得的收益歸上傳人(含作者)所有。裝配圖網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對上載內(nèi)容本身不做任何修改或編輯。若文檔所含內(nèi)容侵犯了您的版權(quán)或隱私,請立即通知裝配圖網(wǎng),我們立即給予刪除!