阅读 731

静态代理这么用?---剖析BannerViewPager中Indicator的设计思想

很高兴又和大家见面了,本篇文章是《BannerViewPager系列》的第三篇。就在不久前BannerViewPager发布了2.5.0版本,在这个版本中针对Indicator部分的代码进行了重构。本篇文章带大家一起来了解下本次重构Indicator中用到的设计思想,顺便回顾及加深认识一下静态代理模式。如果你还不了解BannerViewPager可以先阅读前两篇文章:

《打造一个丝滑般自动轮播无限循环Android库》

《BannerViewPager源码解析》

也可以点此处到GitHub查看BannerViewPage源码。

一、为什么要重构

在BannerViewPager中针对IndicatorView已进行了两次较大的重构。第一次重构在第二篇文章《BannerViewPager源码解析》中也有提到。最初的Indicator是在BannerViewPager内部维护了一个指示器ImageView的List集合,在BannerViewPager内部会根据页面size动态添加指示器的Image。显然这种处理方式存在很大的弊端,即:不灵活、可扩展性低、性能相对较差等诸多问题。针对这一系列问题,BannerViewPager在2.0.1中对Indicator进行了第一次重构。这次重构将Indicator改为自定义View,并且抽象出了IIndicator接口,极大的增强了Indicator的可扩展性。因此,在后续若干个版本迭代中Indicator逐渐支持了多种Style(CIRCLE/DASH/ROUND_RECT)、多种滑动模式(SMOOTH/NORMAL)以及完全自定义Indicator。相比最初版本,不管在功能还是性能上都有了很大的提升。但是,在后续版本的迭代中却又暴露出许多问题。而这些问题很大程度上影响了开发和使用。列举其中一个最大问题如下:

多个IndicatorView不利于维护和使用

在2.5.0版本之前BannerViewPager已经支持了CIRCLE和DASH两种Indicator样式,与之对应的是CircleIndicatorView和DashIndicatorView。在BannerViewPager内部用简单工厂模式根据IndicatorStyle来生成对应的IndicatorView。2.5.0版本之前的代码如下:

# BannerViewPager
initIndicator(IndicatorFactory.createIndicatorView(getContext(), mIndicatorStyle));

# IndicatorFactory 
public class IndicatorFactory {
    public static BaseIndicatorView createIndicatorView(Context context, @AIndicatorStyle int indicatorStyle) {
        BaseIndicatorView indicatorView;
        if (indicatorStyle == IndicatorStyle.DASH) {
            indicatorView = new DashIndicatorView(context);
        } else {
            indicatorView = new CircleIndicatorView(context);
        }
        return indicatorView;
    }
}
复制代码

这么以来,每当添加一种Indicator Style时候都需要一个与之对应的IndicatorView类,并且需要修改IndicatorFactory 代码生成对应的IndicatorView。当Indicator Style越来越多的时候维护成本和使用成本都会随之增加。使用该库的开发人员需要记住每种Indicator Style对应的IndicatorView,作为该库维护者也要面对越来越臃肿的代码结构,这是大家都不愿意看到的。因此,在这样的背景下IndicatorView的第二次重构就势在必行,不得不做了。

二、回顾静态代理模式

回想初学Java时大家都应该学过Java的23种设计模式。看完设计模式发现也没什么难的,但是在项目中使用的时候就犯了难,每写一个需求都在想着是不是可以用某一种设计模式来实现呢?但当着手写时却又不知道该选取哪一种。相信大家都经历过这样的迷茫期。对于这样的情况我觉得顺其自然就好,只要平时多写代码,多思考,加之多看优秀的开源代码,这种设计思想逐渐的就会被积淀下来,可能在写代码的时候不经意间就发现自己使用了某一种设计模式。就像我在重构Indicator之前我并没有考虑该用怎样的设计模式去写,但是在着手重构的时候就觉得我应该这么做啊。等到写完之后回顾自己代码的时候发现这不就是一个静态代理模式吗?

不知道现在大家对代理模式还记得多少,也不知道是否经常会在项目种用到代理模式。不管怎样,我们先来回顾以下静态代理模式吧:

代理模式即为其它对象提供一种代理控制对这个对象的访问。在代理模式中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

