需求
展示一只鴨子,鴨子會(huì)叫,會(huì)游泳,還有不同的外觀。
初步設(shè)計(jì)
public abstract class Duck
{
/**
* 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
*/
public abstract void display();
public void swim()
{
System.out.println("鴨子會(huì)游泳");
}
public void quack()
{
System.out.println("鴨子會(huì)呱呱叫");
}
}
看起來(lái)似乎不錯(cuò),?鴨子只要繼承自 Duck,然后實(shí)現(xiàn)自己的外觀即可了。
一段時(shí)間后,需求改了,現(xiàn)在需要鴨子會(huì)飛,叫聲也可能有不一樣的。
于是就在父類(lèi)中加了 fly 的方法
public abstract class Duck
{
public void fly()
{
System.out.println("讓鴨子飛");
}
// 省略其他的
}
叫聲在這里不用修改,因?yàn)樽宇?lèi)只需要覆蓋quack方法即可。
然后可怕的事情發(fā)生了,現(xiàn)在發(fā)現(xiàn),橡皮鴨子也會(huì)飛了,原本是不應(yīng)該會(huì)飛的。
可見(jiàn)為了復(fù)用而使用繼承會(huì)導(dǎo)致問(wèn)題:原本某些子類(lèi)不需要的能力被父類(lèi)強(qiáng)行賦予了
思考以上代碼會(huì)導(dǎo)致的問(wèn)題:
- 代碼在多個(gè)子類(lèi)中重復(fù)(不會(huì)飛的鴨子也有了飛的方法)
- 牽一發(fā)動(dòng)全身,后續(xù)需要加入新功能,會(huì)導(dǎo)致所有的子類(lèi)都受影響
- 不能動(dòng)態(tài)改變鴨子的行為,比如讓他豎著飛或橫著飛,也很難知道鴨子的全部行為
既然繼承?不好,那就使用接口怎么樣?
考慮到“飛”,“叫聲”行為是?可變的,因此封裝成接口,讓需要飛和叫的鴨子實(shí)現(xiàn)這些接口不就好了嗎?來(lái)試試!
/**
* 所有飛行行為類(lèi)必須實(shí)現(xiàn)的接口
*/
public interface FlyBehavior
{
public void fly();
}
/**
* 所有鴨叫行為必須實(shí)現(xiàn)的接口
*/
public interface QuackBehavior
{
public void quack();
}
那么鴨子父類(lèi)就變成:
public abstract class Duck
{
/**
* 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
*/
public abstract void display();
public void swim()
{
System.out.println("鴨子會(huì)游泳");
}
}
而需要叫聲或者飛行行為的鴨子之類(lèi),就可以通過(guò)實(shí)現(xiàn)接口來(lái)完成,然后實(shí)現(xiàn)自己的飛和叫的行為,不管是橫著飛豎著飛,你自己都可以決定。
但是也會(huì)導(dǎo)致以下問(wèn)題:
- 如果需要修改行為,那么就要具體到每個(gè)鴨子子類(lèi)的源碼,一不小心可能就會(huì)出現(xiàn)問(wèn)題。
- 代碼無(wú)法復(fù)用,比如說(shuō),定義了 A 鴨子會(huì)?橫著飛,,B 鴨子會(huì)橫著飛,那么橫著飛這個(gè)行為就會(huì)在 A 和 B 中重復(fù)。
軟件開(kāi)發(fā)的真理
需求永遠(yuǎn)在變
設(shè)計(jì)原則 1
找出可能變化的地方,并將可能會(huì)變化的獨(dú)立出來(lái),不要和不變的那些?代碼混在一起。
也就是說(shuō):如果有新需求?來(lái)了,就會(huì)使某一塊變化,那么就可以確定,這塊是需要被?抽出來(lái)的。確保系統(tǒng)中某一部分的改變,不會(huì)導(dǎo)致影響到其他部分
設(shè)計(jì)原則 2
針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程,是針對(duì)超類(lèi)型編程,這里的接口不一定是語(yǔ)法意義上的接口。也就是利用多態(tài)。
OK ?,現(xiàn)在可以將飛和叫聲這兩個(gè)會(huì)變化的獨(dú)立出來(lái)了
我們可以將飛這個(gè)行為歸為一組,將叫聲這個(gè)行為歸為一組。它們將和鴨子類(lèi)完全隔離。
/**
* 飛行行為的實(shí)現(xiàn)類(lèi),給不會(huì)飛的鴨子用
*/
public class FlyNoWay implements FlyBehavior
{
@Override
public void fly()
{
System.out.println("我不會(huì)飛");
}
}
/**
* 飛行實(shí)現(xiàn)類(lèi),用火箭來(lái)飛
*/
public class FlyRocketPowered implements FlyBehavior
{
@Override
public void fly()
{
System.out.println("我可以和火箭一起飛");
}
}
/**
* 飛行行為的實(shí)現(xiàn),給真會(huì)飛的鴨子用
*/
public class FlyWithWings implements FlyBehavior
{
@Override
public void fly()
{
System.out.println("我會(huì)飛");
}
}
/**
* 聲音實(shí)現(xiàn)類(lèi),給會(huì)吱吱叫的鴨子用
*/
public class Squeak implements QuackBehavior
{
@Override
public void quack()
{
System.out.println("我會(huì)吱吱叫");
}
}
/**
* 叫聲實(shí)現(xiàn)類(lèi),給不會(huì)叫的鴨子用
*/
public class MuteQuack implements QuackBehavior
{
@Override
public void quack()
{
System.out.println("我不會(huì)叫");
}
}
整合鴨子的行為
?如何?讓鴨子和他們的行為發(fā)生關(guān)聯(lián)呢?我們的目的是行為可以被動(dòng)態(tài)賦予,因此可以讓行為成為鴨子的一個(gè)實(shí)例變量,暴露出相應(yīng)的方法去觸發(fā)行為。新的鴨子類(lèi)如下:
/**
* 鴨子父類(lèi)
*/
public abstract class Duck
{
// 所有鴨子子類(lèi)都實(shí)現(xiàn)這兩個(gè)接口類(lèi)型
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
/**
* 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
*/
public abstract void display();
// 委托給飛行行為類(lèi)
public void performFly()
{
flyBehavior.fly();
}
// 委托給鴨叫聲類(lèi)
public void perfirmQuack()
{
quackBehavior.quack();
}
/**
* 只要是鴨子都會(huì)游泳
*/
public void swim()
{
System.out.println("所有的鴨子都會(huì)游泳");
}
/**
* 動(dòng)態(tài)設(shè)定鴨子的飛行方式
*/
public void setFlyBehavior(FlyBehavior fb)
{
flyBehavior = fb;
}
/**
* 動(dòng)態(tài)設(shè)定鴨子的叫聲
*/
public void setQuackBehavior(QuackBehavior qb)
{
quackBehavior = qb;
}
}
測(cè)試
現(xiàn)在可以創(chuàng)建你想要的鴨子了。
/**
* 綠頭鴨類(lèi),一繼承了Duck類(lèi),從一開(kāi)始就是會(huì)游泳的,而外觀可以自己定義,飛行方式和叫聲都可以根據(jù)需要自己定義
*/
public class MallardDuck extends Duck
{
public MallardDuck()
{
// 聲明這個(gè)鴨子是飛行和叫聲行為
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display()
{
System.out.println("我是一只綠頭鴨");
}
}
/**
* 創(chuàng)建一個(gè)模型鴨,一開(kāi)始是不會(huì)飛的
*/
public class ModelDuck extends Duck
{
public ModelDuck()
{
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display()
{
System.out.println("我是一只模型鴨");
}
}
public static void main(String[] args)
{
// 創(chuàng)建一個(gè)綠頭鴨
Duck mallardDuck = new MallardDuck();
// 調(diào)用繼承而來(lái)的performFly方法,委托給FlyBehavior對(duì)象處理
mallardDuck.performFly();
// 調(diào)用繼承而來(lái)的performQuack方法,委托給QuackBehavior對(duì)象處理
mallardDuck.perfirmQuack();
// 創(chuàng)建一個(gè)模型鴨
Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.perfirmQuack();
// 動(dòng)態(tài)設(shè)定鴨子的飛行方式,這里讓鴨子和火箭一起飛
modelDuck.setFlyBehavior(new FlyRocketPowered());
// 這樣就可以動(dòng)態(tài)的設(shè)定鴨子的行為了,如果綁定在鴨子類(lèi)中,就無(wú)法這樣做
modelDuck.performFly();
}
設(shè)計(jì)原則 3
多用組合,少用繼承。
上面可以看到的是,鴨子的行為并不是通過(guò)繼承而來(lái)的,而是通過(guò)組合而來(lái)的。
鴨子的飛行行為可以看做是一族算法,叫聲行為也可以看做是一族算法,在一族算法內(nèi),族內(nèi)的行為是可以互相替換的,比如一族飛行算法,橫著飛和豎著飛是可以互相替換的。
策略模式的定義:
定義了算法族,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶(hù)。