需求
展示一只鴨子秃踩,鴨子會(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ì)飛,叫聲也可能有不一樣的
谎亩。
于是就在父類中加了 fly 的方法
public abstract class Duck
{
public void fly()
{
System.out.println("讓鴨子飛");
}
// 省略其他的
}
叫聲在這里不用修改炒嘲,因?yàn)樽宇愔恍枰采wquack方法即可。
然后可怕的事情發(fā)生了匈庭,現(xiàn)在發(fā)現(xiàn)夫凸,橡皮鴨子也會(huì)飛了,原本是不應(yīng)該會(huì)飛的阱持。
可見(jiàn)為了復(fù)用而使用繼承會(huì)導(dǎo)致問(wèn)題:原本某些子類不需要的能力被父類強(qiáng)行賦予了
思考以上代碼會(huì)導(dǎo)致的問(wèn)題:
- 代碼在多個(gè)子類中重復(fù)(不會(huì)飛的鴨子也有了飛的方法)
- 牽一發(fā)動(dòng)全身夭拌,后續(xù)需要加入新功能,會(huì)導(dǎo)致所有的子類都受影響
- 不能動(dòng)態(tài)改變鴨子的行為衷咽,比如讓他豎著飛或橫著飛鸽扁,也很難知道鴨子的全部行為
既然繼承?不好,那就使用接口怎么樣镶骗?
考慮到“飛”献烦,“叫聲”行為是?可變的,因此封裝成接口卖词,讓需要飛和叫的鴨子實(shí)現(xiàn)這些接口不就好了嗎巩那?來(lái)試試!
/**
* 所有飛行行為類必須實(shí)現(xiàn)的接口
*/
public interface FlyBehavior
{
public void fly();
}
/**
* 所有鴨叫行為必須實(shí)現(xiàn)的接口
*/
public interface QuackBehavior
{
public void quack();
}
那么鴨子父類就變成:
public abstract class Duck
{
/**
* 所有的鴨子都會(huì)有外觀此蜈,只是每只鴨子的外觀都可能不一樣
*/
public abstract void display();
public void swim()
{
System.out.println("鴨子會(huì)游泳");
}
}
而需要叫聲或者飛行行為的鴨子之類即横,就可以通過(guò)實(shí)現(xiàn)接口來(lái)完成,然后實(shí)現(xiàn)自己的飛和叫的行為裆赵,不管是橫著飛豎著飛东囚,你自己都可以決定。
但是也會(huì)導(dǎo)致以下問(wèn)題:
- 如果需要修改行為战授,那么就要具體到每個(gè)鴨子子類的源碼页藻,一不小心可能就會(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ì)超類型編程落恼,這里的接口不一定是語(yǔ)法意義上的接口箩退。也就是利用多態(tài)。
OK ?佳谦,現(xiàn)在可以將飛和叫聲這兩個(gè)會(huì)變化的獨(dú)立出來(lái)了
我們可以將飛這個(gè)行為歸為一組戴涝,將叫聲這個(gè)行為歸為一組。它們將和鴨子類完全隔離钻蔑。
/**
* 飛行行為的實(shí)現(xiàn)類啥刻,給不會(huì)飛的鴨子用
*/
public class FlyNoWay implements FlyBehavior
{
@Override
public void fly()
{
System.out.println("我不會(huì)飛");
}
}
/**
* 飛行實(shí)現(xiàn)類,用火箭來(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)類可帽,給會(huì)吱吱叫的鴨子用
*/
public class Squeak implements QuackBehavior
{
@Override
public void quack()
{
System.out.println("我會(huì)吱吱叫");
}
}
/**
* 叫聲實(shí)現(xiàn)類,給不會(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ā)行為扬虚。新的鴨子類如下:
/**
* 鴨子父類
*/
public abstract class Duck
{
// 所有鴨子子類都實(shí)現(xiàn)這兩個(gè)接口類型
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
/**
* 所有的鴨子都會(huì)有外觀努隙,只是每只鴨子的外觀都可能不一樣
*/
public abstract void display();
// 委托給飛行行為類
public void performFly()
{
flyBehavior.fly();
}
// 委托給鴨叫聲類
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)建你想要的鴨子了。
/**
* 綠頭鴨類辜昵,一繼承了Duck類荸镊,從一開(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è)定鴨子的行為了宛逗,如果綁定在鴨子類中,就無(wú)法這樣做
modelDuck.performFly();
}
設(shè)計(jì)原則 3
多用組合钦椭,少用繼承拧额。
上面可以看到的是碑诉,鴨子的行為并不是通過(guò)繼承而來(lái)的彪腔,而是通過(guò)組合而來(lái)的。
鴨子的飛行行為可以看做是一族算法进栽,叫聲行為也可以看做是一族算法德挣,在一族算法內(nèi),族內(nèi)的行為是可以互相替換的快毛,比如一族飛行算法格嗅,橫著飛和豎著飛是可以互相替換的。
策略模式的定義:
定義了算法族唠帝,分別封裝起來(lái)屯掖,讓他們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶襟衰。