背景

跟着业务的开展,主页的弹窗越来越多,隐私方针弹窗,广告弹窗,好评弹窗,运用内更新弹窗等等。 并且弹框显现还有要求,比如:

  • 用户本次运用app,只能显现一个弹框,究竟谁都不愿意翻开app就看到一堆弹框
  • 这些弹框有优先级:如隐私方针弹窗优先级肯定比好评弹窗高,所以希望优先级高的优先显现
  • 广告弹框只展现一次
  • 等等

怎样优雅的处理这个逻辑呢?请出咱们的主角:职责链形式。

职责链形式

举个栗子

一位男性在成婚之前有事要和爸爸妈妈请示,成婚之后要请示妻子,老了之后就要和孩子们商议。作为决策者的爸爸妈妈、妻子或孩子,只要两种选择:要不承当起职责来,答应或不答应相应的恳求; 要不就让他请示下一个人,下面来看怎样经进程序来完成整个流程。

先看一下类图:

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

类图十分简略,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类中进行组装出来, 也便是说 原本应该是父亲这个类做的事情抛给了其他类进行处理,不该该是这样的。

  • 代码臃肿

咱们在Client类中写了if…else的判别条件,并且能跟着能处理该类型的请示人员越多,
if…else的判别就越多,想想看,臃肿的条件判别还怎样有可读性?!

  • 耦合过重

这是什么意思呢,咱们要依据Man的type来决议运用IHandler的那个完成类来处理请

求。有一个问题是:假如IHandler的完成类持续扩展怎样办?修正Client类? 与开闭准则违背了!【开闭准则:软件实体如类,模块和函数应该对扩展敞开,对修正封闭】
www.jianshu.com/p/05196fac1…

  • 异常状况欠考虑

老公只能向妻子请示吗?老公向自己的爸爸妈妈请示了,爸爸妈妈应该做何处理?
咱们的程序上可没有表现出来,逻辑失利了!

已然有这么多的问题,那咱们要想办法来处理这些问题,咱们先来剖析一下需求,男性提出一个请示,必定要获得一个答复,别管是赞同仍是不赞同,总归是要一个答复的,并且这个答复是仅有的,不能说是爸爸妈妈作出一个决断,而妻子也作出了一个决断,也即是请示传递出去,必定有一个仅有的处理人给出仅有的答复,OK,剖析结束,收工,从头规划,咱们能够笼统成这样一个结构,男性的恳求先发送到父亲,爸爸妈妈一看是自己要处理的,就作出回应处理,假如男性现已成婚了,那就要把这个恳求转发到妻子来处理,假如男性现已垂暮,那就由孩子来处理这个恳求,类似于如图所示的次序处理图。

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

爸爸妈妈、妻子、孩子每个节点有两个选择:要么承当职责,做出回应;要么把恳求转发到后序环节。结构剖析得现已很清楚了,那咱们看怎样来完成这个功能,类图从头修正,如图 :
Android:优雅的处理首页弹框逻辑:责任链模式

从类图上看,三个完成类Parent、Wife、Children只要完成构造函数和父类中的笼统办法 response就能够了,详细由谁处理男性提出的恳求,都现已搬运到了Handler笼统类中,咱们 来看Handler怎样完成,

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、Wife、Children

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("孩子的答复是:赞同");
    }
}

那么再看一下场景复现:
在Client中设置恳求的传递次序,先向爸爸妈妈请示,不是爸爸妈妈应该处理的问题,则由爸爸妈妈传递到妻子类处理,若不是妻子类处理的问题则传递到孩子类处理,最终的成果必定有一个回来,其运转成果如下所示。

----------孩子向爸爸妈妈提出请示----------
15块零花钱
爸爸妈妈的答复是:赞同
----------老公向妻子提出请示----------
15块零花钱
妻子的答复是:赞同
----------父亲向孩子提出请示----------
15块零花钱
孩子的答复是:赞同
----------老公向妻子提出请示----------
15块零花钱
妻子的答复是:赞同
----------父亲向孩子提出请示----------
15块零花钱
孩子的答复是:赞同

成果也正确,业务调用类Client也不必去做判别到底是需求谁去处理,并且Handler笼统类的子类能够持续增加下去,只需求扩展传递链而已,调用类能够不必了解变化进程,乃至是谁在处理这个恳求都不必知道。在这种形式便是职责链形式

界说

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.
(使多个目标都有时机处理恳求,从而防止了恳求的发送者和接受者之间的耦合联系。将这些目标连成一条链,并沿着这条链传递该恳求,直到有目标处理它为止。) 职责链形式的重点是在“链”上,由一条链去处理类似的恳求在链中决议谁来处理这个请 求,并回来相应的成果,其通用类图如图所示

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

最终总结一下,职责链的模版:
包含四个目标,Handler,Request,Level,Response:

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()

第一次翻开

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

第二次翻开

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

第三次翻开

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

总结:

  • 长处

职责链形式十分明显的长处是将恳求和处理分隔。恳求者能够不必知道是谁处理的,处理者能够不必知道恳求的全貌,两者解耦,进步体系的灵活性。

  • 缺陷

职责链有两个十分明显的缺陷:一是功能问题,每个恳求都是从链头遍历到链尾,特别是在链比较长的时分,功能是一个十分大的问题。二是调试不很便利,特别是链条比较长, 环节比较多的时分,由于采用了类似递归的方式,调试的时分逻辑或许比较复杂。

  • 注意事项

链中节点数量需求控制,防止出现超长链的状况,一般的做法是在Handler中设置一个最大节点数量,在setNext办法中判别是否现已是超过其阈值,超过则不答应该链建立,防止无意识地损坏体系功能。