联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

安卓:优雅的处理首页弹框逻辑:责任链模式

作者:访客发布时间:2023-10-30分类:程序开发学习浏览:207


导读:背景随着业务的发展,首页的弹窗越来越多,隐私政策弹窗,广告弹窗,好评弹窗,应用内更新弹窗等等.并且弹框显示还有要求,比如:用户本次使用应用程序、只能显示一个弹框,毕竟谁都不愿意打...

背景

随着业务的发展,首页的弹窗越来越多,隐私政策弹窗,广告弹窗,好评弹窗,应用内更新弹窗等等. 并且弹框显示还有要求,比如:

  • 用户本次使用应用程序、只能显示一个弹框,毕竟谁都不愿意打开应用程序就看到一堆弹框
  • 这些弹框有优先级:如隐私政策弹窗优先级肯定比好评弹窗高,所以希望优先级高的优先显示
  • 广告弹框只展示一次
  • 等等

如何优雅的处理这个逻辑呢?请出我们的主角:责任链模式.

责任链模式

🌰举个栗子

一位男性在结婚之前有事要和父母请示,结婚之后要请示妻子,老了之后就要和孩子们商量.作为决策者的父母、妻子或孩子,只有两种选择:要不承担起责任来,允许或不允许相应的请求;要不就让他请示下一个人,下面来看如何通过程序来实现整个流程.

先看一下类图:
安卓:优雅的处理首页弹框逻辑:责任链模式
类图非常简单、iHandler上三个决策对象的接口。

//决策对象的接口
public interface IHandler {
    //处理请求
    void HandleMessage(IMan man);
}
//决策对象:父母
public class Parent implements IHandler {
    @Override
    public void HandleMessage(IMan man) {
        System.out.println("孩子向父母的请求是:" + man.getRequest());
        System.out.println("父母的回答是:同意");
    }
}
//决策对象:妻子
public class Wife implements IHandler {
    @Override
    public void HandleMessage(IMan man) {
        System.out.println("丈夫向妻子的请求是:" + man.getRequest());
        System.out.println("妻子的回答是:同意");
    }
}
//决策对象:孩子
public class Children implements IHandler{
    @Override
    public void HandleMessage(IMan man) {
        System.out.println("父亲向孩子的请求是:" + man.getRequest());
        System.out.println("孩子的回答是:同意");
    }
}

IMAN上男性的接口:

public interface IMan {
    int getType(); //获取个人状况
    String getRequest(); //获取个人请示(这里就简单的用String)
}
//具体男性对象
public class Man implements IMan {
    /**
     * 通过一个int类型去描述男性的个人状况
     * 0--幼年
     * 1--成年
     * 2--年迈
     */
    private int mType = 0;
    //请求
    private String mRequest = "";

    public Man(int type, String request) {
        this.mType = type;
        this.mRequest = request;
    }

    @Override
    public int getType() {
        return mType;
    }

    @Override
    public String getRequest() {
        return mRequest;
    }
}

最后我们看下一下场景类:

public class Client {
    public static void main(String[] args) {
        //随机生成几个man
        Random random = new Random();
        ArrayList<IMan> manList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            manList.add(new Man(random.nextInt(3), "5块零花钱"));
        }
        //定义三个请示对象
        IHandler parent = new Parent();
        IHandler wife = new Wife();
        IHandler children = new Children();
        //处理请求
        for (IMan man: manList) {
            switch (man.getType()) {
                case 0:
                    System.out.println("--------孩子向父母发起请求-------");
                    parent.HandleMessage(man);
                    break;
                case 1:
                    System.out.println("--------丈夫向妻子发起请求-------");
                    wife.HandleMessage(man);
                    break;
                case 2:
                    System.out.println("--------父亲向孩子发起请求-------");
                    children.HandleMessage(man);
                    break;
                default:
                    break;
            }
        }
    }
}

