引言
方法功能的增强是开发时常做的事情,当官方或者第三方提供的API不能满足我们的需要时,可以在原有API的基础上加上我们自定义的功能来实现需求。
码农阶段需要掌握的功能增强的方式有三种:继承、装饰者模式、动态代理。
准备工作
小码农以最近看的某码农培训班的视频结合自己的理解给出个例子来理解。现在有一个很火的概念:无人驾驶。假设无人驾驶的标准是Oracle公司制定的,Google想使用Java语言来开发无人驾驶系统,那么首先它需要创建一个类实现Oracle公司提供的无人驾驶的接口(接口为AIDriving,类为GoogleAIDriving)。
- AIDriving
public interface AIDriving {
public void start(); //无人驾驶汽车启动的方法
public void stop(); //无人驾驶汽车停止的方法
}
- GoogleAIDriving
public class GoogleAIDriving implements AIDriving {
public void start() {
System.out.println("Google汽车启动了...");
}
public void stop() {
System.out.println("Google汽车停止了...");
}
}
继承
这个时候国内某汽车制造公司(设为A公司)想使用Google提供的无人驾驶系统。但是Google提供的系统不太适合我国国情(比如美国驾驶座在右边,中国在左边),所以A公司的工程师就想在Google系统的基础上进行定制。他们选择的方式是继承GoogleAIDriving,创建一个自己的类:A1GoogleAIDriving。
public class A1GoogleAIDriving extends GoogleAIDriving{
public void start() {
System.out.println("在中国启动汽车");
super.start();
}
public void stop() {
System.out.println("在中国停止汽车");
super.stop();
}
}
装饰者模式
但理想很美好,现实很残忍。GoogleAIDriving被定义为一个final类(不能被继承),这个是可以理解的,因为如果GoogleAIDriving不是一个final类,任何继承GoogleAIDriving的类都可以对其start()、stop()方法进行覆盖,如果覆盖时出现bug,不能启动是小事,要是在高速上不能停止那就完蛋了。所以像启动、停止这种核心功能是不允许汽车制造商随意修改的。A公司的工程师就想到了使用装饰者模式来增强功能(设类为A2GoogleAIDriving)。
装饰者模式该怎么做呢?首先,装饰类得和被装饰类实现相同的接口,即AIDriving;第二,在装饰类中定义一个AIDriving类型的属性,即AIDriving car;第三,有一个参数为AIDriving类型的构造函数,即A2GoogleAIDriving(AIDriving car);第四,装饰类的每个方法都要调用被装饰类相应的方法;第五,使用第三步中的构造函数创建装饰类;第六,在装饰类的方法中自定义功能。
- 创建装饰类
public class A2GoogleAIDriving implements AIDriving {
private AIDriving car;
public A2GoogleAIDriving(AIDriving car) {
this.car = car;
}
public void start() {
System.out.println("在中国启动汽车...");
car.start();
}
public void stop() {
System.out.println("在中国停止汽车...");
car.stop();
}
}
- 调用装饰类
public class Test {
public static void main(String[] args) {
GoogleAIDriving car = new GoogleAIDriving();
A2GoogleAIDriving aCar = new A2GoogleAIDriving(car);
aCar.start();
/* Console :
在中国启动汽车...
Google汽车启动了... */
}
}
动态代理
A公司工程师正沉浸在胜利到来前的喜悦中,突然他们发现AIDriving接口有1000个方法,所以在装饰类中其他不需要加入自定义动能的998个方法我们也要调用。这一看就不是一个好的解决方案,A公司的某大佬就想起来动态代理。动态代理的详细讲解请见这里。
动态代理中可以使用反射技术得到方法的信息,如果是start()或者stop()方法就加上自定义的功能,其他方法直接执行。
- A3GoogleAIDriving
public class A3GoogleAIDriving{
private AIDriving car;
public A3GoogleAIDriving(AIDriving car) {
this.car = car;
}
public AIDriving getIns() {
AIDriving a3GoogleAIDriving = (AIDriving)Proxy.newProxyInstance(
GoogleAIDriving.class.getClassLoader(),
GoogleAIDriving.class.getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("start")
&&method.getParameterTypes().length == 0) {
System.out.println("在中国启动汽车...");
return method.invoke(car, args);
}else if (method.getName().equals("stop")
&&method.getParameterTypes().length == 0) {
System.out.println("在中国停止汽车...");
return method.invoke(car, args);
}
return method.invoke(car, args);
}
});
return a3GoogleAIDriving;
}
}
- 执行A3GoogleAIDriving
public static void main(String[] args) throws Exception {
GoogleAIDriving car = new GoogleAIDriving();
A3GoogleAIDriving a3GoogleAIDriving = new A3GoogleAIDriving(car);
AIDriving ins = a3GoogleAIDriving.getIns();
ins.start();
ins.stop();
/* Console :
在中国启动汽车...
Google汽车启动了...
在中国停止汽车...
Google汽车停止了... */
}
三种方式总结
分析了三种方式的功能增强,各位大腿是不是被小码农带入了一个误区:后者比前者更好?按照存在即合理的解释,肯定不是这样的。相反,三种方法中继承却是最常见的增强方式,因为它结构简单、易于理解。而后两种都是设计模式,一般在大型软件开发时才会用到。
而且对于装饰者模式和动态代理的区别是:装饰者模式一般用于增强功能,动态代理一般用于拦截对方法的请求。