设计模式实践-代理模式

什么是代理模式?什么时候用?

代理模式,也称为委托模式。代理模式可为其他对象提供一种代理的方式,控制被代理对象的访问。代理模式可以屏蔽繁杂的内部实现,替换内部实现时,外部无需改动。代理模式又分为静态代理和动态代理。

怎么实现静态代理模式?

使用代理模式,一般会以下类:

  1. Subject,主题接口,定义Api。

  2. RealSubject,真实主题实现,实现了Subject接口,也是被代理的对象。

  3. ProxySubject,代理对象,也实现了Subject接口,持有真实主题的实现。在构造方法或提供set方法注入RealSubject。复写在对应需要代理的方法,在调用RealSubject同样方法的前后做代理增强或拦截。

静态代理,数据库表操作处理事务

例如在数据库User表中插入一个User数据,都会在开始前开始事务,结束时提交事务。而如果数据库操作每个数据库操作都写一遍事务处理,便有很多冗余代码。那么代理模式可以怎么解决呢?

实现步骤

  1. User实体类,User。

  2. Dao接口,定义数据库操作Api。

  3. DaoImpl,Dao接口实现类,内部进行Api实现。

  4. DaoTransactionProxy,Dao接口代理对象,也实现了Dao接口。持有DaoImpl实例。

具体伪代码

  1. User实体类。
public class User implements Serializable {
    private static final long serialVersionUID = -5645207145201169067L;

    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    //...省略get、set方法

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. Dao接口,定义addUser(),插入一条用户信息。
public interface IUserDao {
    /**
     * 插入一条用户信息
     */
    void addUser(User user);
}
  1. UserDaoImpl,实现addUser()的插入方法。
public class UserDaoImpl implements IUserDao {
    private ArrayList<User> mUserList;

    public UserDaoImpl() {
        mUserList = new ArrayList<>();
    }

    @Override
    public void addUser(User user) {
        mUserList.add(user);
        System.out.println("成功插入了一条数据:" + user);
    }
}
  1. DaoTransactionProxy,构造方法传入被代理对象。在addUser()方法中,对被代理对象的addUser()进行增强。甚至可以退换。
public class DaoTransactionProxy implements IUserDao {
    private IUserDao realDao;

    public DaoTransactionProxy(IUserDao realDao) {
        this.realDao = realDao;
    }

    @Override
    public void addUser(User user) {
        System.out.println("---------- <静态代理>开启事务 ----------");
        try {
            realDao.addUser(user);
        } finally {
            System.out.println("---------- <静态代理>提交事务 ----------");
        }
    }
}
  1. 使用和执行结果
public static void main(String[] args) {
    User user = new User("Wally", 20);
    //静态代理
    staticProxyUserDao(user);
}

/**
 * 静态代理
 */
private static void staticProxyUserDao(User user) {
    IUserDao dao = new UserDaoImpl();
    DaoTransactionProxy daoProxy = new DaoTransactionProxy(dao);
    daoProxy.addUser(user);
}
//输出
---------- <静态代理>开启事务 ----------
成功插入了一条数据:User{name='Wally', age=20}
---------- <静态代理>提交事务 ----------

使用动态代理改造

在上面的数据库表事务静态代理例子中,我们使用了静态代理方式进行代理,那么动态代理是怎样的呢?

此处的动态代理指的是JDK的动态代理,它只能代理接口。它涉及的部分有:

  1. Proxy类,JDK的动态代理使用的是该Proxy类中的方法。

  2. newProxyInstance()方法,在Proxy类中,使用该方法给接口生成一个代理类。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
  • newProxyInstance()方法解析

    1. 参数一:ClassLoader,为被代理类的ClassLoader,我们用被代理对象,调用getClassLoader()即可。
    2. 参数二:Class<?>[] interfaces,被代理类上实现的接口,同样被代理对象上有getInterfaces()方法,调用获取即可。
    3. 参数三:InvocationHandler,调用处理接口,可以理解为,被代理对象的方法被调用时会回调,(就是一个接口回调)我们在这个回调里去进行我们的代理增强即可。
  • InvocationHandler接口,只有一个invoke()方法需要我们实现。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
  • invoke()方法解析

    1. 参数一:Object proxy,为代理类实例。
    2. 参数二:Method method,调用的方法信息,都保存在这个Method对象。
    3. 参数三:Object[] args,调用方法的参数。
    4. 返回值:Object,如果代理的方法有返回值则不为null,否则为null。
  • 拦截和放行

前面说过我们的代理可以增强或者拦截,那么静态代理中怎么进行增强和拦截呢?

  • method对象保存了调用的方法信息,而method对象中有invoke方法,调用invoke方法,传入我们的被代理类引用和方法参数,这样就是放行被代理类的代用操作。

