代理模式详解(一)-静态代理

  一直在想如何讲好这个在实际中应用非常多,又不希望是千篇一律教科书般的说教。的确,代理模式非常重要,作为一个软件设计中基本的设计技巧,我们常常可以看到它的身影。甚至在我们的生活中,也可以看到各式各样代理的场景,比如租房中介、售票黄牛、快递、游戏代练都是代理模式的具体体现。

  如果有人对我提到代理模式,我甚至会忘记代理模式的官方定义,唉,还是放弃那些枯燥无味的东西吧。代理模式不就是一个对象想做一件事情,但是由于某种原因自己不想干了或者干不了,交给代理对象去干,自己又省心又省力,多么美好的事情。但是也有《大话设计模式》一书中的例子,有可能给别人做了嫁衣。

  咱们还是言归正传,来看看游戏代练如何在代码中体现。小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给我们提供了更高级的代理--->动态代理。

 

  

posted @   梦里藏梦、  阅读(415)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
点击右上角即可分享
微信分享提示