代理模式的结构图如下:

这里写图片描述
注:图片来源《大话设计模式》

看定义总是那么的晦涩难懂,我们还是来举一个代理模式的场景:

Ryan想在上海买一套房子,但是他又不懂得房地产的行情,于是委托了中介(Proxy)来帮助他买房子。

我们把这个场景通过Java代码来实现一下:

1.抽象出接口

首先我们把买房子的一类人抽象出来一个接口,接口中有一个buyHouse的方法:

public interface IPersonBuyHouse {
	void buyHouse();
}
复制代码

2.明确被代理的对象

Ryan想要买房子,于是他就需要实现这个IPersonBuyHouse接口:

public class Ryan implements IPersonBuyHouse{

	@Override
	public void buyHouse() {
		System.out.println("Ryan:I want buy a House...");
	}
}
复制代码

3.寻找代理

由于Ryan不了解房地产行情,于是将买房子的事情委托给了中介(Proxy),因此中介(Proxy)也需要实现IPersonBuyHouse的接口。但是中介不是给自己买房子的,而是买给其它有购房需求者的,所以他应该持有一个IPersonBuyHouse。而此处的购房需求者就是Ryan.于是Proxy代码如下:

public class Proxy implements IPersonBuyHouse{
	
	private IPersonBuyHouse mIPerson;
	
	public Proxy() {
	    mIPerson=new Ryan();
	}
	
	@Override
	public void buyHouse() {
	    System.out.println("Proxy:I can help you to buy house");
	    mIPerson.buyHouse();
	}
}
复制代码

接下来我们在Main方法种测试一下Proxy类:

public class ProxyTest {

	public static void main(String[] args) {
	    new Proxy().buyHouse();
	}
}
复制代码

输出结果:

在这里插入图片描述
通过上面的例子可以看到静态代理是一个很简单的设计模式。那么接下来我们看下如何通过静态代理模式来完成对IndicatorView的重构吧。

三、用静态代理模式重构Indicator

在第一章节中我们就已经提到了当前Indicator的弊端:要维护多个IndicatorView,不利于开发也不利于使用。我们当前的目的就是要将IndicatorView统一成一个。而我们现在面临的困境是如何让一个IndicatorView承载多个Indicator Style?因为它既要绘制CIRCLE Style又要绘制DASH Style,以及以后可能还会增加更多的Style样式。在这种场景下我们就可以想到代理模式来解决问题。

上一个章节中我们举了一个静态代理的例子是正向思维写下来的,那么本章中我们就采用逆向思维,看下是如何倒推出来静态代理模式的。

1.初步设想

首先,我们想要一个IndicatorView承接所有Style的绘制,那么正常来说我们就需要在IndicatorView中通过IndicatorStyle判断是哪种样式,然后在IndicatorView中进行绘制。其伪代码如下:

public class IndicatorView  {

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	if(Style==CIRCLE) {
    	    setMeasuredDimension(measurCircleWidth(), measurCircleHeight());
    	} else {
    		setMeasuredDimension(measurDashWidth(), measurDashHeight());
	    }
    }

    public void onDraw(Canvas canvas) {
    	if(Style==CIRCLE) {
	        drawCircleIndicator(canvas);
	    } else {
	        drawDashleIndicator(canvas);
	    }
    }
}
复制代码

但是如果IndicatorStyle样式非常多的情况下,IndicatorView必然会变得非常庞大且臃肿。因此,我们自然而然的就会想到将View的measure和draw的逻辑抽出来单独给一个类来完成,那么这个类中呢至少应该有measure和draw两个方法。因此,我们将这个类的伪代码写出来大概应该是这样子的:

public class DrawerProxy  {

    public BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	if(Style==CIRCLE) {
    		return measureCircleIndicator(int widthMeasureSpec, int heightMeasureSpec);
    	} else {
    		return measureDashIndicator(int widthMeasureSpec, int heightMeasureSpec);
	    }
    }

    public void onDraw(Canvas canvas) {
    	if(Style==CIRCLE) {
	        drawCircleIndicator(canvas);
	    } else {
	        drawDashleIndicator(canvas);
	    }
    }
}
复制代码

2.抽象接口

