03.設(shè)計模式-原型集峦、代理

原型模式

一顽决、原型模式(Prototype Pattern)

  • 介紹:原型模式(Prototype Pattern)是指原型實例指定創(chuàng)建對象的 種類族铆,并且通過拷貝這些原型創(chuàng)建新的對象。
    調(diào)用者不需要知道任何創(chuàng)建細節(jié)恭朗,不調(diào)用構(gòu)造函數(shù)
    屬于創(chuàng)建型模式
  • 適用場景
    1. 類初始化消耗資源較多屏镊。
    2. new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準(zhǔn)備、訪問權(quán)限等)
    3. 構(gòu)造函數(shù)比較復(fù)雜痰腮。
    4. 循環(huán)體中生產(chǎn)大量對象時而芥,可讀性下降。
    • 總結(jié) :原型模式就是如果快速構(gòu)建對象的方法總結(jié)诽嘉, 簡單工廠將getter蔚出、setter封裝到某個方法中 JDK提供的實現(xiàn)Cloneable接口,實現(xiàn)快速復(fù)制 scope=“prototype”虫腋,scope=”singleton”
  • 淺克隆:實現(xiàn)對象拷貝的 == 類A == 骄酗,需要實現(xiàn) ==Cloneable== 接口,并覆寫 ==clone()== 方法
    • 淺拷貝是按位拷貝對象悦冀,它會創(chuàng)建一個新對象趋翻,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型盒蟆,拷貝的就是基本類型的值踏烙;如果屬性是內(nèi)存地址(引用類型),拷貝的就是內(nèi)存地址 历等,因此如果其中一個對象改變了這個地址讨惩,就會影響到另一個對象。即默認(rèn)拷貝構(gòu)造函數(shù)只是對對象進行淺拷貝復(fù)制(逐個成員依次拷貝)寒屯,即只復(fù)制對象空間而不復(fù)制資源荐捻。
  • 深克隆:對于 ==類B== 的引用類型的成員變量 ==T== 黍少,需要實現(xiàn) ==Cloneable== 并重寫 ==clone()== 方法
    1. 對于基本數(shù)據(jù)類型的成員對象,因為基礎(chǔ)數(shù)據(jù)類型是值傳遞的处面,所以是直接將屬性值賦值給新的對象厂置。基礎(chǔ)類型的拷貝魂角,其中一個對象修改該值昵济,不會影響另外一個。
    2. 對于引用類型野揪,比如數(shù)組或者類對象访忿,因為引用類型是引用傳遞,所以淺拷貝只是把內(nèi)存地址賦值給了成員變量囱挑,它們指向了同一內(nèi)存空間醉顽。改變其中一個,會對另外一個也產(chǎn)生影響平挑。
// 淺拷貝
class A 
 ... //屬性
 privat T obj;
 

@Override
protected Object clone() throws CloneNotSupportedException {
    //Subject 如果也有引用類型的成員屬性
    return super.clone();
}
//深拷貝
class B
 ... //屬性
 privat T obj;

@Override
protected Object clone() throws CloneNotSupportedException {
    //Subject 如果也有引用類型的成員屬性
     B b = super.clone();
     b.obj = (T) obj.clone();
     retrun b;
}
  • 優(yōu)點
    • 原型模式性能比直接new一個對象性能高
    • 簡化了創(chuàng)建過程
  • 缺點
    • 必須配備克隆(或者可拷貝)方法
    • 對克隆復(fù)雜對象或?qū)寺〕龅膶ο筮M行復(fù)雜改造時系草,易帶來風(fēng)險通熄。
    • 深拷貝、淺拷貝要運用得當(dāng)
  • 克隆破壞單例模式
    • 如果我們克隆的目標(biāo)的對象是單例對象找都,那意味著唇辨,深克隆就會破壞單例。實際上防止 克隆破壞單例解決思路非常簡單能耻,禁止深克隆便可
      • 要么你我們的單例類不實現(xiàn) Cloneable 接口赏枚;
      • 要么我們重寫 clone()方法,在 clone 方法中返回單例對象即可
      @Override 
      protected Object clone() throws CloneNotSupportedException { 
          return INSTANCE;
      }
      
  • Cloneable 源碼分析
    • ArrayList 就實現(xiàn)了 Cloneable 接口
    /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
    

代理模式

