1. 程式人生 > >java編程思想第四版第九章總結

java編程思想第四版第九章總結

concrete 目標 things charge mobile text 描述 pre microsoft

1. 策略設計模式

  • 參考這篇文章:http://blog.csdn.net/chenjie19891104/article/details/6396458 講的很清楚,策略設計模式。並且舉了一個例子,很具有代表性。
  • 先簡單了解一下:

  技術分享圖片

    技術分享圖片

    和模板方法模式的區別:

    技術分享圖片

    文章裏還有一個例子:

    技術分享圖片

    

  技術分享圖片

    備註:我來分解,解釋一下這個例子。

    將共同的方法定義成了一個接口,在這個接口中並沒有這個共同方法的實現。

    在Strategy類中,定義了一個方法execute,它的參數是擁有共同方法的接口類。

    用戶Context在調用Strategy的execute方法時,在定義這個共同的方法。 這樣就大大提高了靈活性。因為公共方法也是可以後定義的。而不是在創建類的時候就定義好了。

    下面把這個例子敲成代碼

package net.mindview.interfaces;

//公共方法回調類
interface SameCallback{
    void doTheSame();
}

//定義了一個策略類
interface Strategy {
    void execute(SameCallback sc);
}

//定義策略實現類
class concreteStrategy1 implements Strategy {
    @Override
    public void execute(SameCallback sc) {
        sc.doTheSame();
    }
}

