




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
軟件設(shè)計中的5大原則1精選ppt軟件設(shè)計中的5大原則1精選ppt1.單一職責(zé)原則(SRP)陳述:就一個類而言,應(yīng)該只有一個導(dǎo)致其變化的原因分析:一個職責(zé)就是一個變化的軸線一個類如果承擔(dān)的職責(zé)過多,就等于將這些職責(zé)耦合在一起。一個職責(zé)的變化可能會虛弱或者抑止這個類完成其它職責(zé)的能力多職責(zé)將導(dǎo)致脆弱性的臭味2精選ppt1.單一職責(zé)原則(SRP)陳述:2精選pptRectangle+draw()+area():doubleComputationalGeometryApplicationGraphicalApplicationGUIRectangle類具有兩個職責(zé):計算矩形面積的數(shù)學(xué)模型將矩形在一個圖形設(shè)備上描述出來示例1:3精選pptRectangle+draw()ComputationaRectangle類違反了SRP,具有兩個職能——計算面積和繪制矩形這種對SRP的違反將導(dǎo)致兩個方面的問題:包含不必要的代碼一個應(yīng)用可能希望使用Retangle類計算矩形的面積,但是卻被迫將繪制矩形相關(guān)的代碼也包含進(jìn)來一些邏輯上毫無關(guān)聯(lián)的原因可能導(dǎo)致應(yīng)用失敗如果GraphicalApplication的需求發(fā)生了變化,從而對Rectangle類進(jìn)行了修改。但是這樣的變化居然會要求我們重新構(gòu)建、測試以及部署ComputationalGeometryApplication,否則其將莫名其妙的失敗。4精選pptRectangle類違反了SRP,具有兩個職能——計算面積和修改后的設(shè)計如下:5精選ppt修改后的設(shè)計如下:5精選pptModem類(可能)有兩個職責(zé):撥號通信示例2:一個Modem的接口:ClassModem{ public: virtualvoiddail(char*pno)=0; virtualvoidhangup()=0; virtualvoidsend(charc)=0; virtualvoidrecv()=0;};6精選pptModem類(可能)有兩個職責(zé):示例2:6精選ppt什么是職責(zé)?
職責(zé)是“變化的原因”。上面的例子可能存在兩種變化的方式:連接和通信可能獨立變化 在這種情況下,應(yīng)該將職責(zé)分開。例如,應(yīng)用的變化導(dǎo)致了連接部分方法的簽名(signature)發(fā)生了變化,那么使用數(shù)據(jù)連接的部分也需要重新編譯、部署,這會相當(dāng)麻煩,使得設(shè)計僵化。連接和通信同時變化 這種情況下,不必將職責(zé)分開。反而分離可能導(dǎo)致“不必要的復(fù)雜性”的臭味刻舟求劍是錯誤的。
——王亞沙7精選ppt什么是職責(zé)?刻舟求劍是錯誤的。7精選ppt修改后的設(shè)計如下: 有一點需要注意:在ModemImplementation中實際還是集合了兩個職責(zé)。這是我們不希望的,但是有時候卻是必須的。 但是我們注意到,對于應(yīng)用的其它部分,通過接口的分離我們已經(jīng)實現(xiàn)了職責(zé)的分離。 ModemImplementation已經(jīng)不被其它任何程序所依賴。除了main以外,其他所有程序都不需要知道這個函數(shù)的存在。
8精選ppt修改后的設(shè)計如下: 有一點需要注意:在ModemImplem常見錯誤提醒:持久化與業(yè)務(wù)規(guī)則的耦合。例如: 業(yè)務(wù)規(guī)則經(jīng)常變化,而持久化方法卻一般不變。將這兩個職責(zé)耦合在一起,將導(dǎo)致每次因為業(yè)務(wù)規(guī)則變化調(diào)整Employee類時,所有持久化部分的代碼也要跟著變化
9精選ppt常見錯誤提醒: 業(yè)務(wù)規(guī)則經(jīng)常變化,而持久化方法卻一般不變。將2.開放封閉原則(OCP)陳述:軟件實體(類、模塊、函數(shù)等)應(yīng)該是可以擴展的,同時還可以是不必修改的,更確切的說,函數(shù)實體應(yīng)該: (1)對擴展是開放的 當(dāng)應(yīng)用的需求變化時,我們可以對模塊進(jìn)行擴展,使其具有滿足改變的新的行為——即,我們可以改變模塊的功能 (2)對更改是封閉的 對模塊進(jìn)行擴展時,不必改動已有的源代碼或二進(jìn)制代碼。分析:世界是變化的(而且變化很快),軟件是對現(xiàn)實的抽象 軟件必須能夠擴展如果任何修改都需要改變已經(jīng)存在的代碼,那么可能導(dǎo)致牽一發(fā)動全身現(xiàn)象,進(jìn)而導(dǎo)致雪崩效應(yīng),使軟件質(zhì)量顯著下降10精選ppt2.開放封閉原則(OCP)陳述:10精選ppt實現(xiàn)OCP的關(guān)鍵是抽象:例子1classclient{ server&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};classserver{ intserverData;public: voidServerFunc();};11精選ppt實現(xiàn)OCP的關(guān)鍵是抽象:classclient{class例子1(續(xù)) 這個程序出了什么問題?
client和server都是具體類,接口與實現(xiàn)沒有實現(xiàn)分離。如果我們想要讓client調(diào)用一個新的server類,那么我們不得不修改client的源代碼
從而帶來編譯、鏈接、部署等一系列的問題。見下頁程序12精選ppt例子1(續(xù)) client和server都是具體類,接口與實2.開放封閉原則(OCP)例子1(續(xù)) classclient{
server&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};classserver{ intserverData;public: voidServerFunc();};classserver1{ intserverData;public: voidServerFunc();};classclient{ server1&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};13精選ppt2.開放封閉原則(OCP)例子1(續(xù)) classcli2.開放封閉原則(OCP)例子1(續(xù)) 修改后的設(shè)計classclient{ ClientInterface&ci;public: client(ClientInterface&CI):ci(CI){} voiduseServer(){ ci.ServerFunc(); }};classClientInterface{ virtualvoidServerFunc()=0;};classserver:publicClientInterface{ intserverData;public: voidServerFunc();};14精選ppt2.開放封閉原則(OCP)例子1(續(xù))classclie例子1(續(xù))一個問題: 為什么上述的ClientInterface這個類要取這么個名字,叫做AbastractServer豈不更好?? 其實這里面蘊含了一個思想:
——client類中更多的描述了高層的策略,而Server類中是對這些策略的一種具體實現(xiàn)。
而接口是策略的一個組成部分,他根client端的關(guān)系更加密切
我們應(yīng)該這樣想問題:ClientInterface中定義了client期 望Server做什么,而server具體類是對client這種要求的 一種具體實現(xiàn)。
OCP原則其實是要求我們清晰的區(qū)分策略和策略的具體實現(xiàn)形式。允許 擴展具體的實現(xiàn)形式(開放),同時將這種擴展與策略隔離開來,使 其對上層的策略透明(封閉)。
15精選ppt例子1(續(xù))15精選ppt例子2 C語言程序 ---------shape.h-----------------emumShapeType{circle,square};structShape{ ShapeTypeitsType;};---------circle.h-----------------structCircle{ ShapeTypeitsType; doubleitsRadius; CPointitscenter;};---------square.h-----------------structSquare{ ShapeTypeitsType; doubleitsSide; CPointitsTopLeft;};---------drawAllShapes.cpp----------typedefstructShape*ShapePointer;voidDrawAllShapes(ShapePointerlist[],intn){ inti; for(i=0;i<n;i++){ structShape*s=list[i]; switch(s->itsType){ casesquare: s->Square(); break; casecircle: s->DrawCircle(); break; }}}16精選ppt例子2 C語言程序 ---------shape.h-例子2(續(xù))批判 這個程序不符合OCP,如果需要處理的幾何圖形中再加入“三角形”將引發(fā)大量的修改僵化的 增加Triangle會導(dǎo)致Shape、Square、Circle以及DrawAllShapes的重新編譯和部署脆弱的 因為存在大量的既難以查找又難以理解的Switch和If語句,修改稍有不慎,程序就會莫明其妙的出錯牢固的 想在一個程序中復(fù)用DrawAllShapes,都必須帶上Circle、Square,即使那個程序不需要他們17精選ppt例子2(續(xù))17精選ppt例子2(續(xù))修改后的設(shè)計 classShape{public: virtualvoidDraw()const=0;};classSquare:publicShape{public: virtualvoidDraw()const;};classCircle:publicShape{public: virtualvoidDraw()const;};voidDrawAllShapes(Vector<Shape*>&list){vector<Shape*>::iteratori;for(i=list.begin();i!=list.end();i++) (*i)->Draw();}18精選ppt例子2(續(xù))classShape{voidDrawAll例子2(續(xù))再看這些批判 再加入“三角形”將變得十分簡單:僵化的 增加Triangle會導(dǎo)致Shape、Square、Circle以及DrawAllShapes的重新編譯和部署脆弱的 因為存在大量的既難以查找又難以理解的Switch和If語句,修改稍有不慎,程序就會莫明其妙的出錯牢固的 想在一個程序中復(fù)用DrawAllShapes,都必須帶上Circle、Square,即使那個程序不需要他們19精選ppt例子2(續(xù))19精選ppt謊言:上述代碼并不完全封閉——“如果我們希望正方形在所有圓之前繪制”會怎么樣?
——對繪圖的順序無法實現(xiàn)封閉
更糟糕的是,剛才的設(shè)計反而成為了實現(xiàn)“正方形在所有圓之前繪制”功能的障礙。20精選ppt謊言:20精選ppt真實的謊言:一般而言,無論模塊多么“封閉”,都會存在一些無法對之封閉的變化
沒有對所有變化的情況都封閉的模型我們怎么辦?既然不可能完全封閉,我們必須有策略的對待此問題——對模型應(yīng)該封閉那類變化作出選擇,封閉最可能出現(xiàn)的變化
這需要對領(lǐng)域的了解,豐富的經(jīng)驗和常識
錯誤的判斷反而不美,因為OCP需要額外的開銷(增加復(fù)雜度)
敏捷的思想——我們預(yù)測他們,但是直到我們發(fā)現(xiàn)他們才行動21精選ppt真實的謊言:21精選ppt回到例2:要實現(xiàn)對排序的封閉應(yīng)該如何設(shè)計?22精選ppt回到例2:22精選pptclassShape{ public: virtualvoidDraw()const=0; virtualboolPrecedes(constShape&)const=0; booloperator<constShape&s){returnPrecedes(s);}};template<typenameP>classLessp{ public:booloperator(){constPp,constPq){return(*p)<(*q);}}voidDrawAllShapes(vector<Shape*>&list){ vector<Shape*>orderedList=list; sort(orderedList.begin(),orderedList.end(),Lessp<Shape*>()); vector<Shape*>::const_iteratorI; for(i=orderedList.begin();i!=orderedList.end();i++) (*i)->Draw();}23精選pptclassShape{23精選ppt對于各個Shape的派生類,需要實現(xiàn)具體的排序規(guī)則Circle類的排序規(guī)則實現(xiàn)如下:BoolCircle::Precedes(constShape&s)const{ if(dynamic_cast<Square*>(s)) returntrue; else returnfalse;}這個程序符合OCP嗎??24精選ppt對于各個Shape的派生類,需要實現(xiàn)具體的排序規(guī)則24精選p利用“數(shù)據(jù)驅(qū)動”的方法獲得封閉性#include<typeinfo>#include<string>#include<iostream>usingnamespacestd;classShape{public: virtualvoidDraw()const=0; virtualboolPrecedes(constShape&)const; booloperator<(constShape&s)const{ returnPrecedes(s); }private: staticchar*typeOrderTable[];};char*Shape::typeOrderTable[]={ typeid(Circle).name(), typeid(Square).name(), 0};boolShape::Precedes(constShape&s)const{ constchar*thisType=typeid(*this).name(); constchar*argType=typeid(s).name(); booldone=false; intthisOrd=-1; intargOrd=-1; for(inti=0;!done;i++){ constchar*tableEntry=typeOrderTable[i]; if(tableEntry!=0){ if(strcmp(tableEntry,thisType)==0) thisOrd=i; if(strcmp(tableEntry,argType)==0) argOrd=i; if((argOrd>0)&&(thisOrd>0)) done=true; } else//tableentry==0 done=true; } returnthisOrd<argOrd;}25精選ppt利用“數(shù)據(jù)驅(qū)動”的方法獲得封閉性#include<type通過上述方法,我們成功地做到了一般情況下DrawAllShapes函數(shù)對于順序問題的封閉,也使得每個Shape派生類對于新的Shape派生類的創(chuàng)建者或者給予類型的Shape對象排序規(guī)則的改變是封閉的。對于不同的Shapes的繪制順序的變化不封閉的唯一部分就是表本身??梢园驯矸胖迷谝粋€單獨的模塊中,和所有其他模塊隔離,因此對于表的改動不會影響其他任何模塊。事實上在C++中我們可以在鏈接時選擇要使用的表。26精選ppt通過上述方法,我們成功地做到了一般情況下DrawAllSha3.LisKov替換原則(LSP)陳述:子類型(Subtype)必須能夠替換他們的基類型(Basetype) BarbaraLiskov對原則的陳述: 若對每個類型S的對象o1,都存在一個類型T的對象o2,使得在所有針對T編寫的程序P中,用o1替換o2后,程序P的行為功能不變,則S是T的子類型。分析:違法這個職責(zé)將導(dǎo)致程序的脆弱性和對OCP的違反 例如:基類Base,派生類Derived,派生類實例d,函數(shù)f(Base*p);f(&d) 會導(dǎo)致錯誤 顯然D對于f是脆弱的。如果我們試圖編寫一些測試,以保證把d傳給f時可以使f具有正確的行為。那么這個測試違反了OCP——因為f無法對Base的所有派生類都是封閉的27精選ppt3.LisKov替換原則(LSP)陳述:27精選ppt示例1:structPoint{doublex,y;};structshape{ enumShapeType{square,circle}itsType; shape(ShapeTypet):itsType(t){}};structCircle:publicShape{ Circle():Shape(circle){}; voidDraw()const; PointitsCenter; doubleitsRadius;};structSquare:publicShape{ Square():Shape(square){}; voidDraw()const; PointitsTopLeft; doubleitsSide;};voidDrawShape(constShape&s){ if(s.itsType==Shape::square) static_cast<constSquare&>(s).Draw(); elseif(s.itsType==Shape::circle) static_cast<constCircle&>(s).Draw();} 顯然,DrawShape違反了OCP, 為什么?
因為Circle和Square違反了LSP28精選ppt示例1:structPoint{doublex,y;};示例2:一次更加奇妙的違規(guī)classRectangle{ PointtopLeft; doulbewidth; doubleheight;public: voidsetWidth(doublew){width=w;} voidsetHeight(doubleh){height=h;} doublegetWidth()const{returnwidth;} doublegetHeight()const{returnheight;}};classSquare:publicRectangle{public: voidsetWidth(doublew); voidsetHeight(doubleh);};voidSquare::setWidth(doublew){ Rectangle::setWidth(w); Rectangle::setHeight(w);};voidSquare::setHeight(doubleh){ Rectangle::setWidth(h); Rectangle::setHeight(h);};29精選ppt示例2:一次更加奇妙的違規(guī)classRectangle{v問題的第一步分析:看下面這個函數(shù)voidf(Rectangle&r){ r.SetWidth(32);}問題:顯然,當(dāng)我們將一個Square的實例傳給f時,將可能導(dǎo)致其height與width不等,破壞了其完整性——違反了LSP要改正上述問題,很簡單,我們只要將SetWidth和SetHeight兩個函數(shù)設(shè)置成virtual函數(shù)即可——添加派生類需要修改基類,通常意味著設(shè)計上的缺陷但是并非所有人都同意上述的分析反方:真正的設(shè)計缺陷是忘記把SetWidth和SetHeight兩個函數(shù)作為virtual函數(shù)正方:設(shè)置長方形的長寬是非?;镜牟僮?,不是預(yù)見到有正方形這樣的派生類,怎么會想到要將其設(shè)成虛函數(shù)?30精選ppt問題的第一步分析:voidf(Rectangle&r){放下這個爭論,我們先將SetWidth和SetHeight改作虛函數(shù)看看classRectangle{ PointtopLeft; doulbewidth; doubleheight;public:
virtualvoidsetWidth(doublew){width=w;}
virtualvoidsetHeight(doubleh){height=h;} doublegetWidth()const{returnwidth;} doublegetHeight()const{returnheight;}};classSquare:publicRectangle{public: voidsetWidth(doublew); voidsetHeight(doubleh);};voidSquare::setWidth(doublew){ Rectangle::setWidth(w); Rectangle::setHeight(w);};voidSquare::setHeight(doubleh){ Rectangle::setWidth(h); Rectangle::setHeight(h);};看起來,很不錯!31精選ppt放下這個爭論,我們先將SetWidth和SetHeight改真正的問題:voidg(Rectangle&r){ r.setWidth(5); r.setHeight(4); assert(r.Area()==20);}函數(shù)g不能操作Square的實例,Square不能替換Rectangle,所以違反了LSPLSP告訴我們:孤立的看,我們無法判斷模型的有效性——考慮一個設(shè)計是否恰當(dāng)時,不能孤立的看待并判斷,應(yīng)該從此設(shè)計的使用者所作出的假設(shè)來審視它!事先的推測是困難的,我們采用敏捷的思想推遲這個判斷——“一個模型是否違反LSP”。直到出現(xiàn)問題的時候我們才解決它。32精選ppt真正的問題:voidg(Rectangle&r){函數(shù)g更加深入的思索:這個看似明顯正確的模型怎么會出錯呢?“正方形是一種長方形”——地球人都知道錯在哪里? 對不是g函數(shù)的編寫者而言,正方形可以是長方形,但是對g函數(shù)的編寫者而言,Square絕對不是Rectangle??!
OOD中對象之間是否存在IS-A關(guān)系,應(yīng)該從行為的角度來看待。
而行為可以依賴客戶程序做出合理的假設(shè)。33精選ppt更加深入的思索:這個看似明顯正確的模型怎么會出錯呢? 對不是基于契約(和約)的設(shè)計——DBC(DeignbyContract)“合理的假設(shè)”使人郁悶。
——我怎么知道是否合理呢??使用DBC,類的編寫者需要顯示的規(guī)定針對該類的契約??蛻舸a的編寫者可以通過契約獲悉行為的依賴方式。契約通過為每一個方法規(guī)定前置條件(preconditions)和后置條件(postconditions)來指定的。要使一個方法執(zhí)行,前置條件一定要為真(對客戶的要求);函數(shù)執(zhí)行后要保證后置條件為真(對函數(shù)編寫者的要求)。34精選ppt基于契約(和約)的設(shè)計——DBC(DeignbyCont基于契約(和約)的設(shè)計——DBC(DeignbyContract)(續(xù))例如: 在上面的例子中,Rectangle::SetWidth(doublew)的后置條件可以看作是:
assert((itsWidth==w)&&(itsHeight==old.itsHeight));基類和派生類在前置條件和后置條件上的關(guān)系是:
如果在派生類中重新申明了基類中已有的成員函數(shù),這個函數(shù)只能使用相等或更弱的前置條件來替換原有的前置條件;并且,只能使用相等或更強的后置條件來替換原有的后置條件。
派生類必須接受基類已經(jīng)接受的一切;并且,派生類不能違反基類已經(jīng)確定的規(guī)則。在一些語言中明確的支持契約,例如Eiffel,你申明它們,運行時系統(tǒng)會自動的檢查。在Jave和C++標(biāo)準(zhǔn)中尚未支持,我們必須自己考慮。35精選ppt基于契約(和約)的設(shè)計——DBC(DeignbyCont4.依賴倒置原則(DIP)陳述:高層模塊不應(yīng)該依賴于低層模塊。二者應(yīng)該依賴于抽象。抽象不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象。分析:所謂“倒置”是相對于傳統(tǒng)的開發(fā)方法(例如結(jié)構(gòu)化方法)中總是傾向于讓高層模塊依賴于低層模塊而言的軟件結(jié)構(gòu)而言的。高層包含應(yīng)用程序的策略和業(yè)務(wù)模型,而低層包含更多的實現(xiàn)細(xì)節(jié),平臺相關(guān)細(xì)節(jié)等。高層依賴低層將導(dǎo)致:難以復(fù)用。通常改變一個軟硬件平臺將導(dǎo)致一些具體的實現(xiàn)發(fā)生變化,如果高層依賴低層,這種變化將導(dǎo)致逐層的更改。難以維護(hù)。低層通常是易變的。 36精選ppt4.依賴倒置原則(DIP)陳述:36精選ppt層次化:“……所有良構(gòu)的OO體系結(jié)構(gòu)都具有清晰的層次定義,每個層次通過一個定義良好的、受控的接口向外提供了一組內(nèi)聚的服務(wù)?!?/p>
——Booch對上述論述可能存在兩種不同的理解:簡單的理解u1()u2()g(){u1();u2();}p(){g();}37精選ppt層次化:u1()g(){u1();u2();}p(層次化(續(xù)):更好的理解依賴關(guān)系倒置 下層的實現(xiàn),依賴于上層的接口接口所有權(quán)倒置 客戶擁有接口,而服務(wù)者則從這些接口派生38精選ppt層次化(續(xù)):依賴關(guān)系倒置38精選ppt依賴不倒置的開發(fā)自頂向下首先設(shè)計整個軟件的分解結(jié)構(gòu)然后首先實現(xiàn)下層的功能再實現(xiàn)上層的功能,并使上層調(diào)用下層函數(shù)依賴倒置的開發(fā)首先設(shè)計上層需要調(diào)用的接口,并實現(xiàn)上層然后低層類從上層接口派生,實現(xiàn)低層接口屬于上層39精選ppt依賴不倒置的開發(fā)39精選ppt示例1(Button與Lamp):Button(開關(guān))感知外界的變化。 當(dāng)接受到Poll(輪詢)消息時,判斷其是否被“按下”。這個按下是抽象的(不關(guān)心通過什么樣的機制去感知):可能是GUI上的一個按鈕被鼠標(biāo)單擊可能是一個真正的按鈕被手指按下可能是一個防盜裝置檢測到了運動……Lamp(燈)根據(jù)指示,收到turnon消息顯示某種燈光,收到turnoff消息關(guān)閉燈光可能是計算機控制臺的LED可能是停車場的日光燈可能是激光打印機中的激光……應(yīng)該如何設(shè)計程序來用Button控制Lamp呢?
40精選ppt示例1(Button與Lamp):40精選ppt一個不成熟的設(shè)計
Button對象直接依賴Lamp對象,從而:Lamp的任何變化都會影響到Button,導(dǎo)致其改寫或者重新編譯黑盒方式重用Button來控制一個Motor類變得不可能classButton{ Lamp*itsLamp;public: voidpoll(){ if(/*somecondition*/) itsLamp->turnOn(); .... }};41精選ppt一個不成熟的設(shè)計classButton{41精選ppt一個依賴倒置的設(shè)計 依賴于抽象什么是高層策略?就是應(yīng)用背后的抽象 背后的抽象是檢測用戶的開/關(guān)指令用什么機制檢測用戶的指令?無關(guān)緊要目標(biāo)對象是什么?無關(guān)緊要他們不會影響到抽象的具體細(xì)節(jié)改進(jìn)后的設(shè)計:
Button依賴于抽象的接口ButtonServer(向該接口發(fā)消息)。ButtonServer提供一些抽象的方法,Button類通過這些接口可以開啟或關(guān)掉一些東西。Lamp也依賴于ButtonServer接口(從此接口派生),提供具體的實現(xiàn)。42精選ppt一個依賴倒置的設(shè)計Button依賴于抽象的接口ButtonS部分代碼://Button.h#include"ButtonServer.h"classButton{ ButtonServer*bs;public: voidpoll();};//Button.cppvoidButton::poll(){ if(/*mechanismfordetectingturnOncommand*/) bs->turnOn(); elseif((/*mechanismfordetectingturnOffcommand*/) bs->turnOff();}//ButtonServer.hclassButtonServer{public: virtualvoidturnOn()=0; virtualvoidturnOff()=0;};//lamp.hclassLamp:publicButtonServer{public: voidturnOn(); voidturnOff();};//lamp.cppvoidLamp::turnOn(){ /*codesforturnonaspecificdevice*/}voidLamp::turnOff(){ /*codesforturnoffaspecificdevice*/}43精選ppt部分代碼://Button.h//ButtonServer.分析:上述設(shè)計使得Button可以控制所有愿意實現(xiàn)ButtonServer接口的設(shè)備,甚至是一個尚未開發(fā)出來的設(shè)備。質(zhì)疑:這樣的設(shè)計是不是強加了這樣一個約束——所有需要被Button控制的對象一定要實現(xiàn)ButtonServer類。如果我的設(shè)備還希望能夠被另一個對象控制,比如Switch控制,怎么辦?這種設(shè)計是不是將Button對Lamp的依賴轉(zhuǎn)嫁成了Lamp對Button的依賴呢?(畢竟Lamp只能被一種Button控制也是不好的)抗辯:上述質(zhì)疑不成立。Button依賴于ButtonServer接口,但是接口并不依賴于Button,也就是說任何知道如何操作ButtonServer接口的對象都可以操作Lamp。也許需要改進(jìn)的僅僅是ButtonServer這樣一個有些“誤導(dǎo)性”的名字,我們可以將這個名字該得更加抽象一些,例如:SwitchableDevice44精選ppt分析:質(zhì)疑:抗辯:44精選ppt5.接口隔離原則(ISP)陳述:不應(yīng)該強迫客戶依賴于他們不用的方法一個類的不內(nèi)聚的“胖接口”應(yīng)該被分解成多組方法,每一組方法都服務(wù)于一組不同的客戶程序。45精選ppt5.接口隔離原則(ISP)陳述:45精選ppt先說一個例子:classDoor{ public: virtualvoidLock()=0;
virtualvoidUnlock()=0; virtualboolIsDoorOpen()=0;};Door可以加鎖、解鎖、而且可以感知自己是開還是關(guān);Door是抽象基類,客戶程序可以依賴于抽象而不是具體的實現(xiàn)增加功能 如果門打開時間過長,它就會報警。(比如賓館客房的門)46精選ppt先說一個例子:classDoor{Door可以加鎖、解鎖 為了實現(xiàn)上述新增功能,我們要求Door與一個已有的Timer對象進(jìn)行交互classTimer{public: voidRegister(inttimeout,TimerClient*client);};classTimerClient{public: virtualvoidTimerOut();};如果一個對象希望得到超時通知,它可以調(diào)用Timer的Register函數(shù)。該函數(shù)有兩個參數(shù),一個是超時時間,另一個是指向TimerClient對象的指針,此對象的TimerOut函數(shù)會在超時時被調(diào)用 我們?nèi)绾螌imerClient和TimedDoor聯(lián)系起來?問題47精選ppt 為了實現(xiàn)上述新增功能,我們要求Door與一個已有的Time一種常見的解決方案如下:問題——接口污染在Door接口中加入新的方法(Timeout),而這個方法僅僅只為它的一個子類帶來好處?!绻看巫宇愋枰粋€新方法時它都被加到基類接口中,基類接口將很快變胖。 胖接口將導(dǎo)致SRP,LSP被違反,從而導(dǎo)致脆弱、僵化48精選ppt一種常見的解決方案如下:問題——接口污染 胖接口將導(dǎo)致SRP客戶的反作用力:通常接口的變化將導(dǎo)致client的改變但是很多時候,接口之所以變化是因為客戶需要他們變化Client對interface具有反作用力!classTimer{public: voidRegister(inttimeout,inttimeOutId,TimerClient*client);};classTimerClient{public: virtualvoidTimerOut(inttimeOutId);};
TimedDoor的多個超時請求問題,導(dǎo)致Timer接口做出下面的調(diào)整:TimedDoor對Timer接口的影響會傳遞到Door接口,從而導(dǎo)致所有Door都受到此影響,而且這一影響還會影響到Door的所有Clients——牽一發(fā)動全身49精選ppt客戶的反作用力:classTimer{ TimedDoor解決之道:使用委托ClassTimedDoor:publicDoor{public: virtualvoidDoorTimeOut(inttimeOutID);};classDoorTimeAdapter:publicTimerClient{ TimedDoor&itsTimedDoor;public: DoorTimerAdapter(TimedDoor&theDoor):itsTimedDoor(theDoor){} vitualvoidTimeOut(inttimeOutId){ itsTimedDoor.DoorTimeOut(timeOutId); }};50精選ppt解決之道:ClassTimedDoor:publicDo解決之道(續(xù)):使用多繼承ClassTimedDoor:publicDoor,publicTimerClient{public: virtualvoidDoorTimeOut(inttimeOutID);};51精選ppt解決之道(續(xù)):ClassTimedDoor:public軟件設(shè)計中的5大原則52精選ppt軟件設(shè)計中的5大原則1精選ppt1.單一職責(zé)原則(SRP)陳述:就一個類而言,應(yīng)該只有一個導(dǎo)致其變化的原因分析:一個職責(zé)就是一個變化的軸線一個類如果承擔(dān)的職責(zé)過多,就等于將這些職責(zé)耦合在一起。一個職責(zé)的變化可能會虛弱或者抑止這個類完成其它職責(zé)的能力多職責(zé)將導(dǎo)致脆弱性的臭味53精選ppt1.單一職責(zé)原則(SRP)陳述:2精選pptRectangle+draw()+area():doubleComputationalGeometryApplicationGraphicalApplicationGUIRectangle類具有兩個職責(zé):計算矩形面積的數(shù)學(xué)模型將矩形在一個圖形設(shè)備上描述出來示例1:54精選pptRectangle+draw()ComputationaRectangle類違反了SRP,具有兩個職能——計算面積和繪制矩形這種對SRP的違反將導(dǎo)致兩個方面的問題:包含不必要的代碼一個應(yīng)用可能希望使用Retangle類計算矩形的面積,但是卻被迫將繪制矩形相關(guān)的代碼也包含進(jìn)來一些邏輯上毫無關(guān)聯(lián)的原因可能導(dǎo)致應(yīng)用失敗如果GraphicalApplication的需求發(fā)生了變化,從而對Rectangle類進(jìn)行了修改。但是這樣的變化居然會要求我們重新構(gòu)建、測試以及部署ComputationalGeometryApplication,否則其將莫名其妙的失敗。55精選pptRectangle類違反了SRP,具有兩個職能——計算面積和修改后的設(shè)計如下:56精選ppt修改后的設(shè)計如下:5精選pptModem類(可能)有兩個職責(zé):撥號通信示例2:一個Modem的接口:ClassModem{ public: virtualvoiddail(char*pno)=0; virtualvoidhangup()=0; virtualvoidsend(charc)=0; virtualvoidrecv()=0;};57精選pptModem類(可能)有兩個職責(zé):示例2:6精選ppt什么是職責(zé)?
職責(zé)是“變化的原因”。上面的例子可能存在兩種變化的方式:連接和通信可能獨立變化 在這種情況下,應(yīng)該將職責(zé)分開。例如,應(yīng)用的變化導(dǎo)致了連接部分方法的簽名(signature)發(fā)生了變化,那么使用數(shù)據(jù)連接的部分也需要重新編譯、部署,這會相當(dāng)麻煩,使得設(shè)計僵化。連接和通信同時變化 這種情況下,不必將職責(zé)分開。反而分離可能導(dǎo)致“不必要的復(fù)雜性”的臭味刻舟求劍是錯誤的。
——王亞沙58精選ppt什么是職責(zé)?刻舟求劍是錯誤的。7精選ppt修改后的設(shè)計如下: 有一點需要注意:在ModemImplementation中實際還是集合了兩個職責(zé)。這是我們不希望的,但是有時候卻是必須的。 但是我們注意到,對于應(yīng)用的其它部分,通過接口的分離我們已經(jīng)實現(xiàn)了職責(zé)的分離。 ModemImplementation已經(jīng)不被其它任何程序所依賴。除了main以外,其他所有程序都不需要知道這個函數(shù)的存在。
59精選ppt修改后的設(shè)計如下: 有一點需要注意:在ModemImplem常見錯誤提醒:持久化與業(yè)務(wù)規(guī)則的耦合。例如: 業(yè)務(wù)規(guī)則經(jīng)常變化,而持久化方法卻一般不變。將這兩個職責(zé)耦合在一起,將導(dǎo)致每次因為業(yè)務(wù)規(guī)則變化調(diào)整Employee類時,所有持久化部分的代碼也要跟著變化
60精選ppt常見錯誤提醒: 業(yè)務(wù)規(guī)則經(jīng)常變化,而持久化方法卻一般不變。將2.開放封閉原則(OCP)陳述:軟件實體(類、模塊、函數(shù)等)應(yīng)該是可以擴展的,同時還可以是不必修改的,更確切的說,函數(shù)實體應(yīng)該: (1)對擴展是開放的 當(dāng)應(yīng)用的需求變化時,我們可以對模塊進(jìn)行擴展,使其具有滿足改變的新的行為——即,我們可以改變模塊的功能 (2)對更改是封閉的 對模塊進(jìn)行擴展時,不必改動已有的源代碼或二進(jìn)制代碼。分析:世界是變化的(而且變化很快),軟件是對現(xiàn)實的抽象 軟件必須能夠擴展如果任何修改都需要改變已經(jīng)存在的代碼,那么可能導(dǎo)致牽一發(fā)動全身現(xiàn)象,進(jìn)而導(dǎo)致雪崩效應(yīng),使軟件質(zhì)量顯著下降61精選ppt2.開放封閉原則(OCP)陳述:10精選ppt實現(xiàn)OCP的關(guān)鍵是抽象:例子1classclient{ server&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};classserver{ intserverData;public: voidServerFunc();};62精選ppt實現(xiàn)OCP的關(guān)鍵是抽象:classclient{class例子1(續(xù)) 這個程序出了什么問題?
client和server都是具體類,接口與實現(xiàn)沒有實現(xiàn)分離。如果我們想要讓client調(diào)用一個新的server類,那么我們不得不修改client的源代碼
從而帶來編譯、鏈接、部署等一系列的問題。見下頁程序63精選ppt例子1(續(xù)) client和server都是具體類,接口與實2.開放封閉原則(OCP)例子1(續(xù)) classclient{
server&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};classserver{ intserverData;public: voidServerFunc();};classserver1{ intserverData;public: voidServerFunc();};classclient{ server1&s;public: client(server&SER):s(SER){} voiduseServer(){ s.ServerFunc(); }};64精選ppt2.開放封閉原則(OCP)例子1(續(xù)) classcli2.開放封閉原則(OCP)例子1(續(xù)) 修改后的設(shè)計classclient{ ClientInterface&ci;public: client(ClientInterface&CI):ci(CI){} voiduseServer(){ ci.ServerFunc(); }};classClientInterface{ virtualvoidServerFunc()=0;};classserver:publicClientInterface{ intserverData;public: voidServerFunc();};65精選ppt2.開放封閉原則(OCP)例子1(續(xù))classclie例子1(續(xù))一個問題: 為什么上述的ClientInterface這個類要取這么個名字,叫做AbastractServer豈不更好?? 其實這里面蘊含了一個思想:
——client類中更多的描述了高層的策略,而Server類中是對這些策略的一種具體實現(xiàn)。
而接口是策略的一個組成部分,他根client端的關(guān)系更加密切
我們應(yīng)該這樣想問題:ClientInterface中定義了client期 望Server做什么,而server具體類是對client這種要求的 一種具體實現(xiàn)。
OCP原則其實是要求我們清晰的區(qū)分策略和策略的具體實現(xiàn)形式。允許 擴展具體的實現(xiàn)形式(開放),同時將這種擴展與策略隔離開來,使 其對上層的策略透明(封閉)。
66精選ppt例子1(續(xù))15精選ppt例子2 C語言程序 ---------shape.h-----------------emumShapeType{circle,square};structShape{ ShapeTypeitsType;};---------circle.h-----------------structCircle{ ShapeTypeitsType; doubleitsRadius; CPointitscenter;};---------square.h-----------------structSquare{ ShapeTypeitsType; doubleitsSide; CPointitsTopLeft;};---------drawAllShapes.cpp----------typedefstructShape*ShapePointer;voidDrawAllShapes(ShapePointerlist[],intn){ inti; for(i=0;i<n;i++){ structShape*s=list[i]; switch(s->itsType){ casesquare: s->Square(); break; casecircle: s->DrawCircle(); break; }}}67精選ppt例子2 C語言程序 ---------shape.h-例子2(續(xù))批判 這個程序不符合OCP,如果需要處理的幾何圖形中再加入“三角形”將引發(fā)大量的修改僵化的 增加Triangle會導(dǎo)致Shape、Square、Circle以及DrawAllShapes的重新編譯和部署脆弱的 因為存在大量的既難以查找又難以理解的Switch和If語句,修改稍有不慎,程序就會莫明其妙的出錯牢固的 想在一個程序中復(fù)用DrawAllShapes,都必須帶上Circle、Square,即使那個程序不需要他們68精選ppt例子2(續(xù))17精選ppt例子2(續(xù))修改后的設(shè)計 classShape{public: virtualvoidDraw()const=0;};classSquare:publicShape{public: virtualvoidDraw()const;};classCircle:publicShape{public: virtualvoidDraw()const;};voidDrawAllShapes(Vector<Shape*>&list){vector<Shape*>::iteratori;for(i=list.begin();i!=list.end();i++) (*i)->Draw();}69精選ppt例子2(續(xù))classShape{voidDrawAll例子2(續(xù))再看這些批判 再加入“三角形”將變得十分簡單:僵化的 增加Triangle會導(dǎo)致Shape、Square、Circle以及DrawAllShapes的重新編譯和部署脆弱的 因為存在大量的既難以查找又難以理解的Switch和If語句,修改稍有不慎,程序就會莫明其妙的出錯牢固的 想在一個程序中復(fù)用DrawAllShapes,都必須帶上Circle、Square,即使那個程序不需要他們70精選ppt例子2(續(xù))19精選ppt謊言:上述代碼并不完全封閉——“如果我們希望正方形在所有圓之前繪制”會怎么樣?
——對繪圖的順序無法實現(xiàn)封閉
更糟糕的是,剛才的設(shè)計反而成為了實現(xiàn)“正方形在所有圓之前繪制”功能的障礙。71精選ppt謊言:20精選ppt真實的謊言:一般而言,無論模塊多么“封閉”,都會存在一些無法對之封閉的變化
沒有對所有變化的情況都封閉的模型我們怎么辦?既然不可能完全封閉,我們必須有策略的對待此問題——對模型應(yīng)該封閉那類變化作出選擇,封閉最可能出現(xiàn)的變化
這需要對領(lǐng)域的了解,豐富的經(jīng)驗和常識
錯誤的判斷反而不美,因為OCP需要額外的開銷(增加復(fù)雜度)
敏捷的思想——我們預(yù)測他們,但是直到我們發(fā)現(xiàn)他們才行動72精選ppt真實的謊言:21精選ppt回到例2:要實現(xiàn)對排序的封閉應(yīng)該如何設(shè)計?73精選ppt回到例2:22精選pptclassShape{ public: virtualvoidDraw()const=0; virtualboolPrecedes(constShape&)const=0; booloperator<constShape&s){returnPrecedes(s);}};template<typenameP>classLessp{ public:booloperator(){constPp,constPq){return(*p)<(*q);}}voidDrawAllShapes(vector<Shape*>&list){ vector<Shape*>orderedList=list; sort(orderedList.begin(),orderedList.end(),Lessp<Shape*>()); vector<Shape*>::const_iteratorI; for(i=orderedList.begin();i!=orderedList.end();i++) (*i)->Draw();}74精選pptclassShape{23精選ppt對于各個Shape的派生類,需要實現(xiàn)具體的排序規(guī)則Circle類的排序規(guī)則實現(xiàn)如下:BoolCircle::Precedes(constShape&s)const{ if(dynamic_cast<Square*>(s)) returntrue; else returnfalse;}這個程序符合OCP嗎??75精選ppt對于各個Shape的派生類,需要實現(xiàn)具體的排序規(guī)則24精選p利用“數(shù)據(jù)驅(qū)動”的方法獲得封閉性#include<typeinfo>#include<string>#include<iostream>usingnamespacestd;classShape{public: virtualvoidDraw()const=0; virtualboolPrecedes(constShape&)const; booloperator<(constShape&s)const{ returnPrecedes(s); }private: staticchar*typeOrderTable[];};char*Shape::typeOrderTable[]={ typeid(Circle).name(), typeid(Square).name(), 0};boolShape::Precedes(constShape&s)const{ constchar*thisType=typeid(*this).name(); constchar*argType=typeid(s).name(); booldone=false; intthisOrd=-1; intargOrd=-1; for(inti=0;!done;i++){ constchar*tableEntry=typeOrderTable[i]; if(tableEntry!=0){ if(strcmp(tableEntry,thisType)==0) thisOrd=i; if(strcmp(tableEntry,argType)==0) argOrd=i; if((argOrd>0)&&(thisOrd>0)) done=true; } else//tableentry==0 done=true; } returnthisOrd<argOrd;}76精選ppt利用“數(shù)據(jù)驅(qū)動”的方法獲得封閉性#include<type通過上述方法,我們成功地做到了一般情況下DrawAllShapes函數(shù)對于順序問題的封閉,也使得每個Shape派生類對于新的Shape派生類的創(chuàng)建者或者給予類型的Shape對象排序規(guī)則的改變是封閉的。對于不同的Shapes的繪制順序的變化不封閉的唯一部分就是表本身??梢园驯矸胖迷谝粋€單獨的模塊中,和所有其他模塊隔離,因此對于表的改動不會影響其他任何模塊。事實上在C++中我們可以在鏈接時選擇要使用的表。77精選ppt通過上述方法,我們成功地做到了一般情況下DrawAllSha3.LisKov替換原則(LSP)陳述:子類型(Subtype)必須能夠替換他們的基類型(Basetype) BarbaraLiskov對原則的陳述: 若對每個類型S的對象o1,都存在一個類型T的對象o2,使得在所有針對T編寫的程序P中,用o1替換o2后,程序P的行為功能不變,則S是T的子類型。分析:違法這個職責(zé)將導(dǎo)致程序的脆弱性和對OCP的違反 例如:基類Base,派生類Derived,派生類實例d,函數(shù)f(Base*p);f(&d) 會導(dǎo)致錯誤 顯然D對于f是脆弱的。如果我們試圖編寫一些測試,以保證把d傳給f時可以使f具有正確的行為。那么這個測試違反了OCP——因為f無法對Base的所有派生類都是封閉的78精選ppt3.LisKov替換原則(LSP)陳述:27精選ppt示例1:structPoint{doublex,y;};structshape{ enumShapeType{square,circle}itsType; shape(ShapeTypet):itsType(t){}};structCircle:publicShape{ Circle():Shape(circle){}; voidDraw()const; PointitsCenter; doubleitsRadius;};structSquare:publicShape{ Square():Shape(square){}; voidDraw()const; PointitsTopLeft; doubleitsSide;};voidDrawShape(constShape&s){ if(s.itsType==Shape::square) static_cast<constSquare&>(s).Draw(); elseif(s.itsType==Shape::circle) static_cast<constCircle&>(s).Draw();} 顯然,DrawShape違反了OCP, 為什么?
因為Circle和Square違反了LSP79精選ppt示例1:structPoint{doublex,y;};示例2:一次更加奇妙的違規(guī)classRectangle{ PointtopLeft; doulbewidth; doubleheight;public: voidsetWidth(doublew){width=w;} voidsetHeight(doubleh){height=h;} doublegetWidth()const{returnwidth;} doublegetHeight()const{returnheight;}};classSquare:publicRectangle{public: voidsetWidth(doublew); voidsetHeight(doubleh);};voidSquare::setWidth(doublew){ Rectangle::setWidth(w); Rectangle::setHeight(w);};voidSquare::setHeight(doubleh){ Rectangle::setWidth(h); Rectangle::setHeight(h);};80精選ppt示例2:一次更加奇妙的違規(guī)classRectangle{v問題的第一步分析:看下面這個函數(shù)voidf(Rectangle&r){ r.SetWidth(32);}問題:顯然,當(dāng)我們將一個Square的實例傳給f時,將可能導(dǎo)致其height與width不等,破壞了其完整性——違反了LSP要改正上述問題,很簡單,我們只要將SetWidth和SetHeight兩個函數(shù)設(shè)置成virtual函數(shù)即可——添加派生類需要修改基類,通常意味著設(shè)計上的缺陷但是并非所有人都同意上述的分析反方:真正的設(shè)計缺陷是忘記把SetWidth和SetHeight兩個函數(shù)作為virtual函數(shù)正方:設(shè)置長方形的長寬是非?;镜牟僮?,不是預(yù)見到有正方形這樣的派生類,怎么會想到要將其設(shè)成虛函數(shù)?81精選ppt問題的第一步分析:voidf(Rectangle&r){放下這個爭論,我們先將SetWidth和SetHeight改作虛函數(shù)看看classRectangle{ PointtopLeft; doulbewidth; doubleheight;public:
virtualvoidsetWidth(doublew){width=w;}
virtualvoidsetHeight(doubleh){height=h;} doublegetWidth()const{returnwidth;} doublegetHeight()const{returnheight;}};classSquare:publicRectangle{public: voidsetWidth(doublew); voidsetHeight(doubleh);};voidSquare::setWidth(doublew){ Rectangle::setWidth(w); Rectangle::setHeight(w);};voidSquare::setHeight(doubleh){ Rectangle::setWidth(h); Rectangle::setHeight(h);};看起來,很不錯!82精選ppt放下這個爭論,我們先將SetWidth和SetHeight改真正的問題:voidg(Rectangle&r){ r.setWidth(5); r.setHeight(4); assert(r.Area()==20);}函數(shù)g不能操作Square的實例,Square不能替換Rectangle,所以違反了LSPLSP告訴我們:孤立的看,我們無法判斷模型的有效性——考慮一個設(shè)計是否恰當(dāng)時,不能孤立的看待并判斷,應(yīng)該從此設(shè)計的使用者所作出的假設(shè)來審視它!事先的推測是困難的,我們采用敏捷的思想推遲這個判斷——“一個模型是否違反LSP”。直到出現(xiàn)問題的時候我們才解決它。83精選ppt真正的問題:voidg(Rectangle&r){函數(shù)g更加深入的思索:這個看似明顯正確的模型怎么會出錯呢?“正方形是一種長方形”——地球人都知道錯在哪里? 對不是g函數(shù)的編寫者而言,正方形可以是長方形,但是對g函數(shù)的編寫者而言,Square絕對不是Rectangle??!
OOD中對象之間是否存在IS-A關(guān)系,應(yīng)該從行為的角度來看待。
而行為可以依賴客戶程序做出合理的假設(shè)。84精選ppt更加深入的思索:這個看似明顯正確的模型怎么會出錯呢? 對不是基于契約(和約)的設(shè)計——DBC(DeignbyContract)“合理的假設(shè)”使人郁悶。
——我怎么知道是否合理呢??使用DBC,類的編寫者需要顯示的規(guī)定針對該類的契約。客戶代碼的編寫者可以通過契約獲悉行為的依賴方式。契約通過為每一個方法規(guī)定前置條件(preconditions)和后置條件(postconditions)來指定的。要使一個方法執(zhí)行,前置條件一定要為真(對客戶的要求);函數(shù)執(zhí)行后要保證后置條件為真(對函數(shù)編寫者的要求)。85精選ppt基于契約(和約)的設(shè)計——DBC(DeignbyCont基于契約(和約)的設(shè)計——DBC(DeignbyContract)(續(xù))例如: 在上面的例子中,Rectangle::SetWidth(doublew)的后置條件可以看作是:
assert((itsWidth==w)&&(itsHeight==old.itsHeight));基類和派生類在前置條件和后置條件上的關(guān)系是:
如果在派生類中重新申明了基類中已有的成員函數(shù),這個函數(shù)只能使用相等或更弱的前置條件來替換原有的前置條件;并且,只能使用相等或更強的后置條件來替換原有的后置條件。
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 建筑勞務(wù)清包合同
- 園林綠化工程施工合同
- 展廳裝修施工合同協(xié)議書
- 中介房屋買賣合同大全年
- 醫(yī)療健康領(lǐng)域醫(yī)療資源分布統(tǒng)計表
- 導(dǎo)購員聘用合同協(xié)議書
- 2025年潮州貨運上崗證模擬考試0題
- 2025年部編版小學(xué)三年級下冊課外閱讀專項復(fù)習(xí)題(有答案)
- ic芯片購銷合同范本
- 制動氣室市場分析及競爭策略分析報告
- 一年級美術(shù)課后輔導(dǎo)方案-1
- 新法律援助基礎(chǔ)知識講座
- 《鍛造安全生產(chǎn)》課件
- 小學(xué)數(shù)學(xué)1-6年級(含奧數(shù))找規(guī)律專項及練習(xí)題附詳細(xì)答案
- 《同濟大學(xué)簡介》課件
- 《建筑攝影5構(gòu)》課件
- 機電安裝工程質(zhì)量控制
- 愛自己是終身浪漫的開始 心理課件
- 新房房屋買賣合同
- 地鐵出入口雨棚施工工藝
- 人工智能引論智慧樹知到課后章節(jié)答案2023年下浙江大學(xué)
評論
0/150
提交評論