要想寫出可維護、可複用、可擴展的應用程序,就必須掌握一些設計模式的思想.設計模式重在設計,是一種思想層面的東西,需要細細的去領悟。今天學習了一下設計模式的第一個模式——策略模式,現在把它整理出來。
策略模式:定義了算法族,分別封裝起來,讓他們之間可以互相轉換,此模式讓算法的變化獨立於使用算法的用戶。初次去看這麼一個定義,估計很多人跟我一樣,直接暈掉或者崩潰掉。完全不理解,沒關係,我們由一個例子來逐步分析策略模式。
這是一個關於鴨子類的遊戲,在這個程序中,會有各種各樣的鴨子在水裏游泳,呱呱叫等。我們來看代碼實現.
<span style="font-family:Courier New;font-size:14px;">/*
* 鴨子的抽象類 我們定義了一個鴨子超類,他擁有鴨子共同特徵的方法,比如呱呱叫,並且擁有一個抽象的方法,因爲鴨子有各種各樣不同的外觀
*/
public abstract class Duck {
//鴨子呱呱叫
public void quack() {
System.out.println("呱呱...");
}
//游泳行爲
public void swim() {
System.out.println("我可以在水裏游泳");
}
//鴨子外觀的抽象方法
public abstract void display();
}</span>
<span style="font-family:Courier New;font-size:14px;">/*
* 外觀是綠頭的鴨子 繼承自超類 Duck那麼它也就擁有了超類中的行爲,實現超類的抽象方法,以展示自己不同於超類的特徵
*/
class MallardDuck extends Duck{
@Override
public void display() {
System.out.println("我是綠頭鴨哦");
}
}</span>
<span style="font-family:Courier New;font-size:14px;">/*
* 外觀是紅頭的鴨子
*/
class ReadheadDuck extends Duck{
@Override
public void display() {
System.out.println("我是紅頭鴨哦");
}
}</span>
<span style="font-family:Courier New;font-size:14px;">public class DuckTest {
public static void main(String[] args) {
MallardDuck md = new MallardDuck();
md.display();
md.quack();
md.swim();
}
}</span>
這個程序已經寫好了,不過好像還沒有用到設計模式相關的東西,是的,讓我們一步一步的引入策略模式。
現在我們有一個需求,讓會飛的鴨子來超越其他的競爭者。如果讓我們去實現這樣的功能,可能會覺得很簡單啊,我們在超類Duck中,寫一個飛行類的方法,這樣他的子類不就都擁有了飛行行爲。恩,確實是這樣,但是有一個問題,並不是所有的鴨子都具有飛行行爲,比如在這個遊戲中會有一些木有鴨子,他們是不會飛甚至不會叫的。那麼剛纔的做法就會使得不會飛的鴨子也有了飛行的行爲,顯然是不合適的。
那麼我們是不是可以利用接口去達到剛纔的需求呢。我們把飛行的行爲以及呱呱叫的行爲從超類中抽取出來,放到Flyable和Quackable接口中,這樣就可以讓擁有這些行爲的類去實現這些接口.我們來看代碼:
<span style="font-family:Courier New;font-size:14px;">/*
* 鴨子的抽象類
*/
public abstract class Duck {
//鴨子外觀的抽象方法
public abstract void display();
public void swim() {
System.out.println("遊啊遊..");
}
}
/*
* 外觀是綠頭的鴨子
*/
class MallardDuck extends Duck implements Quackable,Flyable{
@Override
public void display() {
System.out.println("我是綠頭鴨哦");
}
@Override
public void fly() {
System.out.println("我正在飛哦..");
}
@Override
public void quack() {
System.out.println("呱呱叫..");
}
}
/*
* 木頭鴨 不會飛也不會叫 外觀是木頭樣的顏色 假定因爲可以在水上漂 就相當於會游泳
*/
class WoodDuck extends Duck{
@Override
public void display() {
System.out.println("我的外觀顏色是木般的顏色");
}
}
/*
* 飛行的接口
*/
interface Flyable {
void fly();
}
/*
* 呱呱叫行爲的接口
*/
interface Quackable {
void quack();
}
public class DuckTest {
public static void main(String[] args) {
MallardDuck md = new MallardDuck();
md.display();
md.swim();
md.quack();
md.fly();
WoodDuck wd = new WoodDuck();
wd.swim();
wd.display();
}</span>
}
不錯,這樣是解決了剛纔的問題,可以有個問題,那麼就是代碼的複用性,假設我們有100個鴨子,都有同樣的飛行行爲,那麼這同樣的代碼我們要在每個子類中寫一遍,相當於100遍,即便粘貼複製,也要好久吧.如果要修改的話就更麻煩了。所以從代碼複用的角度來看,似乎又不太可行。設計中有一個原則就是:找出應用中可能需要變化的地方,將它們獨立出來,與那些不需要變化的代碼分離。
從上面的程序分析可知,需要變化的地方是飛行和呱呱叫的行爲,因爲不同的鴨子具有不同的飛行行爲或者是否會呱呱叫。所以根據設計原則,我們應該將他們抽取出來,建立一組新的類來代表每個行爲。這裏可能有人會有疑問,行爲怎麼可以成爲一個類呢?因爲飛行也會有屬性,比如飛行的速度,飛行的高度等。或者我們將這個行爲類想象成一個技能學習中心,父類中的行爲是從這裏學習得來的,子類也是。但如果我們要想在鴨子子類中可以動態的去改變飛行的行爲,該怎麼做?有這樣的一個設計原則:針對接口編程,而不是針對實現編程。所以我們用FlyBehavior和QuackBehavior接口來代表每個行爲,然後行爲的每個實現類都去實現其中的接口。這種做法跟以往不同,我們以往做法是行爲來自Duck超類的具體實現,或是繼承某個接口並由子去實現,這都是依賴於“實現”,這種做法不靈活。現在我們來看代碼實現:
<span style="font-family:Courier New;font-size:14px;">/*
* 鴨子的抽象類
*/
public abstract class Duck {
//行爲接口的變量
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;
//鴨子外觀的抽象方法
public abstract void display();
public void performFly() {
//委託給行爲類
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("遊啊遊..");
}
}
/*
* 飛行的接口
*/
interface FlyBehavior{
public void fly();
}
//飛行的具體實現類
class FlyWithWing implements FlyBehavior {
public void fly() {
System.out.println("我會飛哦");
};
}
class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("我不會飛啊");
}
}
/*
* 呱呱叫行爲的接口
*/
interface QuackBehavior {
public void quack();
}
class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("我不會叫啊");
}
}
/*
* 外觀是綠頭的鴨子
*/
class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWing();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("我是綠頭鴨哦");
}
}
/*
* 木頭鴨 不會飛也不會叫 外觀是木頭樣的顏色 假定因爲可以在水上漂 就相當於會游泳
*/
class WoodDuck extends Duck{
public WoodDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new MuteQuack();
}
@Override
public void display() {
System.out.println("我的外觀顏色是木般的顏色");
}
}
public class DuckTest {
public static void main(String[] args) {
//向上轉型
Duck md = new MallardDuck();
md.display();
md.performFly();
md.performQuack();
Duck wd = new WoodDuck();
wd.display();
wd.performFly();
wd.performQuack();
}
}</span>
雖然代碼量並沒有減少,但是可擴展性可維護性是增加的。現在我們雖然在main方法裏調用的是同一個方法,但是具體的結果卻不同。我們還可以在Duck類裏設定一個改變飛行行爲或其他行爲的方法,這樣子類就可以動態的去設定行爲了。
設計模式是一種思想,既然是思想上的東西,就需要我們不斷的思考,領悟,在實踐當中不斷的加以運用與驗證。雖然寫了這篇博客,但並不代表我已經徹底掌握,後面還需要不斷的理解、思考並將這些思想運用到實踐當中去。
ps:如要轉載,請註明出處。另推薦Head First 設計模式這本書,對於初學者來說是一本不錯的書,博客中的例子也是引用書裏面的。