class concreteStrategy2 implements Strategy { @Override public void execute(SameCallback sc) { sc.doTheSame(); } } class concreteStrategy3 implements Strategy { @Override public void execute(SameCallback sc) { sc.doTheSame(); } } public class Context {
public static void main(String[] args) { Strategy strategy = new concreteStrategy1(); //這個公共方法類 SameCallback sc = new SameCallback() { @Override public void doTheSame() { System.out.println("do same things"); } }; //那個策略需要用到公共方法類,調用即可,如果不用, 那就不在方法中小勇 strategy.execute(sc); strategy = new concreteStrategy2(); strategy.execute(sc); } }

      技術分享圖片

      技術分享圖片

      思考: 如果一段代碼中, 總會出現很多的if...else或者case,就要考慮使用策略設計模式了

  

2. 解耦

  • 看下面這兩個例子:
    
    
    案例一

    package net.mindview.interfaces.classprocessor; import java.math.BigInteger; import java.util.Arrays;
    /** * 處理器 */ class Processor { public String name(){ return getClass().getName(); }; //處理 Object process(Object input) { return input; } } /** * 大寫處理器 */ class UpCase extends Processor { @Override public Object process(Object input) { return ((String)input).toUpperCase(); } } /** * 小寫處理器 */ class DownCase extends Processor { @Override public Object process(Object input) { return ((String)input).toLowerCase(); } } /** * 分割數組處理器 */ class Splitter extends Processor { @Override public Object process(Object input) { return Arrays.toString(((String)input).toString().split(" ")); } } public class Apply { public static void process(Processor p, Object s){ System.out.println("Using Processor "+ p.name()); System.out.println(p.process(s)); } public static String s = "Disagreement with beliefs is by definition incorrect"; /** * 向本例這樣, 創建一個能夠根據所傳遞的參數對象的不同而具有不同行為的方法, * 被稱為策略設計模式 * @param args */ public static void main(String[] args) { BigInteger b = new BigInteger("111111111111111"); process(new UpCase(), s); process(new DownCase(), s); process(new Splitter(), s); } /** * 這個類使用到了一個設計模式: 叫做策略設計模式 * 所謂 的策略設計模式指的是: 能夠根據傳遞參數的不同而具有不同的行為的方法, 被稱為策略設計模式 * 這類方法包含了所有執行的算法中固定不變的部分(這裏是基類Processor,Object),而策略就是實際調用該方法時傳遞的實際參數, * 這部分參數是變化的,因此被稱之為是"策略"部分. * 在main中看到了三種不同類型的策略應用到了String類型s對象上。 */ }

    在這段代碼中,定義了一個處理器,這個處理器有兩個方法name()和process(). 然後定義了三個類, UpCase, DownCase,Spltter繼承自這個類. 當我們在Apply類中定義process(Process process, Object o)方法, 方法傳遞的參數是父類Process. 這樣不僅可以處理父類,還可以處理子類. 在main方法中就有體現. 這就是策略設計模式的思想。但是,他還有局限性。如果在main中想調用Apply.process()方法, 則必須傳遞Process類或者子類,不能傳遞其他類。 下面這個案例濾波器就說明了這一點:

    
    
    案例二


    package net.mindview.interfaces.filters;
    //波形 public class Waveform { //計數器,第幾波 private static long counter; private final long id = counter ++; //當前第幾波 @Override public String toString() { return "Waveform " + id; } } package net.mindview.interfaces.filters; //濾波器 public class Filter { public String name(){ return getClass().getSimpleName(); } //加工 public Waveform process(Waveform input){ return input; } } package net.mindview.interfaces.filters; /** * 低頻電磁波 */ public class LowPass extends Filter{ //切波的波長 double cutoff; public LowPass(double cutoff){ this.cutoff = cutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } } package net.mindview.interfaces.filters; /** * 高頻電磁波 */ public class HighPass extends Filter { //切波 double cutoff; public HighPass(double cutoff){ this.cutoff = cutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } } package net.mindview.interfaces.filters; /** * 段波電磁波 */ class BandPass extends Filter { //其實切波點, 最高且波點 double cutoff ,highCutoff; public BandPass(double cutoff, double highCutoff){ this.cutoff = cutoff; this.highCutoff = highCutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } }

    在上面這個類中,我們定義了一個Filter濾波器。這裏面有何Process中同名的兩個方法。這個時候,我想復用案例一的Apply類中的process()方法,可不可以呢?不可以。因為Filter不是Process的子類。雖然, process中的方法體調用完全適用於繼承Filter類的子類。為什麽不可以呢?Apply就是一個應用類。應用類要處理一些列的類。他可以堆字符串進行處理, 應該也可以對波進行處理呀。現在不可以,因為Process類是一個實體類,他和Apply類中的process方法緊密耦合。如果能夠實現解耦,那麽,就可以將Apply類更加泛化。這就是將Process作為接口來處理

    案例三

    package net.mindview.interfaces.classprocessor2; import java.math.BigInteger; import java.util.Arrays;
    /** * 處理器 */ interface Processor { public String name(); //處理 Object process(Object input); } /** * 處理器有一部分是所有處理器都是一樣的.有一部分是不同的. * 所以定義一個抽象類, 將共有方法定義為實體方法, 將各自實現的部分定義為抽象的 */ abstract class StringProcess implements Processor { @Override public String name() { return getClass().getSimpleName(); } public abstract Object process(Object input); } /** * 大寫處理器 */ class UpCase extends StringProcess { @Override public Object process(Object input) { return ((String)input).toUpperCase(); } } /** * 小寫處理器 */ class DownCase extends StringProcess { @Override public Object process(Object input) { return ((String)input).toLowerCase(); } } /** * 分割數組處理器 */ class Splitter extends StringProcess { @Override public Object process(Object input) { return Arrays.toString(((String)input).toString().split(" ")); } } public class Apply { public static void process(Processor p, Object s){ System.out.println("Using Processor "+ p.name()); System.out.println(p.process(s)); } public static String s = "Disagreement with beliefs is by definition incorrect"; /** * 向本例這樣, 創建一個能夠根據所傳遞的參數對象的不同而具有不同行為的方法, * 被稱為策略設計模式 * @param args */ public static void main(String[] args) { BigInteger b = new BigInteger("111111111111111"); process(new UpCase(), s); process(new DownCase(), s); process(new Splitter(), s); } /** * 這個類使用到了一個設計模式: 叫做策略設計模式 * 所謂 的策略設計模式指的是: 能夠根據傳遞參數的不同而具有不同的行為的方法, 被稱為策略設計模式 * 這類方法包含了所有執行的算法中固定不變的部分(這裏是基類Processor,Object),而策略就是實際調用該方法時傳遞的實際參數, * 這部分參數是變化的,因此被稱之為是"策略"部分. * 在main中看到了三種不同類型的策略應用到了String類型s對象上。 */ }

    在這個類裏面,將Process定義為了接口。並且添加了StringProcess 字符串處理器類,提供了字符串處理的公用方法。這時如何修改案例二,才能讓Apply成為一個公用類呢?其中一個方法是讓Filter實現Process接口。但為題又來了,Filter中的process的輸入輸出參數都是Waveform類型的對象。而Process中process的輸入輸出都是Object。這時我們使用一個適配器來解決這個問題

package net.mindview.interfaces.filters2;

import net.mindview.interfaces.classprocessor2.Processor;

/**
 * 由於Filter中的process的輸入輸出參數都是Waveform,而Processor中process的輸入
 * 輸出參數都是Object. 因此,寫一個適配器,讓Filter可以適配Processor類型的接口
 * 
 */

public class FilterAdapter implements Processor{
    Filter filter;
    public FilterAdapter(Filter filter){
        this.filter = filter;
    }
    
    @Override
    public String name() {
        return filter.name();
    }

    @Override
    public Object process(Object input) {
        return filter.process((Waveform)input);
    }
}

  接下來看看Apply類如何處理的?

public class Apply {
    public static void process(Processor p, Object s){
        System.out.println("Using Processor "+ p.name());
        System.out.println(p.process(s));
    }
    
    public static String s = "Disagreement with beliefs is by definition incorrect";
    
    /**
     * 向本例這樣, 創建一個能夠根據所傳遞的參數對象的不同而具有不同行為的方法,
     * 被稱為策略設計模式
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("======處理字符串===========");
        process(new UpCase(), s);
        process(new DownCase(), s);
        process(new Splitter(), s);
        
        System.out.println("======過濾波===========");
        process(new FilterAdapter(new LowPass(1.0)), new Waveform());
        process(new FilterAdapter(new HighPass(100.0)), new Waveform());
        process(new FilterAdapter(new BandPass(1.0,10.0)), new Waveform());
    }
    
}

  上面這個例子就示範了使用接口的好處,讓類和方法解耦。同時還涉及到一個設計模式,叫適配器設計模式。適配器設計模式有三種, 確切的說這是對象適配器模式.

3. 適配器設計模式

  參考文章:http://blog.csdn.net/zxt0601/article/details/52848004

  定義:適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,主的目的是兼容性,讓原本因接口不匹配不能一起工作的兩個類可以協同工作。其別名為包裝器(Wrapper)。

  

  屬於結構型模式

    主要分為三類:類適配器模式、對象的適配器模式、接口的適配器模式。

  本文做以下約定

    • 需要被適配的類,對象,接口被定義為源(目前已有的),簡稱為src(source).
    • 最終需要輸出的(我們需要的),簡稱dst(destination,也叫target)
    • 適配器Adapter

    一句話描述適配器模式:src->adapter->dist,即src以某種形式(類,對象,接口)給到適配器,適配器最終輸出dist。

  使用 場景

    • 想要使用一個已經存在的類,但如果它的方法不滿足需求時;

    • 兩個類的職責相同或相似,但是具有不同的接口時要使用它;

    • 應該在雙方都不太容易修改的時候再使用適配器模式適配,而不是一有不同時就使用它

   第一類: 類適配器模式

    一句話描述使用方法:Adapter類,通過繼承src類,實現dist接口,完成src -> dist的適配.

    以充電器為例:充電器本身就是一個適配器。輸入的時220v電壓, 輸出的是5v電壓。

    首先, 有一個src類,本身是輸出220v電壓  

package net.mindview.interfaces.adaptor;

/**
 * 這是目前有的電壓 200v
 * @author samsung
 *
 */
public class Voltage220 {
    public int output(){
        int i = 220;
        System.out.println("這是220v電壓");
        return i;
    }
}

    然後, 有一個目標使用電壓是5v

package net.mindview.interfaces.adaptor;

/**
 * 我們需要使用的目標電壓是5v
 * 這是一個5v電壓的接口
 * @author samsung
 *
 */
public interface Voltage5 {
    public int output();
}

    定義一個適配器, 將220v電壓經過處理後轉換為5v電壓供用戶使用

package net.mindview.interfaces.adaptor;

/**
 * 適配器,將220v電壓輸出為5v電壓
 * 適配器 繼承src類, 實現dist類,實現從src->dist的轉換
 */
public class VoltageAdapter extends Voltage220 implements Voltage5{
    
    @Override
    public int output() {
        int src = super.output();
        System.out.println("適配器工作開始適配電壓");
        int dist = src/44;
        System.out.println("適配完成後輸出電壓:" + dist);
        return dist;
    }
}

   有一款手機需要充電,充電電壓是5v

package net.mindview.interfaces.adaptor;

public class Mobile {
    
    public void chargeing(Voltage5 v5){
        if(v5.output() == 5){
            System.out.println("正常充電");
        } else {
            System.out.println("秒變note7");
        }
    }

}

使用手機的時候,需要將家裏的220v電壓轉換為5v輸出.調用適配器

public static void main(String[] args) {
        Mobile m = new Mobile();
        m.chargeing(new VoltageAdapter());
    }

輸出:

===============類適配器==============
我是220V
適配器工作開始適配電壓
適配完成後輸出電壓:5
電壓剛剛好5V,開始充電

技術分享圖片

  

小結:

Java這種單繼承的機制,所有需要繼承的我個人都不太喜歡。
所以類適配器需要繼承src類這一點算是一個缺點,
因為這要求dst必須是接口,有一定局限性;
且src類的方法在Adapter中都會暴露出來,也增加了使用的成本。

但同樣由於其繼承了src類,所以它可以根據需求重寫src類的方法,使得Adapter的靈活性增強了。

    第二類: 對象適配器模式

    基本思路和類適配器一樣, 只是將adapter類做了修改, 這次不繼承src類,而是持有src類實例對象,以解決兼容性問題。

    一句話總結使用方法:持有src類,實現dist類接口, 實現src -> dist的適配  

    (根據"合成復用規則", 在系統中盡量使用組合來代替繼承)

package net.mindview.interfaces.adaptor;
/**
 * 持有src類,實現dist類接口, 實現src -> dist的適配
 * @author samsung
 *
 */
public class VoltageAdapter2 implements Voltage5{
    Voltage220 voltage220;
    
    public VoltageAdapter2(Voltage220 voltage220){
        this.voltage220 = voltage220;
    }
    
    @Override
    public int output() {
        System.out.println("得到220v電壓");
        int src = voltage220.output();
        System.out.println("經過處理,輸出5v電壓");
        int dist = src/44;
        System.out.println("最終使用的電壓是"+dist+"v");
        return dist;
    }
}

測試結果:

得到220v電壓
這是220v電壓
經過處理,輸出5v電壓
最終使用的電壓是5v
正常充電

  技術分享圖片

    

小結:

對象適配器和類適配器其實算是同一種思想,只不過實現方式不同。
根據合成復用原則,組合大於繼承,
所以它解決了類適配器必須繼承src的局限性問題,也不再強求dst必須是接口。
同樣的它使用成本更低,更靈活。

  第三類 接口適配器模式

    

也有文獻稱之為認適配器模式(Default Adapter Pattern)或缺省適配器模式。
定義:
  當不需要全部實現接口提供的方法時,可先設計一個抽象類實現接口,並為該接口中每個方法提供一個默認實現(空方法),那麽該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求,它適用於一個接口不想使用其所有的方法的情況。

我們直接進入大家最喜愛的源碼撐腰環節:

源碼撐腰環節:

Android中的屬性動畫ValueAnimator類可以通過addListener(AnimatorListener listener)方法添加監聽器,
那麽常規寫法如下:

 ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();

有時候我們不想實現Animator.AnimatorListener接口的全部方法,我們只想監聽onAnimationStart,我們會如下寫:

 ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                //xxxx具體實現
            }
        });
        valueAnimator.start();

顯然,這個AnimatorListenerAdapter類,就是一個接口適配器。
查看該Adapter類源碼:

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {
    @Override
    public void onAnimationCancel(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationPause(Animator animation) {
    }

    @Override
    public void onAnimationResume(Animator animation) {
    }
}

可見,它空實現了Animator.AnimatorListener類(src)的所有方法.
對應的src類:

 public static interface AnimatorListener {
        void onAnimationStart(Animator animation);

        void onAnimationEnd(Animator animation);

        void onAnimationCancel(Animator animation);

        void onAnimationRepeat(Animator animation);
    }

類圖:

技術分享圖片
我們程序裏的匿名內部類就是Listener1 2 這種具體實現類。

 new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                //xxxx具體實現
            }
        }

接口適配器模式很好理解,令我們的程序更加簡潔明了。

總結

我個人理解,三種命名方式,是根據 src是以怎樣的形式給到Adapter(在Adapter裏的形式)來命名的。
類適配器,以類給到,在Adapter裏,就是將src當做類,繼承,
對象適配器,以對象給到,在Adapter裏,將src作為一個對象,持有。
接口適配器,以接口給到,在Adapter裏,將src作為一個接口,實現。

Adapter模式最大的作用還是將原本不兼容的接口融合在一起工作。
但是在實際開發中,實現起來不拘泥於本文介紹的三種經典形式,
例如Android中ListView、GridView的適配器Adapter,就不是以上三種經典形式之一,
我個人理解其屬於對象適配器模式,一般日常使用中,我們都是在Adapter裏持有datas,然後通過getView()/onCreateViewHolder()方法向ListView/RecyclerView提供View/ViewHolder。

Client是Lv Gv Rv ,它們是顯示View的類。
所以dst(Target)是View。
一般來說我們有的src是數據datas,
即,我們希望:datas(src)->Adapter->View(dst)->Rv(Client)。

java編程思想第四版第九章總結