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 1 test 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 OperatesBase Operates
Extend1 OperatesBase 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 { // ... }
这种方式其实就是用适配器来替代潜在类型,没有什么新奇的,相对与支持潜在类型的语言来说,这样的写法更加冗长。