一晓猛、代理模式(Proxy Pattern)

  • 介紹:代理模式(Proxy Pattern)的定義也非常簡單饿幅,是指為其他對象提供一種代理,以控制對這個對象的訪問戒职。 代理對象在客服端和目標(biāo)對象之間起到中介作用.
    代理模式屬于結(jié)構(gòu)型設(shè)計模式栗恩。使用 代理模式主要有兩個目的:
    • 一保護目標(biāo)對象,
    • 二增強目標(biāo)對象洪燥。
  • 代理類型
    • 靜態(tài)代理
      • 缺點: 不符合開閉原則
    • 動態(tài)代理
      • jdk 動態(tài)代理
        • 實現(xiàn)原理
          1. 拿到被代理類的引用磕秤,并且獲取它的所有的接口(反射獲取)
          2. jdk Proxy 類重新生成一個新的類,實現(xiàn)了被代理類所有接口的方法
          3. 動態(tài)生成java代碼捧韵,把增強邏輯加入到新生成的代碼中
          4. 編譯生成新的java代碼的class文件
          5. 加載并重新運行新的class
      • cglib代理
      • CGLib 和 JDK 動態(tài)代理對比
        • JDK 動態(tài)代理是實現(xiàn)了被代理對象的接口市咆,CGLib 是繼承了被代理對象(jdk是采用讀取接口的信息,CGLib 覆蓋父類方法
          jdk proxy 對于用戶而言再来,必須要有一個接口實現(xiàn)蒙兰,目標(biāo)類相對來說復(fù)雜
          CGLib可以代理任意一個普通票煤化工的類,沒有任何要求
          CGLib有個坑,CGLib不能代理final的方法)
        • JDK 和 CGLib 都是在運行期生成字節(jié)碼,JDK 是直接寫 Class 字節(jié)碼癞己,CGLib 使用 ASM 框架寫 Class 字節(jié)碼膀斋,Cglib 代理實現(xiàn)更復(fù)雜,生成代理類比 JDK 效率低痹雅。
        • JDK 調(diào)用代理方法仰担,是通過反射機制調(diào)用,CGLib 是通過 FastClass 機制直接調(diào)用方法绩社, CGLib 執(zhí)行效率更高
    • Spring中代理選擇原則(源碼入口ProxyFactoryBean.getObject)
      • 當(dāng)bean 有實現(xiàn)接口時,spring就會選擇會用jdk動態(tài)代理
      • 當(dāng)bean 沒有實現(xiàn)接口時摔蓝,spring選擇cglib
      • spring可以通過配置強制使用CGLib 在,只需在spring的配置文件中加入如下代碼
      <aop:aspectj-autoproxy proxy-target-class="true"/>
      
    • 靜態(tài)代理和動態(tài)的本質(zhì)區(qū)別
      • 靜態(tài)代理只能通過手動完成代理操作,如果被代理類增加新的方法愉耙,代理類需要同步 新增贮尉,違背開閉原則。
      • 動態(tài)代理采用在運行時動態(tài)生成代碼的方式朴沿,取消了對被代理類的擴展限制猜谚,遵循開 閉原則
      • 若動態(tài)代理要對目標(biāo)類的增強邏輯擴展,結(jié)合策略模式赌渣,只需要新增策略類便可完成魏铅, 無需修改代理類的代碼
    • 代理模式的優(yōu)點:
      1. 代理模式能將代理對象與真實被調(diào)用的目標(biāo)對象分離。
      2. 一定程度上降低了系統(tǒng)的耦合度坚芜,擴展性好览芳。
      3. 可以起到保護目標(biāo)對象的作用。
      4. 可以對目標(biāo)對象的功能增強鸿竖。
    • 代理模式的缺點
      1. 代理模式會造成系統(tǒng)設(shè)計中類的數(shù)量增加沧竟。
      2. 在客戶端和目標(biāo)對象增加一個代理對象,會造成請求處理速度變慢缚忧。
      3. 增加了系統(tǒng)的復(fù)雜度悟泵。
//靜態(tài)代理
// Subject主題接口
public interface Subject {
    void doTask();
}


// 真是主題類,真正處理請求的類
public class RealSubject implements Subject {

    @Override
    public void doTask() {
    System.out.println("RealSubject doTask ...");
    }
}

// 攔截對真實主題對象訪問搔谴,代理類
public class Proxy implements Subject {

    private Subject realSubject;

    public Proxy(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void doTask() {
        System.out.println("記日志等  .... ");
        realSubject.doTask();
    }
}


// 客戶端代理
public class Client {

    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        Subject proxy = new Proxy(realSubject);
        proxy.doTask();
    }
}



//動態(tài)代理
Subject 魁袜、RealSubject 同上

1) 首先創(chuàng)建調(diào)用處理器
//TODO  重點
/*
1. 實現(xiàn)InvocationHandler接口    implements InvocationHandler
2.重寫invoke方法    public Object invoke(Object proxy, Method method, Object[] args) 
3.重寫方法中調(diào)用method.invoke(subject, args);
*/
public class SubjectHandler<T extends Subject> implements InvocationHandler {
    private T subject;
    public SubjectHandler(T subject) {
        this.subject = subject;
    }
    
    
    // TODO 2 
    public T getInstance(T target) throws Exception{
        this.target = target;
        Class<?> clazz = target.getClass();
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    
    
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在代理真實對象前我們可以添加一些自己的操作
        System.out.println("記日志等  .... ");
        Object invoke = method.invoke(subject, args);
        // 在代理真實對象后我們也可以添加一些自己的操作
        System.out.println("task ???");
        return invoke;
    }
}

2) 創(chuàng)建動態(tài)代理