首先是通过随机方法产生了5个男性的对象,然后看他们是如何就要5块零花钱这件事去请示的,运行结果如下所示:

--------丈夫向妻子发起请求-------
丈夫向妻子的请求是:5块零花钱
妻子的回答是:同意
--------丈夫向妻子发起请求-------
丈夫向妻子的请求是:5块零花钱
妻子的回答是:同意
--------父亲向孩子发起请求-------
父亲向孩子的请求是:5块零花钱
孩子的回答是:同意
--------孩子向父母发起请求-------
孩子向父母的请求是:5块零花钱
父母的回答是:同意
--------丈夫向妻子发起请求-------
丈夫向妻子的请求是:5块零花钱
妻子的回答是:同意

发没发现上述的代码是不是有点不舒服,有点别扭,有点想重构它的感觉?那就对了!这段代码有以下几个问题:

  • 职责界定不清晰

对孩子提出的请示,应该在父母类中做出决定,父母有责任、有义务处理孩子的请示,

因此Parent类应该是知道孩子的请求自己处理,而不是在客户端类中进行组装出来, 也就是说原本应该是父亲这个类做的事情抛给了其他类进行处理,不应该是这样的.

  • 代码臃肿

我们在Client类中写了if...Else的判断条件,而且能随着能处理该类型的请示人员越多,
If...Else的判断就越多,想想看,臃肿的条件判断还怎么有可读性?!

  • 耦合过重

这是什么意思呢,我们要根据MAN的类型来决定使用IHandler的那个实现类来处理请

求.有一个问题是:如果IHandler的实现类继续扩展怎么办?修改客户端类? 与开闭原则违背了![开闭原则:软件实体如类,模块和函数应该对扩展开放,对修改关闭]
网址:www.jianShu.com/p/05196fac1…

  • 异常情况欠考虑

丈夫只能向妻子请示吗?丈夫向自己的父母请示了,父母应该做何处理?
我们的程序上可没有体现出来,逻辑失败了!

既然有这么多的问题,那我们要想办法来解决这些问题,我们先来分析一下需求,男性提出一个请示,必然要获得一个答复,甭管是同意还是不同意,总之是要一个答复的,而且这个答复是唯一的,不能说是父母作出一个决断,而妻子也作出了一个决断,也即是请示传递出去,必然有一个唯一的处理人给出唯一的答复,好,分析完毕,收工,重新设计,我们可以抽象成这样一个结构,男性的请求先发送到父亲,父母一看是自己要处理的,就作出回应处理,如果男性已经结婚了,那就要把这个请求转发到妻子来处理,如果男性已经年迈,那就由孩子来处理这个请求,类似于如图所示的顺序处理图.
安卓:优雅的处理首页弹框逻辑:责任链模式
父母、妻子、孩子每个节点有两个选择:要么承担责任,做出回应;要么把请求转发到后序环节.结构分析得已经很清楚了,那我们看怎么来实现这个功能,类图重新修正,如图:
安卓:优雅的处理首页弹框逻辑:责任链模式
从类图上看,三个实现类Parent、妻子、儿童只要实现构造函数和父类中的抽象方法响应就可以了,具体由谁处理男性提出的请求,都已经转移到了处理程序抽象类中,我们来看处理程序怎么实现,

public abstract class Handler {
    //处理级别
    public static final int PARENT_LEVEL_REQUEST = 0; //父母级别
    public static final int WIFE_LEVEL_REQUEST = 1;	//妻子级别
    public static final int CHILDREN_LEVEL_REQUEST = 2;//孩子级别

    private Handler mNextHandler;//下一个责任人

    protected abstract int getHandleLevel();//具体责任人的处理级别

    protected abstract void response(IMan man);//具体责任人给出的回应

    public final void HandleMessage(IMan man) {
        if (man.getType() == getHandleLevel()) {
            response(man);//当前责任人可以处理
        } else {
            //当前责任人不能处理,如果有后续处理人,将请求往后传递
            if (mNextHandler != null) {
                mNextHandler.HandleMessage(man);
            } else {
                System.out.println("-----没有人可以请示了,不同意该请求-----");
            }
        }
    }

