美文网首页Android技术知识Android开发Android开发经验谈
Android源码设计模式(五)-- 使编程更有灵活性:责任链模

Android源码设计模式(五)-- 使编程更有灵活性:责任链模

作者: 随时学丫 | 来源:发表于2018-07-12 10:44 被阅读28次

    Android源码设计模式(一) -- 面向对象的六大原则
    Android源码设计模式(二)-- 应用最广的模式:单例模式
    Android源码设计模式(三)-- 自由扩展你的项目:Builder 模式
    Android源码设计模式(四)-- 时势造英雄:策略模式
    Android源码设计模式(五)-- 使编程更有灵活性:责任链模式
    Android源码设计模式(六)— 编程好帮手:代理模式
    Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者模式

    简书 MD 语法不识别 [TOC] ,也不会根据标题行(#) 来插入目录,作为每次看资料喜欢先看目录把握总体的我来说,很不习惯,查找跳转也不方便,所以,如果看到文章没有目录预览的,请安装脚本:简书目录脚本地址

    一、责任链模式的定义

    使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,指导有对象处理它为止。

    二、责任链模式的使用场景

    1. 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
    2. 在请求处理者不明确的情况下向多个对象提交一个请求。
    3. 需要动态指定一组对象处理请求。

    三、责任链模式的 UML 类图

    责任链模式 UML

    角色介绍

    • Handler:抽象处理者角色,声明一个请求处理的方法,并保持对下一个处理节点 Handler 对象的引用。
    • ConcreteHandler : 具体处理角色,对请求进行处理,若不能处理则将该请求转发给下一个节点的处理对象。
    • Client:客户端类,调用责任链模式的类。

    四、责任链模式的简单实现

    在看责任链模式之前,先看一个我们平时开发中一直在写的代码

     public void test(int i, Request request){
            if(i==1){
                Handler1.response(request);
            }else if(i == 2){
                Handler2.response(request);
            }else if(i == 3){
                Handler3.response(request);
            }else if(i == 4){
                Handler4.response(request);
            }else{
                Handler5.response(request);
            }
        }
    

    代码的业务逻辑是这样的,方法有两个参数:整数 i 和一个请求 request,根据 i 的值来决定由谁来处理 request,如果i == 1,由 Handler1 来处理,如果 i == 2,由 Handler2 来处理,以此类推。在编程中,这种处理业务的方法非常常见,所有处理请求的类有 if…else… 条件判断语句连成一条责任链来对请求进行处理,相信大家都经常用到。这种方法的优点是非常直观,简单明了,并且比较容易维护,但是这种方法也存在着几个比较令人头疼的问题:

    • 代码臃肿:实际应用中的判定条件通常不是这么简单地判断是否为 1 或者是否为 2,也许需要复杂的计算,也许需要查询数据库等等,这就会有很多额外的代码,如果判断条件再比较多,那么这个 if…else… 语句基本上就没法看了。
    • 耦合度高:如果我们想继续添加处理请求的类,那么就要继续添加 else if 判定条件;另外,这个条件判定的顺序也是写死的,如果想改变顺序,那么也只能修改这个条件语句。

    既然缺点我们已经清楚了,就要想办法来解决。这个场景的业务逻辑很简单:如果满足条件 1,则由 Handler1 来处理,不满足则向下传递;如果满足条件 2,则由 Handler2 来处理,不满足则继续向下传递,以此类推,直到条件结束。其实改进的方法也很简单,就是把判定条件的部分放到处理类中,这就是责任连模式的原理。

    程序猿狗屎运被派出去异国出差一周,这时候就要去申请一定的差旅费了,心里小算一笔加上各种车马费估计大概 5 万,于是先向小组长汇报申请,可是大于1000 小组长没权利批复,于是只好去找项目主管,项目主管一看妈蛋这么狠要这么多我只能批小于 5000 的,于是你只能再跑去找部门经理,部门经理看了下一阵淫笑后说没法批我只能批小于 10000 的,于是你只能狗血地去跪求老总,老总一看哟!小伙子心忒黑啊!老总话虽如此但还是把钱批给你了毕竟是给公司办事,到此申请处理完毕,你也可以屁颠屁颠地滚了。

    实现源码

    上面的场景我们可以使用使用如下的代码来模拟实现:

    首先定义一个 Leader 类,一是定义两个抽象方法来确定领导应有的行为和属性,二是声明一个处理请求的方法和确定当前领导是否有处理报账能力,如果没有这个权限,则交友下一个处理请求者。

    public abstract class Leader {
        protected Leader nextHandler;// 上一级领导处理者
    
        public final void handleRequest(int money) {
            if (money <= limit()) {
                handle(money);
            } else {
                if (nextHandler != null) {
                    nextHandler.handleRequest(money);
                }
            }
        }
    
        /**
         * 自身能审批的权限
         * @return
         */
        public abstract int limit();
    
        /**
         * 处理报账的能力
         * @param money
         */
        public abstract void handle(int money);
    }
    

    各个处理请求具体类

    public class GroupLeader extends Leader {
        @Override
        public int limit() {
            return 1000;
        }
        @Override
        public void handle(int money) {
            System.out.println("组长审批报销 " + money + " 元");
        }
    }
    
    public class Director extends Leader {
        @Override
        public int limit() {
            return 5000;
        }
        @Override
        public void handle(int money) {
            System.out.println("主管审批报销 " + money + " 元");
        }
    }
    
    public class Manager extends Leader {
        @Override
        public int limit() {
            return 10000;
        }
        @Override
        public void handle(int money) {
            System.out.println("经理审批报销 " + money + " 元");
        }
    }
    
    public class Boss extends Leader {
        @Override
        public int limit() {
            return Integer.MAX_VALUE;
        }
        @Override
        public void handle(int money) {
            System.out.println("老板审批报销 " + money + " 元");
        }
    }
    

    最后,程序员开始从小组长开始发起申请报账请求。

    public class Client {
        public static void main(String[] args) {
            GroupLeader groupLeader = new GroupLeader();
            Director director = new Director();
            Manager manager = new Manager();
            Boss boss = new Boss();
            
            groupLeader.nextHandler = director;
            director.nextHandler = manager;
            manager.nextHandler = boss;
            
            groupLeader.handleRequest(50000);
        }
    }
    //---------------------------------------运行结果----------------------------------------------
    老板审批报销 50000 元
    

    这里大家可能会想,我们可不可以越过组长直接找主管报账呢?答案是肯定的,这就是责任链模式的灵活之处,请求的发起可以从责任链的任何一个节点开始,同时也可以改变责任链内部传递的规则,如主管不在,我们完全可以跨过主管直接将请求转送给经理。

    对于责任链中的一个处理对象,其只有两个行为,一是处理请求,二是将请求转送给下一个节点,不允许某个处理者对象在处理了请求之后又将请求转送给上一个节点的情况。

    对于责任链来说,一个请求最终只有两种情况,一是被某个处理对象所处理,另一个是所有对象均未对其处理,对于前一种情况我们成该责任链为纯责任链,对于后一种成为不纯责任链,在实际应用中,我们所见的责任链大多为不纯责任链。

    五、Android 中责任链模式实现

    责任链模式在 Android 中比较类似的莫过于事件的分发处理,每当用户接触屏幕时,Android 都将对应事件包装成一个事件对象从 ViewTree 顶端至上而下的分发传递,这里我们主要来看看 ViewGroup 中是如何将事件派发到子 View 的,我们知道 ViewGroup 中执行事件分发的方法是 dispatchTouchEvent,在该方法中其对事件进行了统一的分发。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        //对辅助功能的事件处理
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
    
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //处理原始 DOWN 事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //在新事件开始时处理完上一个事件
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            //检查事件拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
            //如果事件被拦截,则进行正常的事件分发
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }
    
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            //如果有必要,为 DOWN 事件检查所有目标对象
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //如果事件未被取消并未被拦截
            if (!canceled && !intercepted) {
                //如果有辅助功能参与,则直接将事件投递到对应的 View,否则将事件分发给所有子View
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //如果事件为起始事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;
    
                    removePointersFromTouchTargets(idBitsToAssign);
                
                    final int childrenCount = mChildrenCount;
                    //如果 TouchTarget 为空并子元素为 0
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //由上至下寻找一个可以接收此事件的View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
    
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                  //如果这个子View无法接收 pointer event 或这个事件点压根就没有落在子元素的边界范围内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                //跳出该次循环继续遍历
                                continue;
                            }
                          //找到 event 该由哪个子元素持有
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                          
                            resetCancelNextUpFlag(child);
                            //投递事件执行的触摸事件
                            //如果子元素是 ViewGroup,则递归调用重复此过程
                          //如果子元素是View,则调用View 的dispatchTouchEvent,最终由OnTouchEvent 处理
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
    
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
    
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }
    
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    } 
    

    再来看看 dispatchTransformedTouchEvent 方法是如何调度子元素 dispatchTouchEvent 方法的:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
    
        final int oldAction = event.getAction();
    
        //如果事件被取消
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果没有子元素
            if (child == null) {
                // 那么就直接调用父类的dispatchTouchEvent,注意这里的父类终会为View类
                handled = super.dispatchTouchEvent(event);
            } else {
                // 如果有子元素则传递cancle事件
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
    
        //计算即将被传递的点的数量
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
        //如果事件木有相应的点那么就丢弃该次事件
        if (newPointerIdBits == 0) {
            return false;
        }
    
        // 声明临时变量保存坐标转换后的MotionEvent
        final MotionEvent transformedEvent;
    
        //如果事件点的数量一致
        if (newPointerIdBits == oldPointerIdBits) {
            //子元素为空或子元素有一个单位矩阵
            if (child == null || child.hasIdentityMatrix()) {
               //再次区分子元素为空的情况
                if (child == null) {
                    // 为空则调用父类dispatchTouchEvent
                    handled = super.dispatchTouchEvent(event);
                } else {
                    // 否则尝试获取xy方向上的偏移量(如果通过scrollTo或scrollBy对子视图进行滚动的话)
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    // 将MotionEvent进行坐标变换
                    event.offsetLocation(offsetX, offsetY);
                    // 再将变换后的MotionEvent传递给子元素
                    handled = child.dispatchTouchEvent(event);
                    // 复位MotionEvent以便之后再次使用
                    event.offsetLocation(-offsetX, -offsetY);
                }
                // 如果通过以上的逻辑判断当前事件被持有则可以直接返回
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }
    
        //下述雷同不再累赘
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
    
            handled = child.dispatchTouchEvent(transformedEvent);
        }
    
        transformedEvent.recycle();
        return handled;
    }
    

    ViewGroup 事件投递的递归调用就类似于一条责任链,一旦其寻找到责任者,那么将由责任者持有并消费掉该次事件,具体的体现在 View 的 onTouchEvent 方法中返回值的设置(这里介于篇幅就不具体介绍 ViewGroup 对事件的处理了),如果 onTouchEvent 返回 false 那么意味着当前 View 不会是该次事件的责任人将不会对其持有,如果为 true 则相反,此时 View 会持有该事件并不再向外传递。

    六、总结

    优点

    对请求者和处理者关系的解耦提高代码的灵活性

    缺点

    对链中处理者的遍历,如果处理者太多那么遍历必定会影响性能,特别是在一些递归调用中,要慎重。

    相关文章

      网友评论

        本文标题:Android源码设计模式(五)-- 使编程更有灵活性:责任链模

        本文链接:https://www.haomeiwen.com/subject/nxplpftx.html