好了,正传开始,此处已无男神,区区在下小女子目下无尘,只有代码。
现在,我们开始假装要搞一个商城了。商城先得有商品吧,有商品就得把商品存到数据库里去吧。那我们就在此顺势建一个将商品持久化的类。(注:此处可能有小朋友会举手问什么是持久化,持久化你目前可以理解为就是把商品的数据保存到数据库里,以后想看,可以拿出来看。持久化了的东东,就不是只活在内存里,一关机就不见了。而是放到数据库里,保存到硬盘上了的。如果还有小朋友想问什么是内存,什么是硬盘,来,我慢慢告诉你,放心,我不得打小朋友的)。
将商品保存到数据库,增删改查是必须的。但查暂时不理它。(注:查我们要单独处理,容后再说。)
就此决定,先建三个方法:增删改:
第一代代码
商品持久化类 ProductDaoImpl
public class ProductDaoImpl { |
客户端测试
正常运行,打印出"删除商品" 没有问题。
好了,这三个特别特别复杂的方法我们写好了,核心功能都实现了(是吧,是吧,都打印出来了的)。然后,对数据库操作,得有事务吧(不懂什么是事务的小朋友请举手,我们另外处理。),得写日志吧,还得先判断当前登录用户对这个商品是否有操作权限吧?(注:看明白了吧?至少查确实不能开个事务来独占资源,所以不一起说。)
于是,这三个特别复杂的方法瞬间又上一个档次的复杂了起来。
第二代代码
代码这么多,还大多是重复的,这不应该是人干的活,这应该是ChatGPT干的事儿哇~~纵观以上代码,每个方法中,核心业务只有一行,边边角角,又必须存在的代码有4行之多。所以,我们必须想个办法消灭它们,让我们核心突出。
主角得有主角的待遇。
所以,我们另外建一个类,专注处理边边角角,可以吗?那是当然。来,走起。
新建一个代理类ProxyDao
1. 首先,这个代理类有商品持久化类ProductDaoImpl的各同名方法。
2. 类里只需要一个成员变量productDao,该成员变量的类型就是ProductDaoImpl。
private ProductDaoImpl productDao; |
3. 要保证只要代理类ProxyDao的对象被创建,其属性productDao就必须是一个已构建好的ProductDaoImpl的对象,而不会是null。所以用带参构造方法给productDao赋初值。ProxyDao只有一个构造方法,是带一个参数的构造方法,参数就是ProductDaoImpl的对象。
public ProxyDao(ProductDaoImpl productDao){ this.productDao=productDao; } |
4. 在代理类ProxyDao的各方法中,调用属性productDao的同名方法。
public class ProxyDao { private ProductDaoImpl productDao; public ProxyDao(ProductDaoImpl productDao){ this.productDao=productDao; } public void insert(); productDao.insert(); } public void update(){ productDao.update(); } public void delete(){ productDao.delete(); } } |
代码写到这里,相信大家也看出来了,我们使用这个代理类ProxyDao的各个方法,与直接使用商品持久化类ProductDaoImpl的各个方法,产生的效果是一样的。那么,如果我们在代理类ProxyDao的各方法前后各加一些边边角角的内容呢? 象这样?
public class ProxyDao { private ProductDaoImpl productDao; public ProxyDao(ProductDaoImpl productDao){ this.productDao=productDao; } public void insert(){ System.out.println("判断是否有操作权限"); System.out.println("开始事务"); productDao.insert(); System.out.println("结束事务"); System.out.println("写日志"); } public void update(){ System.out.println("判断是否有操作权限"); System.out.println("开始事务"); productDao.update(); System.out.println("结束事务"); System.out.println("写日志"); } public void delete(){ System.out.println("判断是否有操作权限"); System.out.println("开始事务"); productDao.delete(); System.out.println("结束事务"); System.out.println("写日志"); } } |
这样是不是商品持久化类ProductDaoImpl中的代码,只需要保留核心的那一行代码就可以了?
此时客户端测试,就不是直接调商品持久化类ProductDaoImpl对象的方法了,而是调代理类ProxyDao对象的方法。
public class ProxyTest { public static void main(String[] args) { ProductDaoImpl productDao=new ProductDaoImpl (); ProxyDao proxyDao=new ProxyDao(productDao); proxyDao.delete(); } |
再观察一下,我们还可以把代理类ProxyDao中重复的代码再抽一抽,该封装就封装一哈。
在客户端,productDao这个变量也只是做为参数传一传,就没用了。调方法根本不关它事,于是,感觉它可以不必拥有姓名?我们在new 代理类对象的时候,传一个商品持久化类ProductDaoImpl的匿名对象进去,不香吗?外面根本找不到,不用在客户端扰我视线,乱我思维。
于是最后代码成了这样
第三代代码
代理类ProxyDao
属性productDao是商品持久化类ProductDaoImpl的对象。并通过一个有参构造进行初始化。
ProxyDao类中有与ProductDaoImpl类相同的public方法,用于调用ProductDaoImpl类中的同名方法,处理核心业务。并在调用前后,进行事务、日志、判断操作权限等非核心的相关业务的处理。
注:此例中,将核心功能前后的业务处理都设计为相同,并分别封装在begin()和last()两个私有方法中。实际应用中,可根据具体情况进行处理。
被代理的类
商品持久化类ProductDaoImpl又恢复了原样,边边角角都交给别人去处理了,拒绝拼盘,从代理做起。
测试类
此时客户端测试
运行结果:
代理了?对不对?代理了哇。别激动,这才刚开始呢。
这个时候呢,我们就得思考了,商城哇,怎么也得有订单吧?订单呢?要不来个订单持久化先?
于是订单持久化来了
新增的类订单持久化类OrderDaoImpl
那订单持久化的边边角角怎么办呢?又搞一个代理类?格局小了吧~~
此时,理应接口闪亮登场了~~请跟着我念:面向接口编程~~
增加一个接口,并修改两个地方:
1. 接口有增删改三个方法,与商品持久化类ProductDaoImpl的方法一致。
2. 订单持久化类OrderDaoImpl和商品持久化类ProductDaoImpl都实现这个接口
3. 代理类ProxyDao的成员变量改为接口,构造方法的形参也改为接口。
代码如下:
第四代代码
接口IGeneralDao
代理类ProxyDao
public class ProxyDao { private IGeneralDao generalDao; public ProxyDao(IGeneralDao generalDao){ this.generalDao=generalDao; } public void insert(){ begin(); generalDao.insert(); last(); } public void update(){ begin(); generalDao.update(); last(); } public void delete(){ begin(); generalDao.delete(); last(); } private void begin(){ System.out.println("判断是否有操作权限"); System.out.println("开始事务"); } private void last(){ System.out.println("结束事务"); System.out.println("写日志"); } } |
两个被代理的类
商品持久化类ProductDaoImpl
public class ProductDaoImpl implements IGeneralDao { public void insert(){ System.out.println("新增商品"); } public void update(){ System.out.println("修改商品"); } public void delete(){ System.out.println("删除商品"); } } |
订单持久化类OrderDaoImpl
public class OrderDaoImpl implements IGeneralDao { public void insert(){ System.out.println("新增订单"); } public void update(){ System.out.println("修改订单"); } public void delete(){ System.out.println("删除订单"); } } |
写到这里,是不是觉得想怎么代理就怎么代理了?轻松方便?
搞个测试?
测试类
运行结果:
好象挺完美了?世界有我,我有世界~~
但是,你以为就这样结束了吗?告诉你,并没有~~~
那设计模式,不是开玩笑的,是老几辈优秀的科学家智慧的结晶,严谨得很的~~(注:是的,long long ago之前的程序员这活,只有科学家才干得了~~)
谁能思考出哪里不严谨吗?举手没奖。
那个,代理类ProxyDao的几个public方法,是不是要和被代理的类的方法一致呀?所以呢,ProxyDao是不是也可以和被代理的类实现同一个接口呢?这样就能保证两者的方法一致,不至于被写错了?请再跟着我念:面向接口编程~~
所以,最后一改,就是把ProxyDao类改成接口的实现类。
本次静态代理模式示例代码最终的完整呈现如下:
静态代理模式代码
接口
代理类
public class ProxyDao implements IGeneralDao { private IGeneralDao generalDao; public ProxyDao(IGeneralDao generalDao){ this.generalDao=generalDao; } public void insert(){ begin(); generalDao.insert(); last(); } public void update(){ begin(); generalDao.update(); last(); } public void delete(){ begin(); generalDao.delete(); last(); } private void begin(){ System.out.println("判断是否有操作权限"); System.out.println("开始事务"); } private void last(){ System.out.println("结束事务"); System.out.println("写日志"); } } |
两个被代理的类
商品持久化类
订单持久化类
测试类
public class ProxyTest { public static void main(String[] args) { IGeneralDao proxyDao=new ProxyDao(new ProductDaoImpl()); proxyDao.delete(); System.out.println("-----------------------------"); proxyDao=new ProxyDao(new OrderDaoImpl()); proxyDao.insert(); } } |
运行结果:
好了,静态代理打完收工。都到这份上了,类图可以自己画否?