Java設計模式之鴨子模式

這周個人大學老師在給咱們講UML建模時,說到了一個鴨子的設計模式,我以爲上課太快了,感受沒聽懂,便在網上看到一個大神作出以下很詳細的解釋,我以爲很是適合剛入門的同窗一塊兒學習!html

列出以下:java

假設咱們須要設計出各類各樣的鴨子,一邊游泳戲水, 一邊呱呱叫。很明顯這時咱們須要設計了一個鴨子超類(Superclass),並讓各類鴨子繼承此超類。

public abstract class Duck {
public void Swim() {
//會游泳
}
public abstract display();//各類外觀不同,因此爲抽象
public void Quack() {
//會叫
}
}
每一隻鴨子就繼承Duck類
public class MallardDuck extends Duck {
public void display() {
// 外觀是綠色的
}
}
public class RedheadDuck extends Duck{
public void display(){
// 外觀是紅色的
}
}
好了,咱們完成這些後,可是發現咱們須要有一些鴨子是會飛的,應該怎麼修改呢?
也許你要說這很簡單,在Duck類裏面直接加入一個fly()方法,不就能夠了。

public abstract class Duck {
public void Swim() {
//會游泳
}
public abstract display();//各類外觀不同,因此爲抽象
public void Quack() {
//會叫
}
public void fly(){
//會飛
}
}
這時你會發現全部的鴨子都變成了會飛的,很明顯這是不對了,例如橡皮鴨顯然就不是了。
你也許想到了另外一種方法,在會飛的鴨子類裏才添加該方法不就能夠了嘛,

public class MallardDuck extend Duck{
public void display(){
// 外觀是綠色的
}
public void fly(){
//會飛
}
}
這個方法看起來是很不錯,但是有不少種鴨子都會飛的時候,代碼的複用性很明顯是不夠好的,你不得不在
每個會飛的鴨子類裏去寫上同一個fly()方法,這可不是個好主意.
可能你又想到另外一個方法:採用繼承和覆蓋,在Duck類裏實現fly()方法,在子類裏若是不會飛的就覆蓋它

public abstract class Duck {
public void Swim() {
//會游泳
}
public abstract display();//各類外觀不同,因此爲抽象
public void Quack(){
//會叫
}
public void fly(){
//會飛
}
}
//橡皮鴨吱吱叫,不會飛
public class RubberDuck extend Duck{
public void quack(){
//覆蓋成吱吱叫
}
public void display{
//外觀是橡皮鴨
}
public void fly{
//什麼也不作
}
}

這樣咱們真實現了確實能飛的鴨子才能夠飛起來了,看起來主意不錯!問題到這兒彷佛獲得瞭解決
但咱們如今有了一種新的鴨子,誘鉺鴨(不會飛也不會叫),看來須要這樣來寫
public class DecoyDuck extend Duck{
public void quack(){
//覆蓋,變成什麼也不作
}
public void display(){
//誘餌鴨
}
public void fly(){
//覆蓋,變成什麼也不作
}
}

每當有新的鴨子子類出現或者鴨子新的特性出現,就不得不被迫在Duck類裏添加並在全部子類裏檢查可能須要覆蓋fly()和quark()...這簡直是無窮盡的惡夢。因此,咱們須要一個更清晰的方法,讓某些(而不是所有)鴨子類型可飛或可叫。讓鴨子的特性能有更好的擴展性。
用一下接口的方式把fly()取出來,放進一個Flyable接口中,這樣只有會飛的鴨子才實現這個接口,固然咱們也能夠照此來設計一個Quackbable接口,由於不是全部的鴨子都會叫,也只讓會叫的鴨子纔去實現這個接口.
但這個方法和上面提到的在子類裏去實現fly同樣笨,若是幾十種均可以飛,你得在幾十個鴨子裏去寫上同樣的fly(),若是一旦這個fly有所變動,你將不得不找到這幾十個鴨子去一個一個改它們的fly()方法。
由於改變鴨子的行爲會影響全部種類的鴨子,而這並不恰當。Flyable與Quackable接口一開始彷佛還挺不錯, 解決了問題( 只有會飛的鴨子才繼承Flyable) , 可是Java的接口不具備實現代碼, 因此繼承接口沒法達到代碼的複用。這意味着:不管什麼時候你須要修改某個行爲,你必須得往下追蹤並修改每個定義此行爲的類。
策略模式的第一原則:找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼混在一塊兒。 好吧,回頭看一下這個Duck類,就咱們目前所知,除了fly()和quack()的問題以外,Duck類還算一切正常,主要是鴨子的行爲老是可能變化的,讓咱們頭痛就在於這些行爲的變化,那咱們就把這些行爲獨立出來。
爲了要把這兩個行爲從Duck 類中分開, 咱們將把它們自Duck 類中取出,創建一組新類表明每一個行爲。咱們創建兩組類(徹底遠離Duck類),一個是「fly」相關的,一個是「quack」相關的,每一組類將實現各自 的動做。比方說,咱們可能有一個類實現「呱呱叫」,另外一個類實現「吱吱叫」,另外一個類實現「安靜」。咱們利用接口表明每組行爲,比方說, FlyBehavior來表明飛的行爲,QuackBehavior表明叫的行爲,而讓每一種行爲具體類來實現該行爲接口。
在此,咱們有兩個接口,FlyBehavior和QuackBehavior,還有它們對應的類,負責實現具體的行爲:

public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior{
public void fly{}{
//實現鴨子飛行
}
}
public class FlyNoWay implements FlyBehavior{
public void fly{}{
//什麼也不作,不會飛
}
}
public interface QuackBehavior{
public void quack();
}
public class Quack implements QuackBehavior{
public void quack(){
//實現鴨子呱呱叫
}
}
public class Squeak implements QuackBehavior{
public void quack(){
//實現鴨子吱吱叫
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
//什麼也不作,不會叫
}
}

實際上這樣的設計,咱們已經可讓飛行和呱呱叫的動做被其餘的對象複用,由於這些行爲已經與鴨子類無關了。若是咱們新增一些行爲,也不會影響到既有的行爲類,也不會影響有已經使用到飛行行爲的鴨子類。
好了,咱們設計好鴨子的易於變化的行爲部分後,該到了整合鴨子行爲的時候了。
這時咱們該想到策略模式的另外一個原則了:
針對接口編程,而不是針對實現編程。
首先, 在鴨子中加入兩個實例變量,分別爲「flyBehavior」與「quackBehavior」,聲明爲接口類型( 而不是具體類實現類型), 每一個變量會利用多態的方式在運行時引用正確的行爲類型( 例如:FlyWithWings 、Squeak...等)。咱們也必須將Duck類與其全部子類中的fly()與quack()移除,由於這些行爲已經被搬移到FlyBehavior與 Quackehavior類中了,用performFly()和performQuack()取代Duck類中的fly()與quack()。

public abstract class Duck(){
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void swim(){
//會游泳
}
public abstract void display();//各類外觀不同,因此爲抽象
public void performQuack(){
quackBehavior.quack();
}
public void performFly(){
flyBehavior.fly();
}

}
很容易,是吧?想進行呱呱叫的動做,Duck對象只要叫quackBehavior對象
去呱呱叫就能夠了。在這部分的代碼中,咱們不在意QuackBehavior 接口的對象究竟是什麼,咱們只關心該對象
知道如何進行呱呱叫就夠了。
好吧! 如今來關心如何設定flyBehavior 與quackBehavior的實例變量
看看MallardDuck類:
public class MallardDuck extends Duck {
public MallardDuck() {
\\綠頭鴨使用Quack類處理呱呱叫,因此當performQuack() 被調用,就把責任委託給Quack對象進行真正的呱呱叫。
quackBehavior = new Quack();
\\使用FlyWithWings做爲其FlyBehavior類型。
flyBehavior = new FlyWithWings();
}
}
因此,綠頭鴨會真的『呱呱叫』,而不是『吱吱叫』,或『叫不出聲』。這是怎麼辦到的?當MallardDuck實例化時,它的構造器會
把繼承來的quackBehavior實例變量初始化成Quack類型的新實例(Quack是QuackBehavior的具體實現類)。一樣的處理方式也能夠用在飛行行爲上: MallardDuck 的構造器將flyBehavior 實例變量初始化成FlyWithWings 類型的實例(
FlyWithWings是FlyBehavior的具體實現類)。
輸入下面的Duck類(Duck.java) 以及MallardDuck 類MallardDuck.java),並編譯之。
public abstract class Duck {

//爲行爲接口類型聲明兩個引用變量, 全部鴨子子類(在同一個packge)都繼承它們。
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void performFly() {
flyBehavior.fly();//委託給行爲類
}
public void performQuack() {
quackBehavior.quack();//委託給行爲類
}
public void swim() {
System.out.println("All ducksfloat, even decoys!");
}
}

public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = newQuack();
flyBehavior = newFlyWithWings();
}
public void display() {
System.out.println("I’m a real Mallard duck");
}
}
輸入FlyBehavior接口(FlyBehavior.java)與兩個行爲實現類(FlyWithWings.java與FlyNoWay.java),並編譯之。
public interface FlyBehavior {//全部飛行行爲類必須實現的接口
public void fly();
}
public class FlyWithWings implements FlyBehavior {//這是飛行行爲的實現, 給「真會」飛的鴨子用 .. .
public void fly() {
System.out.println("I’m flying!!");
}
}
public class FlyNoWay implements FlyBehavior {//這是飛行行爲的實現, 給「不會」飛的鴨子用( 包括橡皮鴨和誘餌鴨)
public void fly() {
System.out.println("I can’t fly");
}
}

