設計模式 #5 (策略模式、代理模式)

設計模式 #5 (策略模式、代理模式)


文章中所有工程代碼和UML建模文件都在我的這個GitHub的公開庫—>DesignPatternStar來一個好嗎?秋梨膏!


策略模式

簡述: 一個類的行為或其算法可以在運行時更改。

還有這種好事?運行時可以更改?

需求:現在遊戲中有數種鳥,要求實現鳥的叫,展示功能。

反例 #1:

public abstract  class Bird {
    public abstract void display();

    public void yell() {
        System.out.println("吱吱吱.....");
    }
}
public class RubberBird extends Bird{
    @Override
    public void display() {
        System.out.println("這是橡皮鳥-----------");
    }
}
public class RedHeadBird extends Bird{
    @Override
    public void display() {
        System.out.println("這是  紅頭鳥。。。");
    }
}
public class negtive_01 {
/*===============客戶端========================*/
    public static void main(String[] args) {
        RedHeadBird redHeadBird = new RedHeadBird();
        redHeadBird.display();
        redHeadBird.yell();

        System.out.println("     ");

        RubberBird rubberBird = new RubberBird();
        rubberBird.display();
        rubberBird.yell();
    }
}

好,現在產品笑嘻嘻地來改需求,咱們都是文明人,別拿刀出來。

變化:現在要求為遊戲中的某些鳥添加飛的功能。

反例 #2:

產品說了,「哥,咱首先明確,游戲裏的某些鳥,比如橡皮鳥是飛不起來的。」

通過改寫Bird抽象類增加一個抽象fly方法,在各實現類中實現該抽象方法(因為和以下方法雷同,所以就不在此贅述),或者:

編寫一個Flyable接口,哪個鳥能飛,就讓他實現這個接口即可。

public interface Flyable {
    void fly();
}
public class RedHeadBird extends Bird implements Flyable{
    @Override
    public void display() {
        System.out.println("這是  紅頭鳥。。。");
    }

    @Override
    public void fly() {
        System.out.println("飛飛飛============");
    }
}
public class negtive_02 {
    /*===============客戶端========================*/
    public static void main(String[] args) {
        RedHeadBird redHeadBird = new RedHeadBird();
        redHeadBird.display();
        redHeadBird.yell();

        redHeadBird.fly();
    }
}

這種設計確實實現了需求,但是,這會導致代碼的重複,比如:不同的鳥有不同的飛行高度,但是相當部分的鳥又具有相同的高度。這就帶來代碼重用的問題。

變化:遊戲中的鳥可以變化形態,改變飛的方式。這就要求在運行時可以改變Bird類中飛的行為。

正例 #1:

public interface FlyBehavior {
    void fly();
}
public class FlyByKick implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("被踢飛了!!!!!!");
    }
}
public class FlyByWings implements  FlyBehavior{
    @Override
    public void fly() {
        System.out.println("用翅膀飛~~~~~~~~~~~");
    }
}
public abstract  class Bird {

    protected FlyBehavior flyBehavior;

    public FlyBehavior getFlyBehavior() {
        return flyBehavior;
    }

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

    public abstract void display();

    public void yell() {
        System.out.println("吱吱吱.....");
    }
}
public class RedHeadBird extends Bird  {

    public RedHeadBird() {
        this.flyBehavior = new FlyByWings();
    }

    @Override
    public void display() {
        System.out.println("這是  紅頭鳥。。。");
    }

    public void doFly(){
        this.flyBehavior.fly();
    }

}
public class postive {
    /*===============客戶端========================*/
    public static void main(String[] args) {
        RedHeadBird redHeadBird = new RedHeadBird();
        redHeadBird.display();
        redHeadBird.yell();
        redHeadBird.doFly();

        System.out.println("         ");
        System.out.println("靠近人群中.......");

        redHeadBird.setFlyBehavior(new FlyByKick());
        redHeadBird.doFly();
    }
}

此時,才是真正的策略模式。通過關聯另一個接口FlyBehavior,封裝飛的行為,同時保證了代碼的重用性,接口還可以對擴展保持開放。

UML類圖如下:

image-20200919202001281

總結:

  • 當你想讓某個類中的某一行為能在運行中可以變化,就將這一行為拿出來進行封裝,類再通過關聯的方式獲取到這一行為即可。
  • 需要在運行時改變類的行為時,可以使用策略模式進行設計。

