每一個模式描述了一個在我們周圍不斷重復發(fā)生的問題,以及該問題的解決方案的核心.這樣,你就能一次又一次地使用該方法而不必做重復勞動
by Christopher Alexander
從目前的編碼過程中,可能我的狀態(tài)就是比較注重功能的實現(xiàn)轧膘,然而面對其他的比如對象之間的耦合關系什么的基本沒有考慮到推掸,雖然使用著面向對象的語言罪塔,做的好像不是面向對象的事情懊昨。學習設計模式能讓人加深對面向對象編程的理解玷氏,有時候會讓人有一種醍醐灌頂?shù)母杏X元旬。然而設計模式雖好岂昭,但不要沉迷其中無法自拔,還是得有自己的思考车伞。好了說了些廢話择懂,正文開始了。
說到設計模式就不得不提一本書GOF設計模式:可復用面向對象軟件的基礎另玖,我們把目光鎖定在后面的解釋困曙,可復用,正如開頭所引用的話谦去,模式的最終目標就是復用慷丽,放在軟件開發(fā)中也是如此,一個臃腫的類體系是人們不愿意看到的鳄哭。另外要糊,設計模式提供了一種不同的思維方式,它使得我們跳出了事物本身的局限妆丘,去考慮更加本質的東西锄俄,首先就從java的面向對象說起局劲,這里有兩種不同的思維方式
- 底層思維:向下,如何把握機器底層奶赠,我們是從微觀的角度去理解對象構造鱼填,這涉及到了很多方面的知識:
- 語言構造
- 編譯轉換
- 內存模型
- 運行時機制
- 抽象思維:向上,這種思維方式在設計領域的時候用的比較多毅戈,拋開一些編碼和編譯的細節(jié)苹丸,主要關心如何將我們周圍的世界抽象為程序代碼,這個過程就關系著設計最終的好壞苇经,代碼的品質:
- 面向對象
- 組件封裝
- 設計模式
- 架構模式
在抽象思維里赘理,上面的方式是基本互通的,只是表達方式不同扇单。
抽象思維是建立在比較升入理解了底層思維的基礎上的感憾,面向對象的三大特性:封裝(隱藏內部實現(xiàn)),繼承(復用現(xiàn)有代碼)令花,多態(tài)(改寫對象行為)阻桅,這三大特性在設計模式里十分重要,在設計模式里兼都,理解靈活多變的利用這三大特性來描述現(xiàn)實世界嫂沉,也就理解了設計模式這樣設計的原因和意義所在。
由于個人水平比較菜扮碧,所以現(xiàn)在也還沒有參與過什么項目趟章,然而耳聞過。為什么說軟件設計很復雜慎王,因為在項目開展的過程中蚓土,會有永運猜不透想法的客戶的需求變化,你不得不接受的團隊成員的流動赖淤,技術平臺日新月異的更新等一系列不可控因素蜀漆,然而在軟件設計的初期要盡可能的把這些變化考慮進去,難不難咱旱,當然難确丢。需不需要去解決,當然需要吐限!怎么解決鲜侥?當人們遇到比較復雜的問題的時候,都這樣做诸典,我也這樣做:
- 分解
- 人們面向復雜性有一個常見的做法:即分而治之描函,將大問題分解問哦多個小問題,將復雜問題分解為多個簡單問題,這個方法在一些場景里是非常適用的舀寓,比如算法里分治法的使用益缠。不過在面對復雜性多樣變化的場景時,就不是那么的可愛了基公,首先大問題的劃分就很難統(tǒng)一,因為未來的變化總是未知的宋欺,掌握所有的情況是不可能的轰豆。
- 抽象
- 更高層次來講,人們處理復雜性有一個通用的技術齿诞,即抽象酸休。由于不能掌握全部的復雜對象,我們選擇忽略它的非本質細節(jié)祷杈,而去處理泛化和理想化了的對象模型斑司。這有點哲學的味道了,關注本質但汞。
比如我們有兩個圖形類:
//直線類
class Line {
public int startX;
public int startY;
public int endX;
public int endY;
public Line(int startX, int startY, int endX, int endY) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
}
}
//矩形類
class Rect {
public int left;
public int top;
public int right;
public int bottom;
public Rect(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
然后在MainActivity中分別實現(xiàn)宿刮,不需要在意功能,就只關心使用的地方
Vector<Line> lineVector = new Vector<Line>();
Vector<Rect> rectVector = new Vector<Rect>();
if (lineRadio.isChecked()) {
lineVector.add(new Line(startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y));
} else if (rectRadio.isChecked()) {
rectVector.add(new Rect(startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y));
}
//更改...
else if(...){
}
}
for (int i = 0; i < lineVector.size(); i++) {
canvas.drawLine(lineVector.get(i).startX,
lineVector.get(i).startY,
lineVector.get(i).endX,
lineVector.get(i).endY,
paint);
}
for (int i = 0; i < rectVector.size(); i++) {
canvas.drawRect(rectVector.get(i).left,
rectVector.get(i).top,
rectVector.get(i).right,
rectVector.get(i).bottom,
paint);
}
上面第一段代碼是先生成每一個圖形的Vector私蕾,然后分別add僵缺,下面的for循環(huán)可以理解為輪詢每一個不同的Vector對象,然后畫出圖形踩叭。當代碼寫完了磕潮,突然想起,哎呀忘記Circle了,于是首先得在寫出一個Circle的類,然后new一個Circle的Vector數(shù)組尔崔,接著在add處也要修改凿将,最后還要增加一個for循環(huán),忘記一個就要更改4處沪猴,是不是很復雜,而且當代碼多的時候,更改的出錯率會增多戏罢,這是很危險的。
如果我們從另外一方面脚囊,或者說抽象出來龟糕,這些都是屬于圖形,那我們定義一個抽象圖形類:
//形狀基類
abstract class Shape {
abstract void draw(Canvas canvas, Paint paint);
}
然其他無論什么圖形都繼承它悔耘。接著生成了一個Vector數(shù)組:
Vector<Shape> shapeVector = new Vector<Shape>();
而add時讲岁,增加具體圖形的數(shù)據(jù),for循環(huán)也是它,這樣不管是什么圖形缓艳,都可以繪制出來校摩,不用單獨去考慮:
for (int i = 0; i < shapeVector.size(); i++) {
shapeVector.get(i).draw(canvas, paint);
}
如果需求變更了,增加了圖形阶淘,我們只需要繼承Shape寫一個實體類衙吩,再在add處增加新圖形的add方法,其他的都不會變化溪窒。相對于第一種坤塞,當需求變化發(fā)生的時候,很多的代碼得以復用澈蚌,而不用像第一種手忙腳亂摹芙,這里那里修改,所以相對于分解思維模式宛瞄,抽象模式的優(yōu)勢就體現(xiàn)出來--復用浮禾。
所以用了java語言,并不一定擁有了抽象的設計思維份汗,說的就是我盈电。
重新認識面向對象:
- 理解隔離變化
- 從宏觀層面來看,面向對象的構建方式更能使用軟件的變化杯活,比如上面的例子挣轨,能將變化所帶來的影響減為最小
- 各司其職
- 從微觀層面,面向對象的方式更強調各個類的責任
- 由于需求變化導致的新增類型不應該影響原來的類型的實現(xiàn)-就是叫做所謂各負其責轩猩。
- 對象深入
- 從語言實現(xiàn)層面來說卷扮,對象封裝了代碼和數(shù)據(jù)
- 從規(guī)格層面來講,對象是一系列可被使用的公共接口
- 從概率層面講均践,對象是某種擁有責任的抽象
面向對象設計原則:
- 依賴倒置原則(DIP)
- 高層模塊(穩(wěn)定)不應該依賴低層模塊(變化)晤锹,二者都應該依賴于抽象(穩(wěn)定)
- 抽象(穩(wěn)定)不應該依賴與實現(xiàn)細節(jié)(變化),實現(xiàn)細節(jié)應該依賴于抽象 (穩(wěn)定)
- 開放封閉原則(OCP)
- 對擴展開發(fā)彤委,對更改封閉
- 類模塊應該是可擴展鞭铆,但是不可修改
- 單一職責原則(SRP)
- 一個類應該僅有一個引起它變化的原因
- 變化的方向隱含這類的責任。
- Liskov替換原則(LSP)
- 子類必須能夠替換他們的基類
- 繼承表達類型抽象
- 接口隔離原則(ISP)
- 不應該強迫客戶程序依賴他們不用的方法
- 接口應該小而完備
- 優(yōu)先使用對象組合焦影,而不是類繼承
- 類繼承通常為"白箱復用"车遂,對象組合通常為"黑箱復用"
- 類繼承在某種程度上破壞了封裝性,子類父類耦合度高
- 而對象組合則只要求被組合的對象具有良好定義的接口斯辰,耦合度低
- 封裝變化點
- 使用封裝來創(chuàng)建對象之間的分界層舶担,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響彬呻,從而實現(xiàn)層次間的松耦合
- 針對接口編程衣陶,而不是針對實現(xiàn)編程
- 不將變量類型聲明為某個特定的具體類柄瑰,而是聲明為某個接口
- 客戶程序無需獲知對象的具體類型,只需要知道對象所具有的接口剪况。
- 減少系統(tǒng)中各部分的依賴關系教沾,從而實現(xiàn)“高內聚,松耦合”的類型設計方案
老師舉了兩個例子译断,一個是秦統(tǒng)一六國之后授翻,統(tǒng)一貨幣統(tǒng)一度量衡,從軟件開發(fā)的角度來說就是統(tǒng)一了接口孙咪,并且使其規(guī)范化堪唐。再一個是畢升的活字印刷術,它的發(fā)明之所以推進了人類文明的發(fā)展该贾,是因為首先復用了每個漢字,按需所用捌臊,不需要像雕版印刷一樣來一篇文章刻一次版杨蛋,其次規(guī)定了每個字模的大小規(guī)格,畢升就是松耦合大師理澎。通過這個感性的認識逞力,就會體會到接口規(guī)范化對于行業(yè)的貢獻。
以上糠爬,歡迎指正寇荧。