代理模式详解(一)-静态代理
一直在想如何讲好这个在实际中应用非常多,又不希望是千篇一律教科书般的说教。的确,代理模式非常重要,作为一个软件设计中基本的设计技巧,我们常常可以看到它的身影。甚至在我们的生活中,也可以看到各式各样代理的场景,比如租房中介、售票黄牛、快递、游戏代练都是代理模式的具体体现。
如果有人对我提到代理模式,我甚至会忘记代理模式的官方定义,唉,还是放弃那些枯燥无味的东西吧。代理模式不就是一个对象想做一件事情,但是由于某种原因自己不想干了或者干不了,交给代理对象去干,自己又省心又省力,多么美好的事情。但是也有《大话设计模式》一书中的例子,有可能给别人做了嫁衣。
咱们还是言归正传,来看看游戏代练如何在代码中体现。小A同学很喜欢玩王者荣耀,但是奈何怎么玩都只能在钻石段位徘徊,还差一局死活上不去,又很嫌弃钻石的队友太菜而且还坑,怎么办呢?他想请身边的大神朋友帮他打上星耀,于是他找了某王者大神小B,小B很爽快的答应了,但是总不能帮小A白打。于是对小A说:“你请我吃顿饭,我就帮你打”。小A听后,一顿饭不是不能接受,于是就答应了。
定义一个玩家接口:
public interface IGamePlayer { //登陆王者荣耀游戏 public void login(String userName,String password); //赢了一局 public void win(); //升段位 public void upgrade(); }
定义玩家对象:
public class GamePlayer implements IGamePlayer { //玩家名称 private String name; private String userName; public GamePlayer() { this.name = name; } public GamePlayer(String name) { this.name = name; } @Override public void login(String userName, String password) { this.userName = userName; System.out.println(this.name+"登陆游戏,"+"用户名为:"+userName); } @Override public void win() { System.out.println(this.userName+" 玩家赢了一局"); } @Override public void upgrade() { System.out.println("恭喜:" +this.userName+" 玩家上到星耀了"); } }
定义代打对象:
public class GamePlayerProxy implements IGamePlayer { //代练者名字 private String name; //被代练的玩家 private GamePlayer gamePlayer; public GamePlayerProxy(String name, GamePlayer gamePlayer) { this.name = name; this.gamePlayer = gamePlayer; } @Override public void login(String userName, String password) { before(userName); gamePlayer.login(userName,password); } @Override public void win() { this.gamePlayer.win(); } @Override public void upgrade() { this.gamePlayer.upgrade(); after(); } private void before(String userName){ System.out.println(this.name+"正在使用用户名"+userName+"登陆游戏"); } private void after(){ System.out.printf(this.name+":打上星耀了,一顿饭到手!"); } }
客户端执行:
public static void main(String[] args) { GamePlayer A = new GamePlayer("小A"); //定义玩家小A GamePlayerProxy playerProxy = new GamePlayerProxy("小B",A); //定义代打玩家小B playerProxy.login("我是小A呀","123"); //登陆账号 playerProxy.win(); //赢了一局 playerProxy.upgrade(); //上星耀了 }
运行结果:
小B正在使用用户名我是小A呀登陆游戏
小A登陆游戏,用户名为:我是小A呀
我是小A呀 玩家赢了一局
恭喜:我是小A呀 玩家上到星耀了
小B:打上星耀了,一顿饭到手!
这样小A终于打上星耀了,小B也如愿以偿的吃到了一份免费的午餐。
小结:
在代码中,一般代理会被理解为功能的增强,实际上就是在原代码逻辑前后增加一些代码逻辑。使用代理模式主要有两个目的:一是保护目标对象,而是增强目标对象的功能。
以上案例作为通过一个场景阐述了代理模式,相信小伙伴们应该理解了代理模式的常见写法啦。代理模式也有其官方的定义:Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问)。代理模式的通用类图如下:
我们再来看看这个类结构图,代理模式真的很简单!代理类与被代理类实现了同一个接口,代码也很简单,我这里也就不在赘述了。
我再来举一个我们实际工作中可能会用的例子。需求是这样的:由于电商系统的在双11的订单量巨大,我们需要通过时间条件分库来缓解单个数据库的压力。如何通过代理来实现呢?请看下文。
注: 为了方便演示,本项目示例基于springboot编写,示例源码地址:https://github.com/peis40666/project-study-demo/tree/master/dyndatasource
现在有一个实体类OraderEntity:
/** * 订单实体类 */ @Data public class OrderEntity { private String id; private Object orderInfo; // 订单的详细信息 private Long createTime; //订单的创建时间 }
实现把订单的实例写入数据库中,orderDao:
@Repository public class OrderDao { public int insert(OrderEntity order){ System.out.println("创建订单成功"); //主要是演示原理 return 1; } }
订单业务接口,OrderService:
public interface OrderService { public int createOrder(OrderEntity order); }
订单业务接口的实现类,OrderServiceImpl
@Service("orderService") public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Override public int createOrder(OrderEntity order) { System.out.println("OrderService调用orderDao创建订单订单"); return orderDao.insert(order); } }
数据源配置类,DataSourceProperties、DynamicDataSourceConfig
@Data public class DataSourceProperties { private String userName; private String password; private String driverClassName; private String url; }
@Data @Component @ConfigurationProperties(prefix = "dynamic") public class DynamicDataSourceConfig { private String primary; private Map<String,DataSourceProperties> dataSource = new HashMap<>(); //**为了简述原理,这里提供一个路由方法 public void router(Integer router){ String dbnameprefix = "mysql_"; System.out.println("动态分配数据源名称:"+dbnameprefix+router); //打印数据源信息 System.out.println("打印数据源信息"+dataSource.get(dbnameprefix+router).toString()); } }
重头戏来了,实现动态路由的代理类,OrderServiceStaticProxy,主要是通过时间戳的奇偶来判断分库。
@Service("orderServiceStaticProxy") public class OrderServiceStaticProxy implements OrderService{ @Autowired private OrderService orderService; @Autowired private DynamicDataSourceConfig config; @Override public int createOrder(OrderEntity order) { before(order.getCreateTime()); return orderService.createOrder(order); } private void before(Long createTime){ //根据时间戳 奇数就路由到mysql_1,偶数就路由到mysql_2 config.router(getOddOrEven(createTime)); } /** * 判断是奇数还是偶数 * 奇数返回1 * 偶数返回2 * @param x * @return */ private int getOddOrEven(Long x){ if(x % 2 == 0){ return 2; } return 1; } }
配置信息:
测试代码:
@Autowired private OrderServiceStaticProxy proxy; @Test public void Test(){ OrderEntity order1 = new OrderEntity(); order1.setId("1"); order1.setCreateTime(new Date().getTime()); order1.setOrderInfo("{订单1}"); System.out.println(order1.toString()); proxy.createOrder(order1); OrderEntity order2 = new OrderEntity(); order2.setId("1"); order2.setCreateTime(new Date().getTime()); order2.setOrderInfo("{订单2}"); System.out.println(order2.toString()); proxy.createOrder(order2); }
测试结果:
OrderEntity(id=1, orderInfo={订单1}, createTime=1587194702311)
动态分配数据源名称:mysql_1
打印数据源信息DataSourceProperties(userName=db01, password=123456, driverClassName=com.mysql.jdbc.Driver, url=jdbc:mysql://192.168.0.104:3306/db01?Unicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false)
OrderService调用orderDao创建订单订单
创建订单成功
OrderEntity(id=1, orderInfo={订单2}, createTime=1587194702312)
动态分配数据源名称:mysql_2
打印数据源信息DataSourceProperties(userName=db02, password=123456, driverClassName=com.mysql.jdbc.Driver, url=jdbc:mysql://192.168.0.104:3306/db02?Unicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false)
OrderService调用orderDao创建订单订单
创建订单成功
通过代理就比较简单的实现了数据库的分库存储了。但是以上代理也只能为order数据进行路由了,如果再来个其他的什么数据也需要进行分库存储,那我是不是还需要基于这个再开发一个别的什么代理呢?那也太麻烦了,我能不能只通过一个代理类代理所有这种类似的呢?答案当然可以,java给我们提供了更高级的代理--->动态代理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】