通过上一小节的操作我们虽然将测量和绘制逻辑从IndicatorView中剥离了出来,但是DrawerProxy 这个类却承载了所有的测量和绘制逻辑。当Style样式多的时候同样会使DrawerProxy类变得臃肿不堪。因此,我们又很自然的想到了应该把不同Style的绘制逻辑单独抽出来,于是就有了CircleDrawer和DashDrawer两个类来分别处理各自的逻辑。但因为这两个类又要同时被放在DrawerProxy类中,且这两个类都又共同的方法。因此可以抽出一个CircleDrawer和DashDrawer的共同接口。于是就有了这样的一个IDrawer的接口:

public interface IDrawer {

    BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec);

    void onDraw(Canvas canvas);
}
复制代码

同时CircleDrawer和DashDrawer都应该实现该接口:

public class CircleDrawer implements IDrawer {

    @Override
    public MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   		// ... 省略measure逻辑
        return mMeasureResult;
    }

    @Override
    public void onDraw(Canvas canvas) {
        drawIndicator(canvas);
    }

    private void drawIndicator(Canvas canvas) {
       // ...	省略draw逻辑
    }
}
//	DashDrawer与此类似,不再贴出
复制代码

3.回眸一看,静态代理?

到了这里我们在再来看DrawerProxy,发现这个类中同样需要onMeasure和onDraw,那他实现IDrawer接口顺理成章,同时它应该持有一个IDrawer类以便完成真实的测量和绘制任务。于是乎,完善之后的DrawerProxy类就成了这个样子:

public class DrawerProxy implements IDrawer {

    private IDrawer mIDrawer;

    public DrawerProxy(IndicatorOptions indicatorOptions) {
        init(indicatorOptions);
    }

    private void init(IndicatorOptions indicatorOptions) {
        mIDrawer = DrawerFactory.createDrawer(indicatorOptions);
    }

    public void setIndicatorOptions(IndicatorOptions indicatorOptions) {
        init(indicatorOptions);
    }

    @Override
    public BaseDrawer.MeasureResult onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        return mIDrawer.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void onDraw(Canvas canvas) {
        mIDrawer.onDraw(canvas);
    }
}
复制代码

到这里,我们回过神来看一下,这不就是一个非常标准的静态代理模式吗?当然,这里也结合了简单工厂模式来生成对应的Drawer。我们来看下重构后的IndicatorView

public class IndicatorView extends BaseIndicatorView implements IIndicator {

    private DrawerProxy mDrawerProxy;

    public IndicatorView(Context context) {
        this(context, null);
    }

    public IndicatorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDrawerProxy = new DrawerProxy(getIndicatorOptions());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        BaseDrawer.MeasureResult measureResult = mDrawerProxy.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureResult.getMeasureWidth(), measureResult.getMeasureHeight());
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mDrawerProxy.onDraw(canvas);
    }

    @Override
    public void setIndicatorOptions(IndicatorOptions indicatorOptions) {
        super.setIndicatorOptions(indicatorOptions);
        mDrawerProxy.setIndicatorOptions(indicatorOptions);
    }
}
复制代码

可以看到通过静态代理模式简化完后的IndicatorView仅仅剩下了三十多行的代码,所有的测量和绘制逻辑都交给代理类DrawerProxy来处理,而DrawerProxy又将逻辑移交给对应的Drawer来完成。这样,所有的类都各司其职,代码简单明了!开发和维护起来也就变得更加得心应手了!

最后,我们来看下IndicatorView在BannerViewPager中的使用:

    initIndicator(new IndicatorView(getContext()));
复制代码

四、总结

本篇文章分享了对BannerViewPager中Indicator重构的一些经验。通过本篇文章相信大家对于静态代理模式也会有了更深的认识。重构后的代码在维护和使用上相比以前显然有了更明显的提升。但是并不等于现在的Indicator已经无懈可击了。相反,它还有很长的路要走。就目前而言,Indicator的SlideMode部分还是又相当大的优化空间的,那么我们就在后面的版本中拭目以吧。

好了,本篇文章到此就结束了,最后到github顺手点个Star呗,项目链接见文章末尾。

BannerViewPager源码戳此处

关注下面的标签,发现更多相似文章
评论