原型模式(克隆生成對象)

前言

暫時(shí)拋棄掉之前的上下文(機(jī)器人 Samu主人 Alice),創(chuàng)建型模式總不能很好對應(yīng)機(jī)器人的上下文。

因?yàn)樵湍J阶銐蚝唵危圆抛屓苏`解原型模式并不那么值得深入了解(因?yàn)槠鸪跷乙彩潜姸噍p視者中的其中之一),但是事實(shí)上它可以很重要。

正文

__ 繼承__ 對于熟知面向?qū)ο箝_發(fā)的我們是再熟悉不過了,我們通常使用一個(gè)類繼承另外一個(gè)類來實(shí)現(xiàn)對類中屬性與方法的共享那么我們能否使用一個(gè)對象繼承另外一個(gè)對象,來實(shí)現(xiàn)對象之間的屬性、方法的共享呢?

Java 類實(shí)現(xiàn)繼承

程序員視角

思考: 如何建立對象級的繼承關(guān)系?

繼承的目的是為了共享父類的屬性與方法,此時(shí)需要拋開腦海中的基于類才可以實(shí)現(xiàn)繼承的刻板思想。

Java 使用對象實(shí)現(xiàn)繼承

這樣子對象需要父對象的方法和屬性的時(shí)候,可以通過聚合父對象來訪問父對象的方法、屬性。

但是直接直接聚合父對象會(huì)引起問題:父對象的變更,會(huì)對子對象產(chǎn)生影響。(這在Java類繼承中是不可接受的)。

于是我們想到了原型模式,即克隆父對象并讓子對象持有父對象(克?。?/code>的引用,借此來避免這個(gè)問題。

代碼實(shí)現(xiàn)

聲明原型鏈接口,用于實(shí)現(xiàn)如上圖的聚合關(guān)系。

public interface IPrototype {
    // 原型對象(即它的父類對象)
    IPrototype __proto__();
    // 自身構(gòu)造器
    IPrototype __constructor__();
    // 定義ProtoTypeInherits方法的目的是為了演示,通過 原型鏈的"對象繼承"
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface ProtoTypeInherits {

    }
}

定義原型對象基類(用于模擬對象繼承對象的類)

public class ProtoObject implements IPrototype, Cloneable {

    // 克隆的原型對象
    private ProtoObject cloneProto;

    public ProtoObject(/* 繼承的對象 */ProtoObject cloneProto) {
        if (cloneProto == null) {
            this.cloneProto = null;
        } else {
            try {
                this.cloneProto = cloneProto.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public IPrototype __proto__() {
        return cloneProto;
    }

    @Override
    public IPrototype __constructor__() {
        return this;
    }

    @Override
    protected ProtoObject clone() throws CloneNotSupportedException {
        return (ProtoObject) super.clone();
    }

    // 省略了部分與調(diào)用功能相關(guān)的代碼
}

分類定義三個(gè)對象,分別是Root(根節(jié)點(diǎn))、Combination(枝干)、Leaf(葉子)對象。

public class Root extends ProtoObject {
    public Root(ProtoObject cloneProto) {
        super(cloneProto);
    }

    @ProtoTypeInherits
    public void root() {

    }

}
public class Combination extends ProtoObject {
    public Combination(ProtoObject cloneProto) {
        super(cloneProto);
    }

    @ProtoTypeInherits
    public void combination() {

    }

}
public class Leaf extends ProtoObject {
    public Leaf(ProtoObject cloneProto) {
        super(cloneProto);
    }

    @ProtoTypeInherits
    public void leaf() {

    }

}

注意:這3個(gè)對象并沒有類繼承關(guān)系。

public class Client {

    public static void main(String[] args) {

        // 是否找到了 裝飾者模式 的影子?
        ProtoObject root = new Root(null);
        ProtoObject combination = new Combination(root);
        ProtoObject leaf = new Leaf(combination);

        leaf.printChain();
    }
}

到現(xiàn)在為止,我們已經(jīng)成功的構(gòu)建了基于對象聚合的原型鏈。構(gòu)造原型的代碼如下:

public class ProtoObject implements IPrototype, Cloneable {

    public ProtoObject(/* 繼承的對象 */ProtoObject cloneProto) {
        try {
              this.cloneProto = cloneProto.clone();
         } catch (CloneNotSupportedException e) {
              e.printStackTrace();
        }
    }

    @Override
    protected ProtoObject clone() throws CloneNotSupportedException {
        return (ProtoObject) super.clone();
    }
}

ProtoObject實(shí)現(xiàn)了Cloneable,原型其實(shí)就是這么簡單。當(dāng)然在Java中提及Cloneable一定會(huì)提到深拷貝、淺拷貝。

上下文:你有房子A,后來你買了房子B。現(xiàn)在呢,你想把房子A打造的和房子B一模一樣,怎么辦呢?(我要克隆A對象
深拷貝:所以你給房子B,買了房子A中所有的家具。(2套家具)。
淺拷貝:所以你把房子A中的家具全部搬到了房子B。(1套家具)。
當(dāng)然理解這些的前提是,知道Java引用與C指針的區(qū)別。

所以對于對象繼承我們不可避免的要使用深拷貝,所以使用對象繼承的一個(gè)顯著的缺點(diǎn):會(huì)產(chǎn)生大量的碎片對象(父對象)?;仡^再來想想Java的類繼承機(jī)制:類的對象只會(huì)在內(nèi)存中存留一份,繼承關(guān)系中需要使用到父類的方法是通用引用的方式訪問父類。(不要太好 :-P )

工作還沒有做完,現(xiàn)在已經(jīng)有了原型鏈。接下需要實(shí)現(xiàn)通過原型鏈訪問父對象的方法。


public class ProtoObject implements IPrototype, Cloneable {

    public void invoke(String methodString) {
        IPrototype proto = __constructor__();
        Class<?> cls;
        System.out.printf(" 查找 原型鏈 + (%s)%n", methodString);
        while (proto != null) {
            cls = proto.getClass();
            if (pickInvoke(cls, proto, methodString, (Method method) -> (method != null))) {
                break;
            }
            proto = proto.__proto__();

        }
        System.out.printf(" 查找 原型鏈 - (%s)%n", methodString);
    }

    private boolean pickInvoke(Class<?> cls, IPrototype proto, String methodString, Predicate<Method> predicate) {


        Method method = pickMethod(cls, methodString::equalsIgnoreCase);
        if (predicate.test(method)) {
            System.out.println("當(dāng)前輸入類型" + cls.getSimpleName() + ",結(jié)果匹配成功 ");
            try {
                method.invoke(proto);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return true;
        } else {
            System.out.println("當(dāng)前輸入類型" + cls.getSimpleName() + ",結(jié)果匹配失敗 ");
            return false;
        }
    }

    public Method pickMethod(Class<?> cls, Predicate<String> predicate) {
        Method[] methods = cls.getMethods();
        if (methods == null) return null;
        if (methods.length == 0) return null;
        for (Method m : methods) {
            if (predicate.test(m.getName())) {
                return m;
            }
        }
        return null;
    }

    // 省略無關(guān)的代碼
}

這里使用到了JDK 1.8中的Lambda語法,如果你還不了解JDK 1.8可以持續(xù)關(guān)注我的另一篇文集JDK 1.8之旅。

public class Client {
    public static void main(String[] args) {
        ProtoObject root = new Root(null);
        ProtoObject combination = new Combination(root);
        ProtoObject leaf = new Leaf(combination);
        // 通過反射調(diào)用父類的方法
        leaf.invoke("root"); 
    }
}

// 輸出日志
// 查找 原型鏈 + (root)
// 當(dāng)前輸入類型Leaf,結(jié)果匹配失敗 
// 當(dāng)前輸入類型Combination,結(jié)果匹配失敗 
// 當(dāng)前輸入類型Root,結(jié)果匹配成功 
// 查找 原型鏈 - (root)

通過對象繼承對象的功能我們已經(jīng)初步實(shí)現(xiàn)了,原型模式在其中也發(fā)揮了不少的力氣 — — 不關(guān)心對象的具體創(chuàng)建過程、初始化過程,但同時(shí)也因?yàn)樵湍J疆a(chǎn)生了大量的內(nèi)存對象。

總結(jié)

原型模式的本質(zhì):克隆生成對象。

使用原型對象可以封裝對象的創(chuàng)建過程,至于對象的初始化過程是否需要封裝則依據(jù)使用者的需求決定。

原型對象會(huì)產(chǎn)生大量的內(nèi)存對象,所以請勿過度使用原型模式。如果的確需要大量使用原型模式,請考慮結(jié)合原型管理器緩存原型實(shí)例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容