设计模式实践-代理模式

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

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

怎么实现静态代理模式?

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

  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 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 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 loginRoom(ILoginRoomOptions joinRoomOptions);
    
    /**
     * 发布直播推流
     */
    Observable startPublish();

    //...省略其他Api
}

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

//IM引擎实现
public class RoomIMEngineImplByTim implements IRoomIMEngine {
    @Override
    public Observable login(String id, String sign) {
        //...登录聊天室
    }
    
    @Override
    public Observable 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 loginRoom(ILoginRoomOptions joinRoomOptions) {
        checkAudioRoomEngine(mAudioRoomEngineImpl);
        return mAudioRoomEngineImpl.loginRoom(joinRoomOptions);
    }
    
    @Override
    public Observable startPublish() {
        checkAudioRoomEngine(mAudioRoomEngineImpl);
        return mAudioRoomEngineImpl.startPublish();
    }
    
    @Override
    public Observable login(String id, String sign) {
        checkRoomIMEngine(mRoomIMEngine);
        return mRoomIMEngine.login(id, sign);
    }
    
    @Override
    public Observable sendNormal(boolean group, String conversationId, IMSendUserModel sendUser, IIMBaseModel msg) {
        checkRoomIMEngine(mRoomIMEngine);
        return mRoomIMEngine.sendNormal(group, conversationId, sendUser, msg);
    }
}

总结

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

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

你可能感兴趣的:(设计模式实践-代理模式)