程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 讀headFirst設計模式,headfirst設計模式

讀headFirst設計模式,headfirst設計模式

編輯:JAVA綜合教程

讀headFirst設計模式,headfirst設計模式


有些人已經解決你的問題了

什麼是設計模式?我們為什麼要使用設計模式?怎樣使用?按照書上的說法和我自己的理解,我認為是這樣的:我們遇到的問題其他開發人員也遇到過,他們利用他們的智慧和經驗將問題解決了,把他們解決問題的方法提煉總結出來使其能解決其他同類問題。使用設計模式是為了更方便快捷的解決問題。把模式裝進腦子裡,然後在你的設計和已有的應用中,尋找何處可以使用這些模式,以往是代碼復用,現在是經驗復用。

先把書上的例子過一遍,簡單的鴨子模擬應用

Joe所在的公司決定開發一款模擬鴨子的應用。系統中有各種鴨子,鴨子可以游泳,可以呱呱叫,各種鴨子有不同的外觀。此系統建立了一個鴨子的基類,其中有游泳的方法swim,有呱呱叫的方法quack,有顯示鴨子外貌的方法display。每種鴨子的外貌不同,必須在其子類中重寫display方法。就像這樣:

由於競爭加劇,公司決定搞定不一樣的東西:“嘿,Joe,我想鴨子應該能飛!”,“嗯, 這個聽起來很容易”,在Duck中加一個fly方法就行了。過了幾天,公司開會,“Joe,怎麼會有一只橡皮鴨(RubberDuck,不會飛,吱吱叫)在屏幕裡面飛來飛去?”,好吧,這是Joe疏忽了,只有真正的鴨子能飛,橡皮鴨會叫,會游泳但是不會飛,馬上修復(覆蓋RubberDuck中的fly方法, 讓它什麼也不做)。但是如果我需要一只誘餌鴨(DecoyDuck,不會飛也不會叫)呢,也在子類中重寫quack和fly方法?Joe還收到通知,此系統還會不定時更新,至於怎麼更新還沒有想到,於是Joe意識到繼承不是一個好方法,因為每添加一個鴨子的子類,他就要被迫檢查該子類的quack和fly方法並可能需要重寫他們,如果直接修改父類中的方法,但有些子類並不想修改,那麼這些子類就都要重寫這些方法。

 

繼承所采用的代碼

Duck

public abstract class Duck {
    public void quack(){
        System.out.println("呱呱叫");
    }
    
    public void swim(){
        System.out.println("游泳");
    }
    
    //每個鴨子的外觀不同, 在子類中實現
    public abstract void display();
    
    //鴨子飛行的方法
    public void fly(){
        System.out.println("飛行");
    }
}
View Code

MallardDuck

/**
 * 外觀是綠色的鴨子
 */
public class MallardDuck extends Duck {

    @Override
    public void display() {
        System.out.println("綠頭鴨");
    }

}
View Code

RubberDuck

/**
 * 橡皮鴨
 * 橡皮鴨不會呱呱叫(quack), 而是吱吱叫(squeak)
 */
public class RubberDuck extends Duck {

    @Override
    public void display() {
        System.out.println("可愛的黃色橡皮鴨");
    }

    //橡皮鴨不會呱呱叫(quack), 而是吱吱叫(squeak)
    @Override
    public void quack() {
        System.out.println("橡皮鴨吱吱叫");
    }
    
    //橡皮鴨不會飛
    @Override
    public void fly() {
    }
}
View Code

DecoyDuck

/**
 * 誘餌鴨, 不會飛也不會叫
 */
public class DecoyDuck extends Duck {

    @Override
    public void display() {
        System.out.println("誘餌鴨");
    }
    
    @Override
    public void quack() {
        System.out.println("什麼都不會做, 不會叫");
    }
    
    @Override
    public void fly() {
        System.out.println("什麼都不做, 不會飛");
    }
}
View Code

 

采用接口呢

將行為抽離出來,比如將fly方法抽離出來放到Flyable接口中,只有會飛的Duck的子類才實現該接口,同樣的也可以將quack方法抽離到Quackable接口中。就像這樣:

這樣解決了一部分問題,至少橡皮鴨不會到處飛了。但是你有沒有想過另外的一個問題,就是這樣代碼無法復用,因為接口中的方法只能是抽象的,這樣導致在每個子類中都需要重寫方法。這樣無疑是從一個噩夢跳入了另一個噩夢。

 

變化與不變分離

好吧,在軟件開發上,有什麼是你深信不疑的,那就是——change,不變的是變化。

雖然使用接口也不妥,但思想是值得借鑒的: 把變化的和不變的離,那哪些是變化的呢?“真正的”鴨子會游泳swim,會呱呱叫quack,會飛fly,而橡皮鴨會游泳swim,會吱吱叫squeak,不會飛。所以說叫可以是呱呱叫,可以是吱吱叫,也可以不叫,飛可以沿直線飛,可以沿曲線飛,也可以不會飛,所以叫和飛是變化的。游泳都是在水面上無規則的游泳,可以看作是不變的,當然,你要認為游泳有多種方式,也可以認為它是變化的,我在這裡把游泳認為是不變的,所以說判斷變化的和不變的要根據實際情況來定。

 

