代理模式分为静态代理和动态代理。动和静的主要区别,在于是否提前知道代理的类型。就像我们说多态的动态绑定一样,动态绑定是指运行时绑定,在编写代码时不关心具体类型。
这次的场景是咖啡店,根据不同的场景提供静态代理和动态代理的解决方案。
静态代理
咖啡店是接口,只有一个方法make,星巴克和瑞幸两个门店各自实现了自己的make。
场景1:瑞幸在推大师咖啡,但是初期大师没到位,只能先做“贴牌”,从星巴克点个咖啡,换成自己的小蓝杯,再给顾客送过去。虽然是不合法,但是这种模式就叫静态代理。
/**
* 咖啡店接口
* @author yeziran
*/
public interface CoffeeStore {
/**
* 咖啡制作
*/
public void make();
}
2个实现类:
/**
* 星巴克咖啡
* @author yeziran
*
*/
public class Starbuck implements CoffeeStore {
@Override
public void make() {
System.out.println("星巴克咖啡制作中...");
System.out.println("星巴克咖啡制作完成");
}
}
/**
* 静态代理,A和B实现同一接口,B的构造器注入A,B的接口实现调用A的实现
* @author yeziran
*
*/
public class Luckin implements CoffeeStore {
private CoffeeStore cStore;
public Luckin(CoffeeStore cStore) {
this.cStore = cStore;
}
@Override
public void make() {
System.out.println("瑞幸大师咖啡制作中...");
System.out.println("瑞幸大师赶紧在"+cStore.getClass().getSimpleName()+"点了一份咖啡");
cStore.make();
System.out.println("瑞星大师咖啡制作完成");
}
}
main方法模拟消费者:
public class CoffeeCustomer {
public static void main(String[] args) {
CoffeeStore starbuck = new Starbuck();
CoffeeStore luckin = new Luckin(starbuck);
starbuck.make();
System.out.println();
luckin.make();
}
}
运行得到输出:
星巴克咖啡制作中...
星巴克咖啡制作完成
瑞幸大师咖啡制作中...
瑞幸大师赶紧在Starbuck点了一份咖啡
星巴克咖啡制作中...
星巴克咖啡制作完成
瑞星大师咖啡制作完成
动态代理
场景2:美团外卖代理了2种咖啡,客户可以在美团客户端同时点2种咖啡,并且同时星巴克可以支持点食品。那么,美团就不能是通过注入一个咖啡店类型的静态代理类了,因为它还得卖食品,所以在编写代码时还不能确定被代理的类型,那么就需要动态代理来提供解决方案。
增加一个食品接口
/**
* 食品站
* @author yeziran
*/
public interface FoodStation {
public void cook();
}
对星巴克咖啡店的改造,实现FoodStation接口,使它具备制作食品的能力:
/**
* 星巴克咖啡
* @author yeziran
*
*/
public class Starbuck implements CoffeeStore, FoodStation {
@Override
public void make() {
System.out.println("星巴克咖啡制作中...");
System.out.println("星巴克咖啡制作完成");
}
@Override
public void cook() {
System.out.println("星巴克火腿卷面包制作中...");
System.out.println("星巴克火腿卷面包制作完成");
}
}
实现动态代理,必须实现接口InvocationHandler,那么美团的类型就变成这样:
/**
* 美团外卖
* <br>
* 动态代理,通过实现InvocationHandler接口,利用反射的Method.invoke方法调用目标实现
*
* @author yeziran
*
*/
public class Meituan implements InvocationHandler {
private Object target;
public Meituan(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("美团外卖开始接单...");
System.out.println("美团外卖新订单,预定外卖为 " + target.getClass().getSimpleName());
method.invoke(target, args);
System.out.println("美团外卖配送中...");
System.out.println("美团外卖订单结束。");
System.out.println();
return null;
}
}
客户端调用的时候,需要主动地使用Proxy.newProxyInstance方式创建一个被代理的实例,这样在调用对应方法的时候,InvocationHandler的invoke接口会被调用到,也就是上面重写的invoke方法。
public class CoffeeCustomer {
public static void main(String[] args) {
CoffeeStore starbuck = new Starbuck();
CoffeeStore luckin = new Luckin(starbuck);
InvocationHandler meituan = new Meituan(luckin);//瑞幸实例
CoffeeStore dProxy = (CoffeeStore) Proxy.newProxyInstance(luckin.getClass().getClassLoader(),
luckin.getClass().getInterfaces(), meituan);
dProxy.make();
meituan = new Meituan(starbuck);//星巴克实例
CoffeeStore dProxy2 = (CoffeeStore) Proxy.newProxyInstance(starbuck.getClass().getClassLoader(),
starbuck.getClass().getInterfaces(), meituan);
dProxy2.make();
FoodStation dProxy3 = (FoodStation) Proxy.newProxyInstance(starbuck.getClass().getClassLoader(),
starbuck.getClass().getInterfaces(), meituan);
dProxy3.cook();
dProxy.make();
dProxy2.make();
dProxy3.cook();
}
}
得到输出:
美团外卖开始接单...
美团外卖新订单,预定外卖为 Luckin
瑞幸大师咖啡制作中...
瑞幸大师赶紧在Starbuck点了一份咖啡
星巴克咖啡制作中...
星巴克咖啡制作完成
瑞星大师咖啡制作完成
美团外卖配送中...
美团外卖订单结束。
美团外卖开始接单...
美团外卖新订单,预定外卖为 Starbuck
星巴克咖啡制作中...
星巴克咖啡制作完成
美团外卖配送中...
美团外卖订单结束。
美团外卖开始接单...
美团外卖新订单,预定外卖为 Starbuck
星巴克火腿卷面包制作中...
星巴克火腿卷面包制作完成
美团外卖配送中...
美团外卖订单结束。
美团外卖开始接单...
美团外卖新订单,预定外卖为 Luckin
瑞幸大师咖啡制作中...
瑞幸大师赶紧在Starbuck点了一份咖啡
星巴克咖啡制作中...
星巴克咖啡制作完成
瑞星大师咖啡制作完成
美团外卖配送中...
美团外卖订单结束。
美团外卖开始接单...
美团外卖新订单,预定外卖为 Starbuck
星巴克咖啡制作中...
星巴克咖啡制作完成
美团外卖配送中...
美团外卖订单结束。
美团外卖开始接单...
美团外卖新订单,预定外卖为 Starbuck
星巴克火腿卷面包制作中...
星巴克火腿卷面包制作完成
美团外卖配送中...
美团外卖订单结束。