public static void main(String[] args) {
    // 我們要代理的真實對象
    Subject realSubject = new RealSubject();
    // 我們要代理哪個真實對象,就將該對象傳進去敦第,最后是通過該真實對象來調(diào)用其方法的
    InvocationHandler handler = new SubjectHandler<>(realSubject);
    /*
     * 通過Proxy的newProxyInstance方法來創(chuàng)建我們的代理對象峰弹,我們來看看其三個參數(shù)
     * 第一個參數(shù) handler.getClass().getClassLoader() ,我們這里使用handler這個類的ClassLoader對象來加載我們的代理對象
     * 第二個參數(shù)realSubject.getClass().getInterfaces()芜果,我們這里為代理對象提供的接口是真實對象所實行的接口鞠呈,表示我要代理的是該真實對象,這樣我就能調(diào)用這組接口中的方法了
     * 第三個參數(shù)handler右钾, 我們這里將這個代理對象關(guān)聯(lián)到了上方的 InvocationHandler 這個對象上
     */
    Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
            realSubject.getClass().getInterfaces(), handler);
    // 調(diào)用代理類方法
    subject.doTask();
    
    
    
    //TODO 2 調(diào)用
    RealSubject realSubject = new SubjectHandler<RealSubject>().getInstance(new RealSubject());
    Method method = obj.getClass().getMethod("doTask",null);
    method.invoke(obj);
}


//CGLib代理
1)首先創(chuàng)建調(diào)用處理器
/*
1. MethodInterceptor    implements MethodInterceptor
2.重寫intercept方法   public Object (Object o, Method method, Object[] objects, MethodProxy methodProxy)
3.重寫方法中調(diào)用methodProxy.invokeSuper(o,objects);
*/
public class SubjectCGLibHandler<T extends Subject> implements MethodInterceptor {
   
    public Object getCGLibProxy(Class<?> clazz) throws Exception{
        //相當(dāng)于Proxy蚁吝,代理的工具類
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }
}

2) 創(chuàng)建動態(tài)代理
public static void main(String[] args) {

        try {

            //查看項目中產(chǎn)生的cglib動態(tài)類
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");

            RealSubject proxy = (RealSubject) new SubjectCGLibHandler().getCGLibProxy(RealSubject.class);
            System.out.println(proxy);
            proxy.doTask();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


  • 為什么JDK動態(tài)代理中要求目標(biāo)類實現(xiàn)的接口數(shù)量不能超過65535個
    Class文件是一組以8字節(jié)為基礎(chǔ)單位的二進制流
    各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊排列在class文件中
    中間沒有任何分隔符旱爆,這使得class文件中存儲的內(nèi)容幾乎是全部程序運行的程序
    Java虛擬機規(guī)范規(guī)定,Class文件格式采用類似C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù)窘茁,這種結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無符號數(shù)和表
    接口索引計數(shù)器(interfaces_count)怀伦,占2字節(jié)
    
    參考第一句話:class文件是一組8字節(jié)為基礎(chǔ)的二進制流,interface_count占2字節(jié)山林。也就是16.00000000,00000000 所以房待,證明
    interface_count的數(shù)量最多是2^16次方 最大值=65535
    這是在JVM的層面上決定了它的數(shù)量最多是65535
    且在java源碼中也可以看到
    
    if (var2.size() > 65535) {
    throw new IllegalArgumentException("interface limit exceeded: " var2.size());
    
    直接做了65535的長度的校驗,所以驼抹,JDK的動態(tài)代理要求桑孩,目標(biāo)類實現(xiàn)的接口數(shù)量不能超過65535
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市框冀,隨后出現(xiàn)的幾起案子流椒,更是在濱河造成了極大的恐慌,老刑警劉巖明也,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣虾,死亡現(xiàn)場離奇詭異,居然都是意外死亡诡右,警方通過查閱死者的電腦和手機安岂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帆吻,“玉大人,你說我怎么就攤上這事咙边〔轮螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵败许,是天一觀的道長王带。 經(jīng)常有香客問我,道長市殷,這世上最難降的妖魔是什么愕撰? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮醋寝,結(jié)果婚禮上搞挣,老公的妹妹穿的比我還像新娘。我一直安慰自己音羞,他們只是感情好囱桨,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗅绰,像睡著了一般舍肠。 火紅的嫁衣襯著肌膚如雪搀继。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天翠语,我揣著相機與錄音叽躯,去河邊找鬼。 笑死肌括,一個胖子當(dāng)著我的面吹牛点骑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播们童,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼畔况,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慧库?” 一聲冷哼從身側(cè)響起跷跪,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎齐板,沒想到半個月后吵瞻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡甘磨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年橡羞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片济舆。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡卿泽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滋觉,到底是詐尸還是另有隱情签夭,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布椎侠,位于F島的核電站第租,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏我纪。R本人自食惡果不足惜慎宾,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浅悉。 院中可真熱鬧趟据,春花似錦、人聲如沸仇冯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苛坚。三九已至比被,卻和暖如春色难,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背等缀。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工枷莉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尺迂。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓笤妙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親噪裕。 傳聞我的和親對象是個殘疾皇子蹲盘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容