    public void setNext(Handler next) {
        this.mNextHandler = next;
    }
}

再看一下具体责任人的实现:Parent、妻子、儿童

public class Parent extends Handler{

    @Override
    protected int getHandleLevel() {
        return Handler.PARENT_LEVEL_REQUEST;
    }

    @Override
    protected void response(IMan man) {
        System.out.println("----------孩子向父母提出请示----------");
        System.out.println(man.getRequest());
        System.out.println("父母的回答是:同意");
    }
}
public class Wife extends Handler{
    @Override
    protected int getHandleLevel() {
        return Handler.WIFE_LEVEL_REQUEST;
    }

    @Override
    protected void response(IMan man) {
        System.out.println("----------丈夫向妻子提出请示----------");
        System.out.println(man.getRequest());
        System.out.println("妻子的回答是:同意");
    }
}
public class Children extends Handler{
    @Override
    protected int getHandleLevel() {
        return Handler.CHILDREN_LEVEL_REQUEST;
    }

    @Override
    protected void response(IMan man) {
        System.out.println("----------父亲向孩子提出请示----------");
        System.out.println(man.getRequest());
        System.out.println("孩子的回答是:同意");
    }
}

那么再看一下场景复现:
在客户端中设置请求的传递顺序,先向父母请示,不是父母应该解决的问题,则由父母传递到妻子类解决,若不是妻子类解决的问题则传递到孩子类解决,最终的结果必然有一个返回,其运行结果如下所示。

----------孩子向父母提出请示----------
15块零花钱
父母的回答是:同意
----------丈夫向妻子提出请示----------
15块零花钱
妻子的回答是:同意
----------父亲向孩子提出请示----------
15块零花钱
孩子的回答是:同意
----------丈夫向妻子提出请示----------
15块零花钱
妻子的回答是:同意
----------父亲向孩子提出请示----------
15块零花钱
孩子的回答是:同意

结果也正确,业务调用类客户端也不用去做判断到底是需要谁去处理,而且处理程序抽象类的子类可以继续增加下去,只需要扩展传递链而已,调用类可以不用了解变化过程,甚至是谁在处理这个请求都不用知道。在这种模式就是责任链模式

定义

通过给多个对象处理请求的机会来避免将请求的发送者耦合到接收者。链接接收对象并沿链传递请求,直到对象处理它。
(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止.)责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果,其通用类图如图所示
安卓:优雅的处理首页弹框逻辑:责任链模式
最后总结一下,责任链的模版:
包含四个对象、处理程序、请求、级别、响应:

public class Request {
    //请求的等级
    public Level getRequestLevel(){
        return null;
    }
}
public class Level {
    //请求级别
}

public class Response {
   //处理者返回的数据
}
//抽象处理者
public abstract class Handler {
    private Handler mNextHandler;

    //每个处理者都必须对请求做出处理
    public final Response handleMessage(Request request) {
        Response response = null;
        if (getHandlerLevel().equals(request.getRequestLevel())) {
            //是自己处理的级别,自己处理
            response = echo(request);
        } else {
            //不是自己处理的级别,交给下一个处理者
            if (mNextHandler != null) {
                response = mNextHandler.echo(request);
            } else {
                //没有处理者能处理,业务自行处理
            }
        }
        return response;
    }

    public void setNext(Handler next) {
        this.mNextHandler = next;
    }

    @NotNull
    protected abstract Level getHandlerLevel();

    protected abstract Response echo(Request request);
}

实际应用

我们回到开篇的问题:如何设计弹框的责任链?

//抽象处理者
abstract class AbsDialog(private val context: Context) {
    private var nextDialog: AbsDialog? = null
    
    //优先级
    abstract fun getPriority(): Int