代理模式

簡述:代理模式(Proxy),為其他對象提供一種代理以控制對這個對象的訪問。

需求:現在需要實現加減乘除功能。

反例 #1:

    interface Calculator{
        int add(int a ,int b);
        int sub(int a ,int b);
        int mul(int a ,int b);
        int div(int a ,int b);
    }

     class MyCalculator implements Calculator{

        @Override
        public int add(int a, int b) {
            return a + b;
        }

        @Override
        public int sub(int a, int b) {
            return a - b;
        }

        @Override
        public int mul(int a, int b) {
            return a * b;
        }

        @Override
        public int div(int a, int b) {
            return a / b;
        }
    }
/*===================客戶端=============*/
public class negtive {
        public static void main(String[] args) {
           Calculator c = new MyCalculator();
           System.out.println(c.add(2, 3));
           System.out.println(c.sub(10, 3));
           System.out.println(c.mul(8, 3));
           System.out.println(c.div(99, 3));
    }
}

這不是信手拈來的事情?

有請程序猿的好同事–產品經理出場提出需求變化:「這樣太簡單了,我想要加入一些輸出提示。」

我心想,你再改需求,我就給你頭一頓輸出。

動態代理

image-20200920144859139

這時候,不能改動源代碼,否則違反開閉原則,這時候先明確—動態代理API

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • 第1個參數:ClassLoader(動態代理的對象的類加載器)
    我們都知道,要實例化一個對象,是需要調用類的構造器的,在程序運行期間第一次調用構造器時,就會引起類的加載,加載類的時候,就是jvm拿着ClassLoader去加載類的位元組碼的,只有位元組碼被加載到了內存中,才能進一步去實例化出類的對象。簡單來說,就是只要涉及實例化類的對象,就一定要加載類的位元組碼,而加載位元組碼就必須使用類加載器!下面我們使用的是動態代理的api來創建一個類的對象,這是一種不常用的實例化類對象的方式,儘管不常用,但畢竟涉及實例化類的對象,那就一定也需要加載類的位元組碼,也就一定需要類加載器,所以我們手動把類加載器傳入!
  • 第2個參數:Class[]需要調用其方法的接口
    我們已經知道,下面的代碼,是用來實例化一個對象的,實例化對象,就一定是實例化某一個類的對象,問題是,到底是哪個類呢?類在哪裡?位元組碼又在哪裡?這個類,其實並不在硬盤上,而是在內存中!是由動態代理在內存中”f動態生成的!要知道,這個在內存中直接生成的位元組碼,會去自動實現下面方法中的第2個參數中,所指定的接口!所以,利用動態代理生成的代理對象,就能轉成Calculator接口類型!那麼這個代理對象就擁有addsubmuldiv方法!
  • 第3個參數:InvocationHandler調用方法時的處理程序
    我們已經知道,下面的代理對象porxy所屬的類,實現了Calculator接口,所以,這個代理對象就擁有addsubmuldiv方法!我們就可以通過代理對象調用addsubmuldiv方法!注意,每次對代理對象任何方法的調用,都不會進入真正的實現方法中。而是統統進入第3個參數的invoke方法中!
@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
  • Object proxy:代理對象
  • Method:代理對象調用的方法
  • Object[] args:調用方法的參數

正例 #1:

public class MyHandler implements InvocationHandler {
    private Calculator calculator ;
    public MyHandler(Calculator c){
        this.calculator = c;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("調用"+method.getName()+",  參數是"+ Arrays.toString(args));
        int res = (int) method.invoke(calculator, args);
        System.out.println("結果是 "+res);
        return res;
    }
}

先把InvocationHandler的實現類設計好。在實現類的內部關聯Calculator,用於調用Calculator的方法。

public class postive {
    public static void main(String[] args) {
        Calculator c = new MyCalculator();

        ClassLoader loader = postive.class.getClassLoader();
        Calculator proxy = (Calculator)Proxy.newProxyInstance(loader, new Class[]{Calculator.class}, new MyHandler(c));

        proxy.add(22,33);
        proxy.sub(55,22);
        proxy.div(10,2);
        proxy.mul(50,5);
    }
}

image-20200920144746386

總結:代理模式是代理對象通過在其內部關聯被代理對象,對被代理對象的方法實施擴展。