  • 那么拦截呢?就是不调用invoke方法,就为拦截。是不是很简单呢。

具体代码

public static void main(String[] args) {
    //动态代理
    dynamicProxyUserDao(user);
}

/**
 * 动态代理
 */
private static void dynamicProxyUserDao(User user) {
    IUserDao dao = new UserDaoImpl();
    Class<? extends IUserDao> clazz = dao.getClass();
    IUserDao proxyDao = (IUserDao) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("addUser")) {
                System.out.println("---------- <动态代理>开启事务 ----------");
                Object result;
                try {
                    result = method.invoke(dao, args);
                } finally {
                    System.out.println("---------- <动态代理>提交事务 ----------");
                }
                return result;
            }
            return null;
        }
    });
    //调用代理插入数据
    proxyDao.addUser(user);
}
---------- <动态代理>开启事务 ----------
成功插入了一条数据:User{name='Wally', age=20}
---------- <动态代理>提交事务 ----------

代理模式实践,直播间多引擎松耦合

这次开发中,使用了即构直播SDK来做直播,为了好拓展(双引擎)和好替换,采用代理模式和接口形式解耦。上面说到代理模式可以隐藏实现,对外提供统一的接口Api,那么刚好满足我们的需求。

涉及的类

  1. IAudioRoomEngine,直播引擎接口,定义登录、登出房间、开、关推流等Api。

  2. AudioRoomEngineImplByZego,直播引擎具体实现。

  3. IRoomIMEngine,直播聊天引擎接口,定义发送消息、接收消息等Api。

  4. RoomIMEngineImplByTim,直播聊天引擎具体实现。

  5. RoomEngineDelegate,引擎代理,实现了IAudioRoomEngine和IRoomIMEngine接口,是引擎对外的代理。

实现步骤

  1. 定义直播引擎接口,IAudioRoomEngine,IM引擎接口,IRoomIMEngine。
//直播引擎接口
public interface IAudioRoomEngine {
    //...省略其他Api
    
     /**
     * 登录房间
     */
    Observable<Boolean> loginRoom(ILoginRoomOptions joinRoomOptions);
    
    /**
     * 发布直播推流
     */
    Observable<Boolean> startPublish();

    //...省略其他Api
}

//IM引擎接口
public interface IRoomIMEngine {
    /**
     * 登录
     */
    Observable<IMResultModel> login(String id, String sign);
    
    /**
     * 发送消息
     */
    Observable<ImMessageInfo> sendNormal(boolean group, String conversationId, IMSendUserModel sendUser, IIMBaseModel msg);
}
  1. 定义直播引擎和IM引擎具体实现类
//直播引擎实现
public class AudioRoomEngineImplByZego implements IAudioRoomEngine {
    @Override
    public Observable<Boolean> loginRoom(ILoginRoomOptions joinRoomOptions) {
        //...登录直播间
    }
    
    @Override
    public Observable<Boolean> startPublish() {
        //...开推流
    }
}

//IM引擎实现
public class RoomIMEngineImplByTim implements IRoomIMEngine {
    @Override
    public Observable<IMResultModel> login(String id, String sign) {
        //...登录聊天室
    }
    
    @Override
    public Observable<ImMessageInfo> sendNormal(boolean group, String conversationId, IMSendUserModel sendUser, IIMBaseModel msg) {
        //...发送消息
    }
}
  1. 代理类,实现直播引擎、IM引擎接口。代理Api接口,可以做参数校验,方法增强等。
public class RoomEngineDelegate implements IAudioRoomEngine, IRoomIMEngine {
    private IAudioRoomEngine mAudioRoomEngineImpl;
    private IRoomIMEngine mRoomIMEngineImpl;

    public AudioRoomEngineDelegate() {
        //创建被代理类,直播引擎和IM引擎
        mAudioRoomEngineImpl = new AudioRoomEngineImplByZego();
        mRoomIMEngineImpl = new RoomIMEngineImplByTim();
    }
    
    //检查直播引擎
    private void checkAudioRoomEngine(IAudioRoomEngine impl) {
        ObjectUtil.requireNonNull(impl, "AudioRoomEngine实现为null,请确保初始化时传入实现或调用相应的attach系列方法");
    }
    
    /**
     * 检查IM引擎
     */
    private void checkRoomIMEngine(IRoomIMEngine impl) {
        ObjectUtil.requireNonNull(impl, "RoomIMEngine实现为null,请确保初始化时传入实现或调用相应的attach系列方法");
    }

    @Override
    public Observable<Boolean> loginRoom(ILoginRoomOptions joinRoomOptions) {
        checkAudioRoomEngine(mAudioRoomEngineImpl);
        return mAudioRoomEngineImpl.loginRoom(joinRoomOptions);
    }
    
    @Override
    public Observable<Boolean> startPublish() {
        checkAudioRoomEngine(mAudioRoomEngineImpl);
        return mAudioRoomEngineImpl.startPublish();
    }
    
    @Override
    public Observable<IMResultModel> login(String id, String sign) {
        checkRoomIMEngine(mRoomIMEngine);
        return mRoomIMEngine.login(id, sign);
    }
    
    @Override
    public Observable<ImMessageInfo> sendNormal(boolean group, String conversationId, IMSendUserModel sendUser, IIMBaseModel msg) {
        checkRoomIMEngine(mRoomIMEngine);
        return mRoomIMEngine.sendNormal(group, conversationId, sendUser, msg);
    }
}

总结

  • 代理模式优点:可以屏蔽繁杂的内部逻辑,输出清晰的Api,并且可以在前后增强原始实现或拦截原始实现。

  • 代理模式缺点:设计模式通病,会增加子类数量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352