今天聽了《c#面向對象設計模式縱橫談(1):面向對象設計模式與原則》課程。總結了一些筆記。
首先介紹了什麼是設計模式:設計模式描述了軟體設計過程中某一類常見問題的一般性的解決方案。
下面主要讨論面向對象設計模式。
面向對象設計模式描述了類與互相通信的對象之間的組織關系。目的是應對變化、提高複用、減少改變。
那到底什麼是對象:
1、從概念層面講,對象是某種擁有職責的抽象;
2、從規格層面講,對象是一系列可以被其他對象使用的公共接口;
3、從語言實作層面來看,對象封裝了代碼和資料(也就是行為和狀态)。
對于這三點,我了解最深的應該是第三點。這可能和我把大多精力放在了代碼實作上有關,然而忽略了程式設計的的思想。如果我們抛開代碼的實作來看對象的概念,那麼它應該就像一個具體的物體,比如說:榔頭,從概念層面講,榔頭有它的職責,也就是它是做什麼用的(用來砸釘子,當然還會有其他用途,如防身),從規格層面講,比如人使用榔頭砸釘子。
面向對象的設計模式有三大原則:
1、這對接口程式設計,而不是針對實作程式設計。在知道設計模式之前,我對接口的出現很不了解。不明白為什麼這個什麼都不能實作的東西會存在,當然,也對多态表示茫然。實際上我是還沒有了解面向對象程式設計的思想。在對設計模式略有了解後發現接口的确是一個好東西,用它實作多态的确減少了代碼的修改。
比如說在《head first design patterns》中有一個例子,說一個有關鴨子的遊戲。遊戲當中有很多種的鴨子,如:野鴨,木頭鴨,鴨子模型。我們首先會想到做一個抽象類:abstract class duck,duck當中有很多的抽象屬性和方法,如quack。我們用子類繼承的時候都會執行個體化這個方法。
public abstract class duck
{
public abstract void quack()
}
public class mallardduck:duck
public override void quack()
{
console.write("i can quack");
}
當程式成型後,我們有了很多種鴨子,突然,我們發現有的鴨子會飛,我們會在duck中在加上一個抽象方法abstract void fly();于是我們不得不在所有的子類當中添加fly的實作,有人會說,如果我們在duck中直接添加fly的實作,不久不用在子類中添加實作了嗎?那老闆就該問你:你見過木頭鴨子滿天飛(哦,天啊!木頭鴨子也飛起來了,這是什麼世界!)。對不起老闆,現在咱們都見到了。
這時我們換一種想法,如果我們把這些方法都提取出來,把它變成duck的成員,好像問題就會簡單些。
哈哈,好像扯的有點遠了,現在回來接着記我的筆記。
2、優先使用對象組合,而不是類的繼承。
這就使說多使用“has a”,少使用“is a”,哈哈,我又想說回剛才的例子。換個角度考慮duck及其功能,我們設計一個fly的接口和一些具體的飛行方法。
public interface flybehavior
void fly();
public class flywithwing:flybehavior
public void fly()
console.write("i can fly\n");
public class flynoway:flybehavior
console.write("i can't fly\n");
}
好了,對于duck來說,現在它應該有一個(has a)fly的方法
public duck()
{}
public flybehavior flybehavior;
現在我們再來實作兩種鴨子
public class modelduck:duck
public modelduck()
flybehavior = new flynoway();
public mallardduck()
flybehavior = new flywithwing();
這樣如果要是在加上某種行為的話,我們就不必在每一種鴨子上下功夫。把注意力放在我們關心的鴨子品種上(别太使勁關注,小心禽流感,“阿切!”)。
3、封裝變化點,實作松耦合,這點不用多說了。
課程中提到,編碼當中的設計模式使用不是我們在程式設計之初就定下來的,應該是重構得到設計模式(refactoring to patterns)。哦,原來是這樣,也好了解。在編碼中遇到問題,然後想想應對方式。哈哈,我原來認為開始程式設計時就指定我們用什麼設計模式呢。
下面說說設計原則:
1、單一職責原則(srp):一個類應僅有一個引起它變化的原因。
2、開放封閉原則(ocp):類子產品應可擴充,不可修改。這裡要說明一下,擴充和修改是不同的。比如:我們要在加一種modelduck,那麼我們寫一個modelduck的類繼承duck,這叫擴充,不是修改。什麼是修改,就好像我們開始說的那種作法,為了加一個fly的功能,我們要把所有的子類中加入不同的實作,這叫修改。
3、liskov替換原則:子類可替換基類。
4、依賴倒置原則:高層子產品不依賴于低層子產品,二者都依賴于抽象。還是剛才的例子:duck是一個高層子產品,fly是低層子產品。duck不依賴于fly,高層子產品的改變慢,而低層子產品的改變慢。
抽象不應依賴于實作細節,實作細節應依賴于抽象。fly是一個抽象,它不依賴如何飛行。
5、接口隔離原則:不強迫客戶程式依賴于它們不用的方法(有道理,木頭鴨子不會飛為什麼要讓它實作飛的功能。)
最後有人提問接口和抽象類的差別:
接口可以多繼承,抽象類隻能但繼承。接口定義元件間的合同。使用抽象類為一個is a的關系。