1. 程式人生 > >設計模式 #5 (策略模式、代理模式)

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

# 設計模式 #5 (策略模式、代理模式) --- **文章中所有工程程式碼和`UML`建模檔案都在我的這個`GitHub`的公開庫--->[DesignPattern](https://github.com/L1ng14/DesignPattern)。**`Star`來一個好嗎?秋梨膏! --- ## 策略模式 簡述: **一個類的行為或其演算法可以在執行時更改。** 還有這種好事?執行時可以更改? ==需求:==現在遊戲中有數種鳥,要求實現鳥的叫,展示功能。 ### 反例 #1: ~~~java public abstract class Bird { public abstract void display(); public void yell() { System.out.println("吱吱吱....."); } } ~~~ ~~~java public class RubberBird extends Bird{ @Override public void display() { System.out.println("這是橡皮鳥-----------"); } } ~~~ ~~~java public class RedHeadBird extends Bird{ @Override public void display() { System.out.println("這是 紅頭鳥。。。"); } } ~~~ ~~~java 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`介面,哪個鳥能飛,就讓他實現這個介面即可。 ~~~java public interface Flyable { void fly(); } ~~~ ~~~java public class RedHeadBird extends Bird implements Flyable{ @Override public void display() { System.out.println("這是 紅頭鳥。。。"); } @Override public void fly() { System.out.println("飛飛飛============"); } } ~~~ ~~~java public class negtive_02 { /*===============客戶端========================*/ public static void main(String[] args) { RedHeadBird redHeadBird = new RedHeadBird(); redHeadBird.display(); redHeadBird.yell(); redHeadBird.fly(); } } ~~~ 這種設計確實實現了需求,但是,這會導致程式碼的重複,比如:不同的鳥有不同的飛行高度,但是相當部分的鳥又具有相同的高度。這就帶來程式碼重用的問題。 ==變化:==遊戲中的鳥可以變化形態,改變飛的方式。這就要求在執行時可以改變`Bird`類中飛的行為。 ### 正例 #1: ~~~java public interface FlyBehavior { void fly(); } ~~~ ~~~java public class FlyByKick implements FlyBehavior{ @Override public void fly() { System.out.println("被踢飛了!!!!!!"); } } ~~~ ~~~java public class FlyByWings implements FlyBehavior{ @Override public void fly() { System.out.println("用翅膀飛~~~~~~~~~~~"); } } ~~~ ~~~java 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("吱吱吱....."); } } ~~~ ~~~java public class RedHeadBird extends Bird { public RedHeadBird() { this.flyBehavior = new FlyByWings(); } @Override public void display() { System.out.println("這是 紅頭鳥。。。"); } public void doFly(){ this.flyBehavior.fly(); } } ~~~ ~~~java 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](https://i.loli.net/2020/09/19/sCm1Bug249UDdYQ.png) 總結: - 當你想讓某個類中的某一行為能在執行中可以變化,就==將這一行為拿出來進行封裝,類再通過關聯的方式獲取到這一行為即可。== - 需要==在執行時改變類的行為==時,可以使用策略模式進行設計。 ## 代理模式 簡述:**代理模式(Proxy),為其他物件提供一種代理以控制對這個物件的訪問。** ==需求==:現在需要實現加減乘除功能。 #### 反例 #1: ~~~java 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; } } ~~~ ~~~java /*===================客戶端=============*/ 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](https://i.loli.net/2020/09/20/DlkXhOJC4NntTsZ.png) 這時候,不能改動原始碼,否則違反[開閉原則](https://www.cnblogs.com/l1ng14/p/13662445.html#開閉原則),這時候先明確---==動態代理==的`API`。 ~~~java public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) ~~~ - 第1個引數:`ClassLoader`(**動態代理的物件的類載入器**) 我們都知道,要例項化一個物件,是需要呼叫類的構造器的,在程式執行期間第一次呼叫構造器時,就會引起類的載入,載入類的時候,就是`jvm`拿著`ClassLoader`去載入類的位元組碼的,只有位元組碼被載入到了記憶體中,才能進一步去例項化出類的物件。簡單來說,就是隻要涉及例項化類的物件,就一定要載入類的位元組碼,而載入位元組碼就必須使用類載入器!下面我們使用的是動態代理的`api`來建立一個類的物件,這是一種不常用的例項化類物件的方式,儘管不常用,但畢竟涉及例項化類的物件,那就一定也需要載入類的位元組碼,也就一定需要類載入器,所以我們手動把類載入器傳入! - 第2個引數:`Class[]`(**需要呼叫其方法的介面**) 我們已經知道,下面的程式碼,是用來例項化一個物件的,例項化物件,就一定是例項化某一個類的物件,問題是,到底是哪個類呢?類在哪裡?位元組碼又在哪裡?這個類,其實並不在硬碟上,而是在記憶體中!是由動態代理在記憶體中"f動態生成的!要知道,這個在記憶體中直接生成的位元組碼,會去自動實現下面方法中的第2個引數中,所指定的介面!所以,利用動態代理生成的代理物件,就能轉成`Calculator`介面型別!那麼這個代理物件就擁有`add`、 `sub`、 `mul` 、`div`方法! - 第3個引數:`InvocationHandler`(**呼叫方法時的處理程式**) 我們已經知道,下面的代理物件`porxy`所屬的類,實現了`Calculator`介面,所以,這個代理物件就擁有`add`、 `sub`、 `mul` 、`div`方法!我們就可以通過代理物件呼叫`add`、 `sub`、 `mul` 、`div`方法!注意,每次對代理物件任何方法的呼叫,都不會進入真正的實現方法中。而是統統進入第3個引數的`invoke`方法中! ~~~java @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } ~~~ - `Object proxy`:代理物件 - `Method`:代理物件呼叫的方法 - `Object[] args`:呼叫方法的引數 #### 正例 #1: ~~~java 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`的方法。 ~~~java 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](https://i.loli.net/2020/09/20/ZpVhgmU7N1BiTCu.png) 總結:==代理模式是代理物件通過在其內部關聯被代理物件,對被代理物件的方法實施擴