代理模式——静态代理
什么是代理模式?
概念:为其他对象提供一种代理以控制对这个对象的访问。
生活中这种例子很多,我想去国外买东西自己又不方便,可以找代购啊,代购不仅可以帮我买东西也可以帮其他人买东西,代购就是一种代理;我要打官司自己又不会,找律师帮我代理等等。
为什么要用代理模式?
当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的通透性,委托对象与代理对象需要实现相同的接口。
单例的几种方式
代理模式一般分为静态代理和动态代理,这里我们先介绍静态代理。
静态代理
- 抽象主题类:抽象类或接口
public interface ISubject {
/**
* 一个普通的业务方法
*/
void visit();
}
- 被代理类:实现抽象类
public class RealSubject implements ISubject {
@Override
public void visit() {
// 实现visit具体业务
System.out.println("Real subject!");
}
}
- 代理类:实现抽象类
public class SubjectProxy implements ISubject{
// 持有被代理类的引用
private ISubject mSubject;
public SubjectProxy(ISubject subject) {
mSubject = subject;
}
@Override
public void visit() {
// 调用被代理类的业务方法,这里一般是多态的方法分配
mSubject.visit();
}
}
- 使用:
public class Client {
public static void main(String[] args){
// 创建一个真实主题对象
ISubject realSubject = new RealSubject();
// 通过真实主题对象构造一个代理对象
SubjectProxy proxy = new SubjectProxy(realSubject);
// 调用代理的相关方法
proxy.visit();
}
}
还是不知道怎么用?没关系,举个例子你就明白了,我们以不同版本的API发送通知为例。
public class ProxyActivity extends Activity implements View.OnClickListener{
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mBuilder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proxy);
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class),
PendingIntent.FLAG_CANCEL_CURRENT));
initView();
}
private void initView() {
Button btn = findViewById(R.id.btn_send_notify);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notify:
Notification n = mBuilder.build();
n.contentView = new RemoteViews(getPackageName(),
R.layout.remote_notify_proxy);
mNotificationManager.notify(0, n);
break;
}
}
}
这是我们在Activity中点击按钮发送通知的常规写法,但是Android的API版本迭代很快,特别是Notification这块,后面我们还要兼容不同版本的通知,维护起来相当麻烦,这里我们就可以用代理模式去解决。
public abstract class Notify {
private Context mContext;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mBuilder;
public Notify(Context context) {
this.mContext = context;
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(context);
mBuilder.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class),
PendingIntent.FLAG_CANCEL_CURRENT));
}
/**
* 发送一条通知
*/
public abstract void send();
/**
* 取消一条通知
*/
public abstract void cancel();
}
首先初始化通知的一些公用属性,抽象出两个方法send和cancel由子类去实现。
public class NotifyNomal extends Notify {
public NotifyNomal(Context context) {
super(context);
}
@Override
public void send() {
Notification n = mBuilder.build();
n.contentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy);
mNotificationManager.notify(0, n);
}
@Override
public void cancel() {
mNotificationManager.cancel(0);
}
}
NotifyNomal 定义了一个简单的自定义视图的通知。如果对应API 16以后需要一个大图的通知呢:
public class NotifyBig extends Notify {
public NotifyBig(Context context) {
super(context);
}
@Override
public void send() {
Notification n = mBuilder.build();
n.contentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy);
// API 16以后通知上可以使用大视图
n.bigContentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy_big);
mNotificationManager.notify(0, n);
}
@Override
public void cancel() {
mNotificationManager.cancel(0);
}
}
API 20以后还可以为Notification增加一个headsUpContentView,当我们的APP全屏展示的时候收到通知,这个headsUpContentView会悬浮展示在屏幕顶部。
public class NotifyHeadsUp extends Notify {
public NotifyHeadsUp(Context context) {
super(context);
}
@Override
public void send() {
Notification n = mBuilder.build();
n.contentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy);
n.bigContentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy_big);
// API 20以后可以使用悬浮框通知
n.headsUpContentView = new RemoteViews(mContext.getPackageName(),
R.layout.remote_notify_proxy);
mNotificationManager.notify(0, n);
}
@Override
public void cancel() {
mNotificationManager.cancel(0);
}
}
最后定义一个代理类来整合上面的几个类。
public class NotifyProxy extends Notify{
private Notify mNotify;
public NotifyProxy(Context context) {
super(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
mNotify = new NotifyHeadsUp(context);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
mNotify = new NotifyBig(context);
}else {
mNotify = new NotifyNomal(context);
}
}
@Override
public void send() {
mNotify.send();
}
@Override
public void cancel() {
mNotify.cancel();
}
}
现在再来看看在Activity中发送一个通知的代码,怎么样,只需要一句代码就可以根据不同版本发送对应版本的通知即可,很大程度减少了Activity中的代码量,不仅如此如果后续还有新的API新增,我们也只需要新增一种通知类就可以达到效果,可扩展性很强。
public class ProxyActivity extends Activity implements View.OnClickListener{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proxy);
initView();
}
private void initView() {
Button btn = findViewById(R.id.btn_send_notify);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notify:
// 点击发送通知
new NotifyProxy(ProxyActivity.this).send();
break;
}
}
}
代理模式的缺点
这里我们看到新增的通知类里面还是有一些重复代码,如果加上装饰模式后会更简洁;代理模式唯一的缺点大概就是类的增加,这也是所有设计模式的通病了吧,不过设计模式带来的好处远远完全可以忽略这个小瑕疵。
静态代理模式的其他应用场景
假如我们做同一件事有几种不同的实现方式,就可以抽象出一个抽象接口,根据场景替换不同的实现方式。这里就可以用于一些三方框架的替换,比方说网络框架有Volly、OKHttp、OKHttp+Retrofit等等后面可能还会出现新的主流框架,不管哪种框架他们的调用都是get、post、put...这些,抽象出请求接口,不同的框架去具体实现,能做到不用修改业务逻辑用最少的代码替换一个网络库的效果。