假如我們現(xiàn)在有一個(gè)鴨子粉臊,鴨子會(huì)呱呱叫草添,也會(huì)游泳,但是每個(gè)鴨子的外觀不相同(有白顏色的维费,有綠色的)果元,那么你會(huì)怎么設(shè)計(jì)這個(gè)鴨子呢?
我們第一肯定是想到設(shè)計(jì)一個(gè)鴨子超類(lèi)犀盟,這個(gè)超類(lèi)包括swim()和quack()兩個(gè)方法而晒,還有一個(gè)抽象的dispaly()方法。
public abstract class Duck {
public void swim(){
System.out.println("I can swimming");
}
public void quack(){
System.out.println("quack quack");
}
public abstract void display();
}
白色的鴨子繼承Duck類(lèi)阅畴,并實(shí)現(xiàn)display()方法
public class WhiteDuck extends Duck {
public void display() {
System.out.println("I am a white duck!");
}
}
綠色鴨子也繼承Duck類(lèi)倡怎,實(shí)現(xiàn)自己的display()方法
public class GreenDuck extends Duck {
public void display() {
System.out.println("I am a green duck!");
}
}
如果這個(gè)時(shí)候我們加了一個(gè)需求,要求要鴨子會(huì)飛贱枣,那么你又會(huì)怎么設(shè)計(jì)呢监署?你是不是首先想到在Duck類(lèi)上加上一個(gè)fly()方法,像下面這樣
public abstract class Duck {
public void swim(){
System.out.println("I am swimming");
}
public void quack(){
System.out.println("quack quack");
}
public abstract void display();
public void fly(){
System.out.println("I can fly");
}
}
但是如果并不是所有的鴨子都會(huì)飛纽哥,比如橡皮鴨子不會(huì)飛钠乏。這時(shí)候你會(huì)想是不是想可以在橡皮鴨中覆蓋掉fly()方法,讓fly()方法啥也不做春塌,像下面這樣
public class RubberDuck extends Duck{
public void display() {
System.out.println("I am a rubber duck");
}
@Override
public void fly() {
}
}
雖然上面這個(gè)方法可以暫時(shí)解決這個(gè)問(wèn)題晓避,但是如果這個(gè)時(shí)候加入了一個(gè)木頭鴨子簇捍,它既不會(huì)呱呱叫,也不會(huì)飛俏拱,那這個(gè)時(shí)候你是不是就會(huì)想到在木頭鴨子里覆蓋掉fly()方法和quack()方法暑塑。但這樣帶來(lái)的問(wèn)題就是如果有成千上百個(gè)鴨子,每次都要檢查quack()方法和fly()方法锅必,這簡(jiǎn)直是無(wú)窮無(wú)盡的噩夢(mèng)事格。
那如果把fly()方法和quack()方法抽出來(lái)呢,放到一個(gè)Flyable接口和一個(gè)Quackable接口當(dāng)中搞隐。讓會(huì)飛的鴨子實(shí)現(xiàn)Flyable接口驹愚,會(huì)呱呱叫的鴨子實(shí)現(xiàn)Quackable()接口。
Flyable接口
public interface Flyable {
void fly();
}
WhiteDuck類(lèi)
public class WhiteDuck extends Duck implements Flyable{
public void display() {
System.out.println("I am a white duck");
}
public void fly() {
System.out.println("I am fly with wing");
}
}
假如現(xiàn)在有個(gè)火箭鴨尔许,它能以火箭的動(dòng)力飛行么鹤,我們可以這樣
public class RocketDuck extends Duck implements Flyable{
public void fly() {
System.out.println("I can fly with rocket");
}
public void display() {
System.out.println("I am a rocket duck");
}
}
這樣看好像沒(méi)什么問(wèn)題终娃,每個(gè)種類(lèi)的鴨子都可以選擇性的實(shí)現(xiàn)自己想實(shí)現(xiàn)的接口味廊。但是你忽視了一個(gè)非常大的問(wèn)題,假如鴨子一共就有三種飛行方法(用翅膀飛棠耕、不會(huì)飛余佛、以火箭動(dòng)力去飛),這個(gè)時(shí)候如果你有幾十種鴨子的話窍荧,就會(huì)造成大量的代碼冗余辉巡,如果有的鴨子要修改一下飛行行為,就要對(duì)這些鴨子的fly()方法逐一的修改蕊退。
那么我們?cè)賮?lái)看看這種情況策略模式怎么去做的呢郊楣?
策略模式會(huì)將代碼中變化的部分抽取出來(lái)封裝(比如fly,quack),以便以后可以輕易的改動(dòng)或擴(kuò)充此部分瓤荔,而不影響不需要變化的其他部分净蚤。其實(shí)這種思想是每個(gè)設(shè)計(jì)模式背后的精神所在。所有的設(shè)計(jì)模式都提供了一套方法讓“系統(tǒng)中的某部分改變不會(huì)影響其他部分”输硝。
我們利用接口代表每個(gè)行為今瀑,比方說(shuō),F(xiàn)lyBehavior與QuackBehavior点把,而行為的每個(gè)實(shí)現(xiàn)都將實(shí)現(xiàn)其中的一個(gè)接口橘荠。所以這次鴨子類(lèi)不會(huì)負(fù)責(zé)實(shí)現(xiàn)Flyable與Quackable接口,反而是由我們制造的一組其他類(lèi)專(zhuān)門(mén)負(fù)責(zé)實(shí)現(xiàn)FlyBehavior與QuackBehavior郎逃,這種就稱(chēng)為“行為類(lèi)”哥童。所以實(shí)際的行為實(shí)現(xiàn)不會(huì)綁死在鴨子的子類(lèi)當(dāng)中。
FlyBehavior接口
public interface FlyBehavior {
void fly();
}
FlyWithWing類(lèi)褒翰,用翅膀飛
public class FlyWithWing implements FlyBehavior {
public void fly() {
System.out.println("I can fly with wing");
}
}
FlyWithRocket類(lèi)贮懈,以火箭的動(dòng)力飛
public class FlyWithRocket implements FlyBehavior {
public void fly() {
System.out.println("I can fly with rocket");
}
}
FlyNoWay類(lèi)压恒,不會(huì)飛
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can not fly");
}
}
QuackBehavior接口同理,這里就不貼代碼了错邦。
我們看到這樣的設(shè)計(jì)可以使相同的行為代碼能被復(fù)用探赫,即使我們?cè)傩略鲆恍┬袨橐膊粫?huì)影響既有的行為類(lèi),也不會(huì)影響到使用飛行行為的鴨子類(lèi)撬呢。
那么我們?cè)鯓訉⑿袨轭?lèi)和和鴨子類(lèi)進(jìn)行整合呢伦吠?
首先,我們?cè)貲uck類(lèi)中加入兩個(gè)變量魂拦,分別為"flyBehavior"與"quackBehavior"毛仪,申明為接口類(lèi)型而不是具體的實(shí)現(xiàn),每個(gè)鴨子都會(huì)動(dòng)態(tài)的設(shè)置這些變量以在運(yùn)行時(shí)引用正確的行為類(lèi)型芯勘。我們?cè)僖詢(xún)蓚€(gè)相似的方法performFly()和performQuack()取代Duck類(lèi)中的fly()方法和quack()方法箱靴。
改過(guò)之后的Duck類(lèi)
public abstract class Duck {
FlyBehavior flyBehavior;
public void performFly(){
flyBehavior.fly();
}
public abstract void display();
}
我們看到要在Duck類(lèi)中,我們要實(shí)現(xiàn)飛行的行為荷愕,只需要flyBehavior去飛就行了衡怀,在Duck類(lèi)中,我們不在乎flyBehavior接口的對(duì)象到底是什么安疗,我們只要關(guān)心該對(duì)象如何進(jìn)行飛行就行抛杨。
接下來(lái)我們通過(guò)RocketDuck看一下我們?cè)鯓尤ピO(shè)置flyBehavior變量
public class RocketDuck extends Duck {
public RocketDuck() {
flyBehavior = new FlyWithRocket();
}
public void display() {
System.out.println("I am a rocket duck");
}
}
我們看到,RocketDuck類(lèi)中的默認(rèn)構(gòu)造方法會(huì)設(shè)置flyBehavior變量為一個(gè)FlyWithRocket對(duì)象荐类,當(dāng)調(diào)用performFly()方法時(shí)就會(huì)去調(diào)用FlyWithRocket對(duì)象的fly()方法怖现。我們寫(xiě)一個(gè)Main方法去執(zhí)行看一下:
public class Main {
public static void main(String[] args) {
Duck duck = new RocketDuck();
duck.performFly();
}
}
輸出:
I can fly with rocket
我們還可以在Duck類(lèi)中通過(guò)setter方法類(lèi)設(shè)定鴨子的行為類(lèi),以達(dá)到隨時(shí)改變鴨子行為的效果玉罐。
public abstract class Duck {
FlyBehavior flyBehavior;
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void performFly(){
flyBehavior.fly();
}
public abstract void display();
}
假設(shè)現(xiàn)在有一個(gè)模型鴨ModelDuck屈嗤,我們就可以這樣
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyWithWing();
}
public void display() {
System.out.println("I am a model duck");
}
}
再運(yùn)行看一下:
public class Main {
public static void main(String[] args) {
Duck duck = new ModelDuck();
duck.performFly();
duck.setFlyBehavior(new FlyNoWay());
duck.performFly();
}
}
輸出:
I can fly with wing
I can not fly
我們能看到通過(guò)setter方法可以動(dòng)態(tài)的區(qū)改變鴨子的行為。
那么假設(shè)現(xiàn)在我們的鴨子會(huì)講話吊输,但是每種鴨子會(huì)講的語(yǔ)言不一樣饶号,有的會(huì)講英文,有的會(huì)講中文璧亚,還有的會(huì)講漢語(yǔ)讨韭,這個(gè)時(shí)候我們應(yīng)該怎么做呢?
首先我們可以寫(xiě)一個(gè)SpeakBehavior接口癣蟋,它有一個(gè)speak()方法透硝。
public interface SpeakBehavior {
void speak();
}
原后會(huì)講漢語(yǔ)的鴨子和會(huì)講英語(yǔ)的鴨子分別實(shí)現(xiàn)SpeakBehavior接口
public class SpeakChinese implements SpeakBehavior {
public void speak() {
System.out.println("I can speak chinese");
}
}
public class SpeakEnglish implements SpeakBehavior {
public void speak() {
System.out.println("I can speak english");
}
}
原后在Duck類(lèi)中加入speakBehavior變量
public abstract class Duck {
FlyBehavior flyBehavior;
SpeakBehavior speakBehavior;
public void setSpeakBehavior(SpeakBehavior speakBehavior) {
this.speakBehavior = speakBehavior;
}
public void performBehavior(){
speakBehavior.speak();
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void performFly(){
flyBehavior.fly();
}
public abstract void display();
}
在RocketDuck類(lèi)中添加說(shuō)話的行為
public class RocketDuck extends Duck {
public RocketDuck() {
flyBehavior = new FlyWithRocket();
speakBehavior = new SpeakChinese();
}
public void display() {
System.out.println("I am a rocket duck");
}
}
我們運(yùn)行看一下
public class Main {
public static void main(String[] args) {
Duck duck = new RocketDuck();
duck.performFly();
duck.performSpeak();
duck.setFlyBehavior(new FlyNoWay());
duck.performFly();
duck.setSpeakBehavior(new SpeakEnglish());
duck.performSpeak();
}
}
輸出:
I can fly with rocket
I can speak chinese
I can not fly
I can speak english
我們看到新加的行為不會(huì)影響到老的行為的運(yùn)行。
總結(jié)
每一個(gè)鴨子都有一個(gè)FlyBehavior和一個(gè)SpeakBehavior疯搅,好將飛行和講話委托給他們處理濒生。當(dāng)你將兩個(gè)類(lèi)結(jié)合起來(lái)使用,如同本例一般幔欧,這就是組合(composition)罪治。這種做法和“繼承”不同的地方在于丽声,鴨子的行為不是繼承來(lái)的,而是和適當(dāng)?shù)男袨閷?duì)象“組合”來(lái)的觉义。這是一個(gè)很重要的技巧雁社。其實(shí)是使用了我們的第三個(gè)設(shè)計(jì)原則:
多用組合,少用繼承