摘自《轻量级JavaEE企业应用》
代理模式
当客户端代码需要调用某一个对象时,客户端实际上不关心是否准确得到该对象,他只要一个能提供该功能的对象即可,此时就可以返回该对象的代理(Proxy)
有点像无中生有,直到真正使用到对象的方法时候才创建这个对象,之前都是一个对象的假象。
代码举例:一个BigImage对象实例化的时候需要花费3秒钟,但是我们可能并不会真正使用到它或者用到它的时候是比较后面的时候才用到我们就可以这样设计。
延时加载的BigImage
Image接口
package com.proxy;
/**
* 大图所需要实现的接口
* @author Slience
*
*/
public interface Image {
void show();
}
之后BigImage去实现这个接口
package com.proxy;
/**
* 大图片实现类
* 构造函数中暂停了3秒来模拟系统开销,如果不用代理模式
* 系统在创建这个BigImage的时候将会有3秒的延时
* @author Slience
*
*/
public class BigImage implements Image {
public BigImage() {
// TODO Auto-generated constructor stub
try {
//模式加载大图片的时间
Thread.sleep(3000);
System.out.println("图片加载成功");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("绘制实际的大图片");
}
}
之后创建ImageProxy代理类,它来发起Image的show动作
package com.proxy;
public class ImageProxy implements Image {
//组合一个image实例,作为代理的对象
private Image image;
//使用抽象实体来初始化代理的对象
public ImageProxy(Image image) {
// TODO Auto-generated constructor stub
this.image = image;
}
/***
* 重写Image接口的show()方法
* 该方法用于控制对被代理对象的访问
* 并根据需要负责创建和删除被代理对象
*/
@Override
public void show() {
// TODO Auto-generated method stub
//只有当真正需要调用image的show方法是才会创建被代理的对象
if(image == null) {
image = new BigImage();
}
image.show();
}
}
代理类ImageProxy中的show方法控制了系统真正使用此Image的时候才会实例化对象。
package com.proxy;
/***
* 获取到Image很快,因为并没有真正创建BigImage对象,BigImage对象是在调用show方法的时候创建的
* 这样做的好处在于创建BigImage对象的时间推迟到真正需要它的时候,可以减少BigImage在内存中的存活时间
* 还有可能就是系统可能永远也用不到BigImage对象(不调用show方法),意为着系统根本不需要实例化BigImage
* 这是Hibernate延时加载所采用的设计模式
* @author Slience
*
*/
public class BigImageTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Image image = new ImageProxy(null);
System.out.println("系统得到Image对象的时间开销:" + (System.currentTimeMillis() - start));
image.show();
System.out.println("调用show方法时真正创建Image的时间开销:" + (System.currentTimeMillis() - start));
}
}
可以看见系统可以很快的得到Image对象,只不过在它实际使用的时候才会去new这个对象。这样做的好处有两点
- 把创建BigImage推迟到真正需要它时才创建,这样能够保证前面代码运行的流畅性,而且减少BigImage对象在系统内存的存活时间。
- 在有些情况下,程序也许永远不会真正调用BigImage的show方法,这意味着系统根本无需创建BigImage对象。在这种情形下,使用代理模式可以显著提高性能。
第二个好处就是Hibernate【延时加载】所采用的设计模式——当A实体和B实体存在关联关系的时候,当系统加载A实体的时候B实体其实并未加载出来,直到A实体真正需要去访问B实体的时候才会加载B
增强功能的代理
代理模式还可以用到另一种地方,就是当对象功能不足的时候,系统可以创建一个代理对象,而这个代理对象可以增强元目标对象的功能。
这一点很像【拦截器】的功能
举个例子:有一个猎狗,他会执行info和run方法,但是现在需要在执行这两个方法之前执行一些处理,我们就可以这样操作
package com.proxy;
/**
* 狗接口
* @author Slience
*
*/
public interface Dog {
void info();
void run();
}
然后一只猎狗去实现这个接口,没啥特殊的
package com.proxy;
public class GunDog implements Dog {
@Override
public void info() {
// TODO Auto-generated method stub
System.out.println("我是一只猎狗");
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("猎狗奔跑很快");
}
}
现在需要在执行run和info之前【增加事物控制】——在目标方法被调用之前开始事物,在目标方法被调用之后结束事物。
我们在这里创建一个拦截器类TxUtil
package com.proxy;
public class TxUtil {
public void beginTx() {
System.out.println("模拟事物开始");
}
public void endTx() {
System.out.println("模拟结束事物");
}
}
之后借助InvocationHandler来实现代理
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvokationHandler implements InvocationHandler {
//这是需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
/**
* 当执行动态代理的对象的所有方法是,都会被替换成执行如下的invoke方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("method = " + method);
TxUtil tx = new TxUtil();
//执行拦截器中的方法
tx.beginTx();
//执行本该执行的方法
Object result = method.invoke(target, args);
//执行拦截器中的方法
tx.endTx();
return result;
}
}
之后创建一个动态代理的Factory类
package com.proxy;
import java.lang.reflect.Proxy;
/**
* 为指定的target生成动态代理实例
* @author Slience
*
*/
public class MyProxyFactory {
public static Object getProxy(Object object) throws Exception {
//创建一个InvocationHandler对象
MyInvokationHandler handler = new MyInvokationHandler();
//设置将要代理的对象
handler.setTarget(object);
//创建并返回一个动态代理
//这个动态代理与target实现了同样的接口,所以具有相同的public方法
//所以动态代理对象可以当成target目标对象来使用
//当程序调用了动态代理对象的指定方法,实际上转变为
//执行MyInvokationHandler对象的involke方法
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);
}
}
测试类
package com.proxy;
/**
* 测试Dog代理
* @author Slience
*
*/
public class Test {
public static void main(String[] args) throws Exception {
Dog target = new GunDog();
//制定target来创建动态代理
Dog dog = (Dog) MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
其实这种思想在AOP面向切面编程里被称为AOP代理,AOP代理可以替代目标对象,AOP代理包含目标对象的全部方法,而且在指向目标方法之前之后插入一些动作
命令模式
一个参数和执行方法体的代码也可以改变的方法
或者说把一个代码块当作参数传入的方法,Java8的Lambda表达式
当我们需要做一个接收一个数组的方法,这个方法可能对数组然后调用两次这个方法,第一个调用的时候是将这个数组输出出来,第二次调用的时候将这个数组的所有元素相加。具体实现如下
package com.command;
/**
* 有一个each方法用来处理数组,但是具体如何处理数组还不确定
* @author Slience
*
*/
public class ProcessArray {
public void each(int[] target, Command command) {
command.process(target);
}
}
package com.command;
public interface Command {
//接口里定义的process()方法用于封装“处理行为”
void process(int[] target);
}
测试类
package com.command;
public class CommentTest {
public static void main(String[] args) {
ProcessArray processArray = new ProcessArray();
int[] target = {3, -4, 6, 4};
//第一次处理数组,具体的处理行为取决于Comment对象
processArray.each(target, new Command() {
public void process(int[] target) {
for(int temp: target) {
System.out.println("迭代输出目标数组的元素:" + temp);
}
}
});
System.out.println("________________________");
processArray.each(target, new Command(){
public void process(int[] target) {
int sum = 0;
for(int tmp: target) {
sum += tmp;
}
System.out.println("数组元素总和是:" + sum);
}
});
}
}
挺像javascript的匿名函数的
策略模式
主要用来封装系列算法,使用策略模式可以轻松的切换不同的算法
问题引入:网上开书店,有多种打折策略,VIP打XX折,旧书打XX折,如何对多种打折策略进行选择呢?比较不好的方式是使用switch case进行区分,但是如果有一天新增了一种打折策略SVIP,那么需要修改的地方至少修改三个地方:增加一个常量来代表SVIP打折类型;其次需要增加一个case语句;最后实现SVIP的打折逻辑。为了改变这种设计我们可以使用策略模式来实现此功能。
首先定一个一个打折接口,让所有的打折策略去实现这个接口
package com.strategy;
public interface DiscountStrategy {
//定义一个用户计算打折价的方法
double getDiscount(double originPrice);
}
接下来实现两种打折算法
旧书打折
package com.strategy;
public class OldDiscount implements DiscountStrategy {
@Override
public double getDiscount(double originPrice) {
// TODO Auto-generated method stub
System.out.println("使用旧书折扣...");
return originPrice * 0.7;
}
}
VIP打折
package com.strategy;
public class VipDiscount implements DiscountStrategy {
@Override
public double getDiscount(double originPrice) {
// TODO Auto-generated method stub
System.out.println("使用VIP折扣...");
return originPrice * 0.5;
}
}
之后在写一个DsicountContext类,用来找到合适的打折策略
package com.strategy;
/**
* 扮演决策者,决定调用哪个折扣策略来处理打折
* @author Slience
*
*/
public class DiscountContext {
//组合一个DiscountStrategy对象
private DiscountStrategy strategy;
public DiscountContext(DiscountStrategy strategy) {
// TODO Auto-generated constructor stub
this.strategy = strategy;
}
//根据实际所使用的DiscountStrategy对象获取折扣价
public double getDiscountPrice(double price) {
//如果strategy为null,系统自动选择OldDiscount类
if(strategy == null) {
strategy = new OldDiscount();
}
return this.strategy.getDiscount(price);
}
public void changeDiscount(DiscountStrategy strategy) {
this.strategy = strategy;
}
}
测试类
package com.strategy;
public class StrategyTest {
public static void main(String[] args) {
//没有选择打折策略类,默认用旧书打折策略
DiscountContext discountContext = new DiscountContext(null);
double price = 79;
System.out.println(price + "元的书默认折价后的价格是: " + discountContext.getDiscountPrice(price));
//使用VIP打折策略
discountContext.changeDiscount(new VipDiscount());
System.out.println(price + "元的书VIP折价后的价格是:" + discountContext.getDiscountPrice(price));
//如果需要添加一个SVIP的打折策略的话
//只需要创建一个实现了DiscountStrategy接口的类就好
discountContext.changeDiscount(new SVipDiscount());
System.out.println(price + "元的书SVIP折价后的价格是:" + discountContext.getDiscountPrice(price));
}
}
当我们需要添加一个新策略SVIP的时候就可以直接创建一个打折类
package com.strategy;
public class SVipDiscount implements DiscountStrategy {
@Override
public double getDiscount(double originPrice) {
// TODO Auto-generated method stub
System.out.println("使用SVIP打折");
return originPrice * 0.1;
}
}
之后在测试方法中使用changeDiscount来修改当前的打折策略即可。这样做的缺点在于客户端代码需要和不同的策略类耦合。为了弥补不足可以考虑使用配置文件来指定DiscountContext选择那种打折策略。
门面模式
它可以将一组复杂的类包装到一个简单的外部接口中。
举例说明:一个顾客Customer有一个haveDinner方法,这个方法需要使用三个接口的方法,这三个接口是收银部Payment的pay方法,厨师部Cook的cook方法还有服务生部的Waiter的上菜serve方法。代码如下
package com.facade;
//收银部
public interface Payment {
public String pay();
}
package com.facade;
//厨师部
public interface Cook {
public String cook(String food);
}
package com.facade;
//服务生部
public interface Waiter {
public void serve(String food);
}
以及分别实现以上接口的类
package com.facade;
public class PaymentImpl implements Payment{
@Override
public String pay() {
// TODO Auto-generated method stub
String food = "快餐";
System.out.println("你已经向收银员支付了费用,你购买的食物是:" + food);
return food;
}
}
package com.facade;
public class CookImpl implements Cook {
@Override
public String cook(String food) {
// TODO Auto-generated method stub
System.out.println("厨师正在烹调:" + food);
return food;
}
}
package com.facade;
public class WaiterImpl implements Waiter {
@Override
public void serve(String food) {
// TODO Auto-generated method stub
System.out.println("服务生已经将" + food + "端过来了,请慢用");
}
}
那么我们在实现Customer的haveDinner方法时就可以这样:
public void haveDinner() {
//依次创建三个部门的实例
Payment payment = new PaymentImpl();
Cook cook = new CookImpl();
Waiter waiter = new WaiterImpl();
//依次调用三个部门的实例的方法来实现用餐的功能
String food = payment.pay();
food = cook.cook(food);
waiter.serve(food);
}
如果饭店有很多部门的话,就要一个一个去调用他们的方法围栏解决这个问题我们可以创建一个门面类Facade来包装这些类,对外提供一个简单的访问方法。
门面类Facade
package com.facade;
public class Facade {
//定义三个部门
Payment payment;
Cook cook;
Waiter waiter;
public Facade() {
// TODO Auto-generated constructor stub
this.payment = new PaymentImpl();
this.cook = new CookImpl();
this.waiter = new WaiterImpl();
}
public void serveFood() {
//依次调用三个部门的方法,封装成一个serveFood()方法
String food = payment.pay();
food = cook.cook(food);
waiter.serve(food);
}
}
之后Customer去实现haveDinner就可以变成这样
public void haveDinner() {
Facade facade = new Facade();
facade.serveFood();
}