輸入QuackBehavior接口(QuackBehavior.java)及其三個實現類(Quack.java、MuteQuack.java、Squeak.java),並編譯之。
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println(「Quack」);
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println(「<< Silence >>」);
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println(「Squeak」);
}
}
輸入並編譯測試類(MiniDuckSimulator.java)

public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.display();
//這會調用MallardDuck繼承來的performQuack() ,進而委託給該對象的QuackBehavior對象處理。(也就是說,調用繼承來的quackBehavior的quack())
mallard.performQuack();
//至於performFly() ,也是同樣的道理。
mallard.performFly();
}
}
運行結果:
I’m a real Mallard duck
Quack
I’m flying!!

雖然咱們把行爲設定成具體的類(經過實例化相似Quack 或FlyWithWings的行爲類, 並指定到行爲引
用變量中),可是仍是能夠在運行時輕易地改變該行爲。
因此,目前的做法仍是頗有彈性的,只是初始化實例變量的做法不夠彈性罷了。
咱們但願一切能有彈性,畢竟,正是由於一開始的設計的鴨子行爲沒有彈性,才讓咱們走到如今這條路。
咱們還想可以「指定」行爲到鴨子的實例, 比方說, 想要產生綠頭鴨實例,並指定特定「類型」的飛行
行爲給它。乾脆順便讓鴨子的行爲能夠動態地改變好了。換句話說,咱們應該在鴨子類中包含設定行爲的方法。
由於quackBehavior實例變量是一個接口類型,因此咱們是可以在運行時,透過多態動態地指定不一樣的QuickBehavior實現類給它。
咱們在鴨子子類透過設定方法(settermethod)設定鴨子的行爲,而不是在鴨子的構造器內實例化。
在Duck類中,加入兩個新方法:今後之後,咱們能夠「隨時」調用這兩個方法改變鴨子的行爲。

public strate class Duck(){
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}

}
好了,讓咱們再製造一個新的鴨子類型:模型鴨(ModelDuck.java)
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();//初始狀態,咱們的模型鴨是不會飛的。
quackBehavior = new Quack();//初始狀態,咱們的模型鴨是能夠叫的.
}
public void display() {
System.out.println("I’m a modelduck");
}
}
創建一個新的FlyBehavior類型(FlyRocketPowered.java)
public class FlyRocketPowered implements FlyBehavior {
// 咱們創建一個利用火箭動力的飛行行爲。
public void fly() {
System.out.println("I’m flying with arocket!");
}
}

改變測試類(MiniDuckSimulator.java),加上模型鴨,並使模型鴨具備火箭動力。
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
Duck model = new ModelDuck();
//第一次調用performFly() 會被委託給flyBehavior對象(也就是FlyNoWay對象),該對象是在模型鴨構造器中設置的。
model.performFly();
//這會調用繼承來的setter 方法,把火箭動力飛行的行爲設定到模型鴨中。哇咧! 模型鴨忽然具備火箭動力飛行能力。
model.setFlyBehavior(new FlyRocketPowered());
//若是成功了, 就意味着模型鴨動態地改變行爲。若是把行爲的實現綁死在鴨子類中, 可就沒法作到這樣。
model.performFly();
}
}
運行一下,看下結果
I’m a real Mallard duck
Quack
I’m flying!!
I’m a model duck
I can’t fly
I’m flying with a rocket!
如同本例通常,當你將兩個類結合起來使用,這就是組合(composition)。這種做法和『繼承』不一樣的地方在於,
鴨子的行爲不是繼承而來,而是和適當的行爲對象『組合』而來。
這是一個很重要的技巧。實際上是使用了策略模式中的第三個設計原則, 多用組合,少用繼承。
如今來總結一下,鴨子的行爲被放在分開的類中,此類專門提供某行爲的實現。
這樣,鴨子類就再也不須要知道行爲的實現細節。
鴨子類不會負責實現Flyable與Quackable接口,反而是由其餘類專門實現FlyBehavior與QuackBehavior,
這就稱爲「行爲」類。由行爲類實現行爲接口,而不是由Duck類實現行爲接口。
這樣的做法迥異於以往,行爲再也不是由繼承Duck超類的具體實現而來, 或是繼承某個接口並由子類自行實現而來。
(這兩種做法都是依賴於「實現」, 咱們被實現綁得死死的, 沒辦法更改行爲,除非寫更多代碼)。
在咱們的新設計中, 鴨子的子類使用接口( FlyBehavior與QuackBehavior)所表示的行爲,因此實際的實現不會被
綁死在鴨子的子類中。( 換句話說, 特定的實現代碼位於實現FlyBehavior與QuakcBehavior的特定類中),這樣咱們就得到了更大的靈活性和可擴展性。地址:https://zhidao.baidu.com/question/1447672451450665060.html
相關文章
相關標籤/搜索