前言
暫時(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)對象之間的屬性、方法的共享呢?

程序員視角
思考: 如何建立對象級的繼承關(guān)系?
繼承的目的是為了共享父類的屬性與方法,此時(shí)需要拋開腦海中的基于類才可以實(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í)例。