今天聽(tīng)了《C#面向?qū)ο笤O(shè)計(jì)模式縱橫談(1):面向?qū)ο笤O(shè)計(jì)模式與原則》課程。總結(jié)了一些筆記。
首先介紹了什么是設(shè)計(jì)模式:設(shè)計(jì)模式描述了軟件設(shè)計(jì)過(guò)程中某一類常見(jiàn)問(wèn)題的一般性的解決方案。
下面主要討論面向?qū)ο笤O(shè)計(jì)模式。
面向?qū)ο笤O(shè)計(jì)模式描述了類與相互通信的對(duì)象之間的組織關(guān)系。目的是應(yīng)對(duì)變化、提高復(fù)用、減少改變。
那到底什么是對(duì)象:
1、從概念層面講,對(duì)象是某種擁有職責(zé)的抽象;
2、從規(guī)格層面講,對(duì)象是一系列可以被其他對(duì)象使用的公共接口;
3、從語(yǔ)言實(shí)現(xiàn)層面來(lái)看,對(duì)象封裝了代碼和數(shù)據(jù)(也就是行為和狀態(tài))。
對(duì)于這三點(diǎn),我理解最深的應(yīng)該是第三點(diǎn)。這可能和我把大多精力放在了代碼實(shí)現(xiàn)上有關(guān),然而忽略了編程的的思想。如果我們拋開(kāi)代碼的實(shí)現(xiàn)來(lái)看對(duì)象的概念,那么它應(yīng)該就像一個(gè)具體的物體,比如說(shuō):榔頭,從概念層面講,榔頭有它的職責(zé),也就是它是做什么用的(用來(lái)砸釘子,當(dāng)然還會(huì)有其他用途,如防身),從規(guī)格層面講,比如人使用榔頭砸釘子。
面向?qū)ο蟮脑O(shè)計(jì)模式有三大原則:
1、這對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。在知道設(shè)計(jì)模式之前,我對(duì)接口的出現(xiàn)很不理解。不明白為什么這個(gè)什么都不能實(shí)現(xiàn)的東西會(huì)存在,當(dāng)然,也對(duì)多態(tài)表示茫然。實(shí)際上我是還沒(méi)有理解面向?qū)ο缶幊痰乃枷?。在?duì)設(shè)計(jì)模式略有了解后發(fā)現(xiàn)接口的確是一個(gè)好東西,用它實(shí)現(xiàn)多態(tài)的確減少了代碼的修改。
比如說(shuō)在《Head First DesignPatterns》中有一個(gè)例子,說(shuō)一個(gè)有關(guān)鴨子的游戲。游戲當(dāng)中有很多種的鴨子,如:野鴨,木頭鴨,鴨子模型。我們首先會(huì)想到做一個(gè)抽象類:abstract class Duck,Duck當(dāng)中有很多的抽象屬性和方法,如quack。我們用子類繼承的時(shí)候都會(huì)實(shí)例化這個(gè)方法。
public abstract class Duck
{
public abstract void quack()
}
public class MallardDuck:Duck
{
public override void quack()
{
Console.Write("I can quack");
}
}
當(dāng)程序成型后,我們有了很多種鴨子,突然,我們發(fā)現(xiàn)有的鴨子會(huì)飛,我們會(huì)在Duck中在加上一個(gè)抽象方法abstract voidfly();于是我們不得不在所有的子類當(dāng)中添加fly的實(shí)現(xiàn),有人會(huì)說(shuō),如果我們?cè)贒uck中直接添加fly的實(shí)現(xiàn),不久不用在子類中添加實(shí)現(xiàn)了嗎?那老板就該問(wèn)你:你見(jiàn)過(guò)木頭鴨子滿天飛(哦,天啊!木頭鴨子也飛起來(lái)了,這是什么世界?。?。對(duì)不起老板,現(xiàn)在咱們都見(jiàn)到了。
這時(shí)我們換一種想法,如果我們把這些方法都提取出來(lái),把它變成Duck的成員,好像問(wèn)題就會(huì)簡(jiǎn)單些。
哈哈,好像扯的有點(diǎn)遠(yuǎn)了,現(xiàn)在回來(lái)接著記我的筆記。
2、優(yōu)先使用對(duì)象組合,而不是類的繼承。
這就使說(shuō)多使用“has a”,少使用“is a”,哈哈,我又想說(shuō)回剛才的例子。換個(gè)角度考慮Duck及其功能,我們?cè)O(shè)計(jì)一個(gè)fly的接口和一些具體的飛行方法。
public interface FlyBehavior
{
void fly();
}
public class FlyWithWing:FlyBehavior
{
public void fly()
{
Console.Write("I can fly\n");
}
}
public class FlyNoWay:FlyBehavior
{
public void fly()
{
Console.Write("I can‘t fly\n");
}
}
好了,對(duì)于Duck來(lái)說(shuō),現(xiàn)在它應(yīng)該有一個(gè)(has a)fly的方法
public abstract class Duck
{
public Duck()
{}
public FlyBehavior flybehavior;
}
現(xiàn)在我們?cè)賮?lái)實(shí)現(xiàn)兩種鴨子
public class ModelDuck:Duck
{
public ModelDuck()
{
flybehavior = new FlyNoWay();
}
}
public class MallardDuck:Duck
{
public MallardDuck()
{
flybehavior = new FlyWithWing();
}
}
這樣如果要是在加上某種行為的話,我們就不必在每一種鴨子上下功夫。把注意力放在我們關(guān)心的鴨子品種上(別太使勁關(guān)注,小心禽流感,“阿切!”)。
3、封裝變化點(diǎn),實(shí)現(xiàn)松耦合,這點(diǎn)不用多說(shuō)了。
課程中提到,編碼當(dāng)中的設(shè)計(jì)模式使用不是我們?cè)诰幊讨蹙投ㄏ聛?lái)的,應(yīng)該是重構(gòu)得到設(shè)計(jì)模式(Refactoring to Patterns)。哦,原來(lái)是這樣,也好理解。在編碼中遇到問(wèn)題,然后想想應(yīng)對(duì)方式。哈哈,我原來(lái)認(rèn)為開(kāi)始編程時(shí)就指定我們用什么設(shè)計(jì)模式呢。
下面說(shuō)說(shuō)設(shè)計(jì)原則:
1、單一職責(zé)原則(SRP):一個(gè)類應(yīng)僅有一個(gè)引起它變化的原因。
2、開(kāi)放封閉原則(OCP):類模塊應(yīng)可擴(kuò)展,不可修改。這里要說(shuō)明一下,擴(kuò)展和修改是不同的。比如:我們要在加一種ModelDuck,那么我們寫(xiě)一個(gè)ModelDuck的類繼承Duck,這叫擴(kuò)展,不是修改。什么是修改,就好像我們開(kāi)始說(shuō)的那種作法,為了加一個(gè)fly的功能,我們要把所有的子類中加入不同的實(shí)現(xiàn),這叫修改。
3、Liskov替換原則:子類可替換基類。
4、依賴倒置原則:高層模塊不依賴于低層模塊,二者都依賴于抽象。還是剛才的例子:Duck是一個(gè)高層模塊,fly是低層模塊。Duck不依賴于fly,高層模塊的改變慢,而低層模塊的改變慢。
抽象不應(yīng)依賴于實(shí)現(xiàn)細(xì)節(jié),實(shí)現(xiàn)細(xì)節(jié)應(yīng)依賴于抽象。fly是一個(gè)抽象,它不依賴如何飛行。
5、接口隔離原則:不強(qiáng)迫客戶程序依賴于它們不用的方法(有道理,木頭鴨子不會(huì)飛為什么要讓它實(shí)現(xiàn)飛的功能。)
最后有人提問(wèn)接口和抽象類的區(qū)別:
接口可以多繼承,抽象類只能但繼承。接口定義組件間的合同。使用抽象類為一個(gè)is a的關(guān)系。