設計模式 #5 (策略模式、代理模式)
文章中所有工程程式碼和UML
建模檔案都在我的這個GitHub
的公開庫--->DesignPattern。Star
來一個好嗎?秋梨膏!
策略模式
簡述: 一個類的行為或其演算法可以在執行時更改。
還有這種好事?執行時可以更改?
需求:現在遊戲中有數種鳥,要求實現鳥的叫,展示功能。
反例 #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
類圖如下:
總結:
- 當你想讓某個類中的某一行為能在執行中可以變化,就將這一行為拿出來進行封裝,類再通過關聯的方式獲取到這一行為即可。
- 需要在執行時改變類的行為時,可以使用策略模式進行設計。
代理模式
簡述:代理模式(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));
}
}
這不是信手拈來的事情?
有請程式猿的好同事--產品經理出場提出需求變化:“這樣太簡單了,我想要加入一些輸出提示。”
我心想,你再改需求,我就給你頭一頓輸出。
動態代理
這時候,不能改動原始碼,否則違反開閉原則,這時候先明確---動態代理的API
。
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
方法中!
@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);
}
}
總結:代理模式是代理物件通過在其內部關聯被代理物件,對被代理物件的方法實施擴充套件。