軟件設計原則: 變化與不變分離

找出應用中可能會變化的部分,把它們獨立出來,不要把他們和那些不變的混在一起。即把應用中可能會變化的抽離出來並封裝起來。

 

繼續設計

基於變化和不變分離的原則,我們可以把變化的(飛和叫)抽離出來並封裝起來。比如將飛行的行為抽離出來設計成一個接口FlyBehavior,將叫的行為設計成接口QuackBehavior,然後具體的行為實現接口並重寫方法,如:

 

 

針對接口編程

我們把鴨子的行為從鴨子類Duck中分離出來,和以往不同,以往的做法是:行為是從Duck中的具體實現繼承過來,或是實現接口重寫方法而來,這兩種方式都依賴實現,我們被實現綁得死死的。在我們新的設計中,我們把行為分離出來,所以行為不會綁死在鴨子的子類中,換句話說現實行為的代碼位於特定類QuackBehavior和FlyBehavior中,可以在運行時動態的改變行為。這就是針對接口編程的一種體現,針對接口編程指的是針對超類型編程,不一定是interface,可以是abstract class。

 

軟件設計原則:針對接口編程

針對接口編程,而不是針對實現編程

 

整合實現代碼

首先是兩個行為接口QuackBehavior和FlyBehavior

QuackBehavior

public interface QuackBehavior {
    void quack();
}
View Code

 

FlyBehavior

/**
 * 飛行行為接口
 */
public interface FlyBehavior {
    void fly();
}
View Code

 

QuackBehavior具體行為實現,呱呱叫Quack,吱吱叫Squeak,不會叫MuteQuack

Quack

/**
 * 呱呱叫
 */
public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}
View Code

 

Squeak

/**
 * 吱吱叫
 */
public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}
View Code

 

MuteQuack

/**
 * 什麼都不做, 不會叫
 */
public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("什麼都不做, 不會叫");
    }
}
View Code

 

FlyBehavior的具體實現,用翅膀飛FlyWithWings,不會飛FlyNoWay

FlyWithWings

/**
 * 用翅膀飛
 */
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("用翅膀飛行");
    }
}
View Code

 

FlyNoWay

/**
 * 不會飛
 */
public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("什麼都不做, 不能飛");
    }
}
View Code

 

然後是基類Duck,將行為接口當做實例變量放入Duck中,需要在運行時動態的改變行為,可以提供setter方法

Duck

public abstract class Duck {
    //針對接口編程的體現
    private FlyBehavior flyBehavior;
    private QuackBehavior quackBehavior;
    
    //設置一個默認的行為
    public Duck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }

    public void swim(){
        System.out.println("游泳");
    }
    
    //每個鴨子的外觀不同, 在子類中實現
    public abstract void display();

    //執行呱呱叫的方法
    public void performQuack(){
        quackBehavior.quack();
    }
    
    //執行飛行的方法
    public void performFly(){
        flyBehavior.fly();
    }


    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}
View Code

注意,抽象類也是有構造方法的,不過不能創建對象,抽象類的構造方法是用來初始化變量的

 

兩個子類MallardDuck和RubberDuck

MallardDuck

/**
 * 外觀是綠色的鴨子
 */
public class MallardDuck extends Duck {
    
    @Override
    public void display() {
        System.out.println("綠頭鴨");
    }
    
    @Test
    public void test1() throws Exception {
        MallardDuck mallardDuck = new MallardDuck();
        mallardDuck.performQuack();
        mallardDuck.performFly();
        display();
    }
}
View Code

 

RubberDuck

/**
 * 橡皮鴨
 * 橡皮鴨不會呱呱叫(quack), 而是吱吱叫(squeak)
 */
public class RubberDuck extends Duck {
    @Override
    public void display() {
        System.out.println("可愛的黃色橡皮鴨");
    }
    
    @Test
    public void test1() throws Exception {
        Duck rubberDuck = new RubberDuck();
        //運行時動態改變行為
        rubberDuck.setFlyBehavior(new FlyNoWay());
        rubberDuck.setQuackBehavior(new Squeak());
        rubberDuck.performFly();
        rubberDuck.performQuack();
        rubberDuck.display();
    }
}
View Code

 

封裝行為

好了,我們來看看整體的格局:飛行行為實現了FlyBehavior接口,呱呱叫行為實現了QuackBehavior接口,也請注意,我們描述事務的方式也有所改變,我們把行為看成一組算法,飛行行為是算法,呱呱叫行為也是算法,就像這樣:

 

我們把QuackBehavior和FlyBehavior類型的變量放到了Duck中,這其實就用到了組合。用組合創建的系統更具有彈性,不會如繼承一般一處改可能需要多處改

 

軟件設計原則:

多用組合,少用繼承

 

策略模式

好了,到這裡我們終於學到了第一個模式:策略模式(strategy pattern),介紹一下策略模式的概念

定義了算法族,並分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved