设计模式实践-代理模式
什么是代理模式?什么时候用?
代理模式,也称为委托模式。代理模式可为其他对象提供一种代理的方式,控制被代理对象的访问。代理模式可以屏蔽繁杂的内部实现,替换内部实现时,外部无需改动。代理模式又分为静态代理和动态代理。
怎么实现静态代理模式?
使用代理模式,一般会以下类:
-
Subject,主题接口,定义Api。
-
RealSubject,真实主题实现,实现了Subject接口,也是被代理的对象。
-
ProxySubject,代理对象,也实现了Subject接口,持有真实主题的实现。在构造方法或提供set方法注入RealSubject。复写在对应需要代理的方法,在调用RealSubject同样方法的前后做代理增强或拦截。
静态代理,数据库表操作处理事务
例如在数据库User表中插入一个User数据,都会在开始前开始事务,结束时提交事务。而如果数据库操作每个数据库操作都写一遍事务处理,便有很多冗余代码。那么代理模式可以怎么解决呢?
实现步骤
-
User实体类,User。
-
Dao接口,定义数据库操作Api。
-
DaoImpl,Dao接口实现类,内部进行Api实现。
-
DaoTransactionProxy,Dao接口代理对象,也实现了Dao接口。持有DaoImpl实例。
具体伪代码
- 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 +
'}';
}
}
- Dao接口,定义addUser(),插入一条用户信息。
public interface IUserDao {
/**
* 插入一条用户信息
*/
void addUser(User user);
}
- 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);
}
}
- 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("---------- <静态代理>提交事务 ----------");
}
}
}
- 使用和执行结果
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的动态代理,它只能代理接口。它涉及的部分有:
-
Proxy类,JDK的动态代理使用的是该Proxy类中的方法。
-
newProxyInstance()方法,在Proxy类中,使用该方法给接口生成一个代理类。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
-
newProxyInstance()方法解析
- 参数一:ClassLoader,为被代理类的ClassLoader,我们用被代理对象,调用getClassLoader()即可。
- 参数二:Class<?>[] interfaces,被代理类上实现的接口,同样被代理对象上有getInterfaces()方法,调用获取即可。
- 参数三:InvocationHandler,调用处理接口,可以理解为,被代理对象的方法被调用时会回调,(就是一个接口回调)我们在这个回调里去进行我们的代理增强即可。
-
InvocationHandler接口,只有一个invoke()方法需要我们实现。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
-
invoke()方法解析
- 参数一:Object proxy,为代理类实例。
- 参数二:Method method,调用的方法信息,都保存在这个Method对象。
- 参数三:Object[] args,调用方法的参数。
- 返回值: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,那么刚好满足我们的需求。
涉及的类
-
IAudioRoomEngine,直播引擎接口,定义登录、登出房间、开、关推流等Api。
-
AudioRoomEngineImplByZego,直播引擎具体实现。
-
IRoomIMEngine,直播聊天引擎接口,定义发送消息、接收消息等Api。
-
RoomIMEngineImplByTim,直播聊天引擎具体实现。
-
RoomEngineDelegate,引擎代理,实现了IAudioRoomEngine和IRoomIMEngine接口,是引擎对外的代理。
实现步骤
- 定义直播引擎接口,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);
}
- 定义直播引擎和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) {
//...发送消息
}
}
- 代理类,实现直播引擎、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,并且可以在前后增强原始实现或拦截原始实现。
-
代理模式缺点:设计模式通病,会增加子类数量。