    //是否需要展示
    abstract fun needShownDialog(): Boolean

    fun setNextDialog(dialog: AbsDialog?) {
        nextDialog = dialog
    }

    open fun showDialog() {
        //这里的逻辑,我们就简单点,具体逻辑根据业务而定
        if (needShownDialog()) {
            show()
        } else {
            nextDialog?.showDialog()
        }
    }

    protected abstract fun show()
    
    // Sp存储, 记录是否已经展示过
    open fun needShow(key: String): Boolean {
        val sp: SharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
        return sp.getBoolean(key, true)
    }

    open fun setShown(key: String, show: Boolean) {
        val sp: SharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
        sp.edit().putBoolean(key, !show).apply()
    }

    companion object {
        const val LOG_TAG = "Dialog"
        const val SP_NAME = "dialog"
        const val POLICY_DIALOG_KEY = "policy_dialog"
        const val AD_DIALOG_KEY = "ad_dialog"
        const val PRAISE_DIALOG_KEY = "praise_dialog"
    }
}
/**
 * 模拟 隐私政策弹窗
 * */
class PolicyDialog(context: Context) : AbsDialog(context) {
    override fun getPriority(): Int = 0

    override fun needShownDialog(): Boolean {
        // 这里可以根据业务逻辑判断是否需要显示弹窗,如接口控制等等
        // 这里通过Sp存储来模拟
        return needShow(POLICY_DIALOG_KEY)
    }

    override fun show() {
        Log.d(LOG_TAG, "显示隐私政策弹窗")
        setShown(POLICY_DIALOG_KEY, true) //记录已经显示过
    }
}
/**
 * 模拟 广告弹窗
 * */
class AdDialog(private val context: Context) : AbsDialog(context) {
    private val ad = DialogData(1, "XX广告弹窗") // 模拟广告数据

    override fun getPriority(): Int = 1

    override fun needShownDialog(): Boolean {
        // 广告数据通过接口获取,广告id应该是唯一的,所以根据id保持sp
        return needShow(AD_DIALOG_KEY + ad.id)
    }

    override fun show() {
        Log.d(LOG_TAG, "显示广告弹窗:${ad.name}")
        setShown(AD_DIALOG_KEY + ad.id, true)
    }
}
/**
 * 模拟 好评弹窗
 * */
class PraiseDialog(context: Context) : AbsDialog(context) {
    override fun getPriority(): Int = 2

    override fun needShownDialog(): Boolean {
        // 这里可以根据业务逻辑判断是否需要显示弹窗,如用户使用7天等
        // 这里通过Sp存储来模拟
        return needShow(PRAISE_DIALOG_KEY)
    }

    override fun show() {
        Log.d(LOG_TAG, "显示好评弹窗")
        setShown(PRAISE_DIALOG_KEY, true)
    }
}
//模拟打开app
val dialogs = mutableListOf<AbsDialog>()
dialogs.add(PolicyDialog(this))
dialogs.add(PraiseDialog(this))
dialogs.add(AdDialog(this))
//根据优先级排序
dialogs.sortBy { it.getPriority() }
//创建链条
for (i in 0 until dialogs.size - 1) {
    dialogs[i].setNextDialog(dialogs[i + 1])
}
dialogs[0].showDialog()

第一次打开安卓:优雅的处理首页弹框逻辑:责任链模式

第二次打开安卓:优雅的处理首页弹框逻辑:责任链模式

第三次打开安卓:优雅的处理首页弹框逻辑:责任链模式

总结:

  • 优点

责任链模式非常显著的优点是将请求和处理分开.请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性.

  • 缺点

责任链有两个非常显著的缺点:一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题.二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂.

  • 注意事项

链中节点数量需要控制,避免出现超长链的情况,一般的做法是在处理程序中设置一个最大节点数量,在设置下一个方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。


标签:首页逻辑优雅模式责任安卓系统


程序开发学习排行
最近发表
网站分类
标签列表