Java與混型
C++中存在一種叫混型的東西,他可以通過Template動態構建繼承鏈
#include <string>#include <ctime>#include <iostream>using namespace std;template<class T> class TimeStamped : public T { long timeStamp;public: TimeStamped() { timeStamp = time(0); } long getStamp() { return timeStamp; }};template<class T> class SerialNumbered : public T { long serialNumber; static long counter;public: SerialNumbered() { serialNumber = counter++; } long getSerialNumber() { return serialNumber; }};template<class T> long SerialNumbered<T>::counter = 1;class Basic { string value;public: void set(string val) { value = val } string get() { return value; }}int main() { TimeStamped<SerialNumbered<Basic>> mixin1, mixin2; mixin1.set("test string 1"); mixin2.set("test string 2"); cout << mixin1.get() << " " << mixin1.getStamp() << " " << mixin1.getSerialNumber() << endl; cout << mixin2.get() << " " << mixin2.getStamp() << " " << mixin2.getSerialNumber() << endl;}/* Output: (Sample)test string 1 1129840250 1test string 2 1129840250 2*/
以上代碼摘抄自 Thinking in Java
可以看到C++中可以使用template實現對泛型的繼承,從而在客戶代碼中動態聲明一個繼承鏈引用,而 Java 中因為對泛型的擦除,我們並不能直接對泛型進行繼承,所以只能盡量的去模仿實現這樣的功能,通常有以下兩種方法:
1) 使用Decorator模式實現不修改基類的情況下對基類的擴充
package decorator;public class Base { public void operation() { System.out.println("Base Operates"); }}package decorator;public class Decorator extends Base { protected Base base; public void set(Base base) { this.base = base; } public void operation() { base.operation(); }}package decorator;public class Extend1 extends Decorator { public void operation() { super.operation(); System.out.println("Extend1 Operates"); }}package decorator;public class Extend2 extends Decorator{ public void operation() { super.operation(); System.out.println("Extend2 Operates"); }}package decorator;public class Test { public static void main(String[] args) { Base base = new Base(); Extend1 extend1 = new Extend1(); Extend2 extend2 = new Extend2(); extend2.set(extend1); extend1.set(base); extend2.operation(); System.out.println(); extend1.operation(); System.out.println(); base.operation(); }}
輸出:
Base Operates
Extend1 Operates
Extend2 Operates
Base Operates
Extend1 Operates
Base Operates
這裡使用Decorator的目的是為了達到讓我們最外層的持有對象,例如 Extend2 同時擁有 Extend1, Base 的功能,與連續的繼承不同的是,Decorator模式並不是一個繼承鏈,而是一個持有鏈, 簡單的模型圖大概如下
可以想想成 Extend2 持有 Extend1 , Extend1 持有 Base,這樣的好處是更為靈活,我們可以自由的調換持有順序,例如代碼如果變成
Extend1.set(Extend2);Extend2.set(Base);
則持有鏈會變成:
其缺陷在於我們可以在Extend1和Extend2內部操作他們的持有對象,但是用戶端對整個持有鏈的操作被窄化成了最外層引用,例如執行個體代碼裡就是我們只能拿到 Extend2 的引用,卻無法擷取 Extend2 -> Extend1 -> Base 。 原因是 Extend2 裡持有的 Extend1 的引用存在與Decorator類裡,它是 Base 類型的,而非具體的Extend1,2,3,4型。
2) 使用動態代理來類比混型
package dynamicproxy;public interface Extend1 { void ext1Func();}package dynamicproxy;public class Extend1Impl implements Extend1 { @Override public void ext1Func() { System.out.println("Ext 1 Impl Func Called"); }}package dynamicproxy;public interface Extend2 { void ext2Func();}package dynamicproxy;public class Extend2Impl implements Extend2 { @Override public void ext2Func() { System.out.println("Ext 2 Impl Func Called"); }}package dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class ProxyClass implements InvocationHandler { // 被代理對象的Map,這些對象通過對象內的方法名與之綁定 private Map<String, Object> delegatesByMethod = new HashMap<String, Object>(); public ProxyClass(Object[] proxiedList) { for (Object o : proxiedList) { for (Method m : o.getClass().getMethods()) { // 這裡會有一定的局限性,也就是當不同的被代理對象 // 有相同的方法時,只能調用第一個擁有該方法的被代理對象 if (!delegatesByMethod.containsKey(m.getName())) delegatesByMethod.put(m.getName(), o); } } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 通過方法名確定需要調用的被代理對象 return method.invoke(delegatesByMethod.get(method.getName()), args); }}package dynamicproxy;import java.lang.reflect.Proxy;public class Test { public static void main(String[] args) { // 代理對象的類型列表,代理對象實際上是這些類型的超類,可讓他以後轉型為這些類型 Class<?>[] interfaces = { Extend1.class, Extend2.class }; // 代理對象內的被代理對象列表,實際調用的方法來自與這些代理對象 Object[] proxieds = { new Extend1Impl(), new Extend2Impl() }; // 產生混型對象 Object proxy = Proxy.newProxyInstance( proxieds[0].getClass().getClassLoader(), interfaces, new ProxyClass(proxieds) ); // 混型向下轉型為不同的類型 Extend1 ext1 = (Extend1) proxy; Extend2 ext2 = (Extend2) proxy; ext1.ext1Func(); ext2.ext2Func(); }}
其實他的原理很簡單,就是在動態代理類內部使用一個Map,這個Map儲存了所有的被代理對象及其對應的方法,而代理對象會成為所有被代理的超類,從而可以很方便的將代理對象變化成任何被代理對象,達到類比混型的效果。
他的局限性在於 1) 我們是通過方法名來判斷需要調用的被代理對象的執行個體是哪一個,如果不同的被代理對象存在重複的方法名,則只能調用第一個被代理對象的方法; 2)需要我們顯式地向下轉型去把代理對象變為一個被代理對象,這並非一種強約束。
儘管如此, Thinking in Java 一書中仍然提到了這是最接近於混型的Java實現形式了。
潛在類型機制
潛在類型又稱為鴨子類型,也就是說不明確的宣告類型的名字,而關注類型的行為,一個方法接受參數時只關注這個參數的行為,只要他的行為符合方法的需要,方法就接納這個參數
class Dog {public: void speak() {} void sit() {} void reproduce() {}};class Robot {public: void speak() {} void sit() {} void oilChange() {}};template<class T> void perform(T anything) { anything.speak(); anything.sit();}int main() { Dog d; Robot r; perform(d); perform(r);}
如上面的 perform() 方法的參數, 作為泛型 T 我們其實並不知道 anything 是個什麼類型,但是我們依然可以去調用 T.speak() T.sit(), 同時我們可以將Dog 或者 Robot 當作 T傳入到方法裡面去,只要T能完成perform() 方法體裡的任務。 也就是說我們並不關注到底 T 是個什麼類型,只要T滿足方法體的需求就可以調用。
而Java裡的泛型因為擦除,我們沒辦法對T進行任何方法的調用,只能作為一種未知的類型作為其他泛型的參數或者當作傳回值來進行處理。 而Java中的替代方式就是使用介面替代perform(T anything)方法的泛型參數
public class Dog { void speak(); void sit(); void eat();}public class Robot { void speak(); void sit(); void oilChange();}public interface SpeakableNSitable { void speak(); void sit();}public void perform(SperkableNSitable s) { s.speak(); s.sit();}// 對於我們要Dog和Robot忽略類型的傳入到perform方法裡而又不修改源// 程式,做法就只有使用適配器了。public class DogAdapter implements SpeakableNSitable { Dog dog; public DogAdapter(Dog dog) { this.dog = dog; } @Override public void speak() { dog.speak; } @Override public void sit() { dog.sit; }}// Robot 適配器同上public class RobotAdapter implements SpeakableNSitable { // ...}
這種方式其實就是用適配器來替代潛在類型,沒有什麼新奇的,相對與支援潛在類型的語言來說,這樣的寫法更加冗長。