自定义布局之树形布局(九):策略模式+装饰模式+工厂方法模式

上一篇讲述如何优化Padding使其在树形布局内部拖拽时也能生效。本篇讲解如何通过两种设计模式来极大地增加布局的拓展性。

前期准备

在真正开始之前我们首先要熟悉两种设计模式。

1.策略模式

策略模式可以将一些具有相似特征的算法提取出来变成一个接口,这样就可以根据需要动态地实现算法的替换。说得有些抽象,举个例子。

比如一个类要用到排序,根据排序量大小、初始状态的不同,我们可以在快排、冒泡、归并等等算法进行选择。但是常规的方式是通过if-else分支来实现。问题就来了,如果现在要添加一种排序算法,就不得不在代码里再添加一个else代码块,这是与开闭原则相悖的。代码如下:

class ClassA{
	void sort(int arr[]){
		if(...){
			//快排
		}else if(...){
			//冒泡
		}else if(...){
			//归并
		}else if(...){
			//新添加的排序算法
		}
	}
}

想想看,如果大多数情况代码都要跳到第四种算法来进行排序,那就避免不了大量的比较判断。利用策略模式就能为了解决上述两种问题。

无论是快排、冒泡还是归并,它们都有共同操作那就是给数组排序,所以可以抽象出一个排序类(或接口)。

public interface SortStrategy{
	void sort(int arr[]);
}

具体是怎么排的就看它们自己的实现方式了。

public class QuickSort extends SortStrategy{
	public void sort(int arr[]){
		...
		//算法用时方恨少,哎...忘的差不多了
	}
}

public class BubbleSort extends SortStrategy{
	public void sort(int arr[]){
		...
	}
}

public class MergerSort extends SortStrategy{
	public void sort(int arr[]){
		...
	}
}

根据需要使用具体的策略就行了,就算要添加新的排序算法,也不需要再修改ClassA了,而且还能避免大量的if-else分支判断。

class ClassA{
	void sort(int arr[],SortStrategy sortStrategy){
		sortStrategy.sort(arr);
	}
}
2.装饰模式

装饰模式是一种结构型设计模式,这种设计模式可以动态地拓展原有对象的功能,代替类的继承。下面举个例子。

//车辆类,抽象类
abstract class Vehicle{
	abstract void run();
}

//汽车类
class Car extends Vehicle{
	@Override
	void run(){
		System.out.print("run as a car");
	}
}

//越野车类
class SUV extends Vehicle{
	@Override
	void run(){
		System.out.print("run as a suv");
	}
}

//加装涡轮增压器的车辆类
class JetVehicle extends Vehicle{
	Vehicle mVehicle ;

	public JetVehicle(Vehicle sourceVehicle ){
		mVehicle  = sourceVehicle ;
	}

	@Override
	void run(){
		mVehicle.run();
		System.out.print(" with jet engine");
	}
}

//全自动车辆类
class AutoVehicle extends Vehicle{
	Vehicle mVehicle ;

	public AutoVehicle (Vehicle sourceVehicle ){
		mVehicle  = sourceVehicle ;
	}

	@Override
	void run(){
		mVehicle.run();
		System.out.print(" which is automatic");
	}
}

现在有如下三种客户需求。A客户想要一辆加装涡轮增压器的汽车,B客户想要一辆能自动驾驶的越野车,C客户想要一辆即加装涡轮增压器又能自动驾驶的汽车。看看装饰模式下的代码实现。

//A客户的需求
Vehicle vehicleA = new JetVehicle(new Car());

//B客户的需求
Vehicle vehicleB = new AutoVehicle (new SUV());

//C客户的需求
Vehicle vehicleC = new AutoVehicle (new JetVehicle(new Car()));

其实我们发现,ABC客户的需求无非就是在 [汽车,SUV] 和 [涡轮增压器,自动驾驶]中自由搭配,可如果使用继承的方式来实现,就不得不为每一种不同的需求创建一个新的类,随着需求多了,继承方式就会变得很不灵活。

3.工厂方法模式

回到刚刚的例子,现在需要制造多辆带有涡轮增压器且能自动驾驶的汽车。原先的做法如下:

Vehicle vehicle1 = new Car();
Vehicle vehicle1WithJet = new JetVehicle(vehicle1);
Vehicle autoVehicle1 = new AutoVehicle(vehicle1WithJet );

Vehicle vehicle2 = new Car();
Vehicle vehicle2WithJet = new JetVehicle(vehicle2);
Vehicle autoVehicle2 = new AutoVehicle(vehicle2WithJet );

...

Vehicle vehicleN = new Car();
Vehicle vehicleNWithJet = new JetVehicle(vehicleN);
Vehicle autoVehicleN = new AutoVehicle(vehicleNWithJet);

每次创建带有涡轮增压器且能自动驾驶的汽车都要进行复杂的改装(初始化),这就是装饰模式的不足之处,把创建过程变复杂了。为了能够简化创建流程,这里可以使用工厂方法模式。工厂方法模式是一种创建型设计模式,它适用于创建过程复杂的对象,调用者不必过于关注对象的创建过程。如下是工厂模式的代码。

abstract class AbsVehicleFactory{
	abstract Vehicle createVehicle();
}

class AutoJetVehicleFactory extends AbsVehicleFactory{
	Vehicle createVehicle(){
		Vehicle vehicle = new Car();
		Vehicle vehicleWithJet = new JetVehicle(vehicle);
		Vehicle autoVehicle = new AutoVehicle(vehicleWithJet );
		return autoVehicle;
	}
}

创建一个专门制造 带涡轮增压器且能自动驾驶汽车 的工厂后,创建这种类型的车辆就简单多了。

AutoJetVehicleFactory factory = new AutoJetVehicleFactory();
Vehicle vehicle1 = factory.createVehicle();
Vehicle vehicle2 = factory.createVehicle();
...
Vehicle vehicleN = factory.createVehicle();

利用策略模式和装饰模式改写连接线绘制类

现在想要给树形布局添加绘制结点外框的功能。想想看,无论是绘制连接线还是绘制结点的外框,其实它们都是树形布局的点缀。既然是这样,我们就可以把它们都抽象成点缀类,点缀类无非是在布局上画点东西,至于画什么,就看子类的具体实现了。再看看下面这张图片。
在这里插入图片描述
不同的连接线可以搭配不同的结点外框,例如第一种线搭配第一种外框,就得到如下的效果。
在这里插入图片描述
代码实现如下。

/**
     * TreeLayout专用的外间距参数类,用于记录布局参数
     */
    public static class LayoutParams extends MarginLayoutParams{
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }

    /**
     * 点缀绘制器,TreeLayout通过这个类的子类来绘制点缀内容
     */
    public static abstract class NodeDectorDrawer {
        private NodeDectorDrawer mSourceDector;

        public NodeDectorDrawer(NodeDectorDrawer sourceDector){
            mSourceDector = sourceDector;
        }

        /**
         * 绘制自身树布局的点缀
         * @param canvas 绘制点缀的画布
         * @param paint 绘制点缀的画笔
         * @param start 点缀的起点控件的区域,即父结点控件所在区域
         * @param end 点缀的终点控件的区域,即子结点控件所在区域
         * @param direction 树的方向
         *                  参考{@link #DIRECTION_LEFT_TO_RIGHT,
         *                       @link #DIRECTION_RIGHT_TO_LEFT,
         *                       @link #DIRECTION_UP_TO_DOWN,
         *                       @link #DIRECTION_DOWN_TO_UP}
         */
        protected abstract void onDrawDecorator(Canvas canvas, Paint paint, Rect start, Rect end, int direction);

        /**
         * 绘制自身树布局的点缀
         * @param canvas 绘制点缀的画布
         * @param paint 绘制点缀的画笔
         * @param start 点缀的起点控件的区域,即父结点控件所在区域
         * @param end 点缀的终点控件的区域,即子结点控件所在区域
         * @param startView 点缀的起点控件
         * @param endView 点缀的终点控件
         * @param direction 树的方向
         */
        public void drawDecorator(Canvas canvas, Paint paint, Rect start, Rect end,View startView,View endView,int direction){
            if(mSourceDector != null){
                mSourceDector.drawDecorator(canvas,paint,start,end,startView,endView,direction);
            }

            if(skipThisDraw(startView,endView)){
                return;
            }

            onDrawDecorator(canvas,paint,start,end,direction);
        }

        /**
         * 跳过本树布局的点缀绘制
         * @param startView 点缀的起点控件
         * @param endView 点缀的终点控件
         * @return 是否跳过本树布局的点缀绘制
         */
        public boolean skipThisDraw(View startView,View endView){
            return false;
        }
    }

由于篇幅问题,点缀绘制器的实现类代码这里就不贴出了,文章最后有Github项目地址。自带的实现类有如下几个。
DirectLineDrawer:直线绘制器
DocumentLineDrawer:折线绘制器
CurveDrawer:曲线绘制器
RectFrameDrawer:方框绘制器
ConnectPointDrawer:连接点绘制器

来看看具体使用,通过装饰模式搭配出一种绘制树形布局的点缀内容。

...
DocumentLineDrawer documentLineDrawer = new DocumentLineDrawer(6,treeView.getLevelInterval(),Color.WHITE);
RectFrameDrawer rectFrameDrawer = new RectFrameDrawer(documentLineDrawer,12,Color.WHITE);
ConnectPointDrawer connectPointDrawer = new ConnectPointDrawer(rectFrameDrawer,12,Color.WHITE);

DocumentLineDrawer documentLineDrawer1 = new DocumentLineDrawer(6,treeView1.getLevelInterval(),Color.WHITE);
RectFrameDrawer rectFrameDrawer1 = new RectFrameDrawer(documentLineDrawer1,12,Color.WHITE);
ConnectPointDrawer connectPointDrawer1 = new ConnectPointDrawer(rectFrameDrawer1,12,Color.WHITE);

treeView.setDecorDrawer(connectPointDrawer);
treeView1.setDecorDrawer(connectPointDrawer1);
...

在这里插入图片描述
换个搭配试试。
在这里插入图片描述
再写几种连接线类和外框类,搭配上的选择顿时就变多了(这时突然想起了高中学的排列组合)。

利用工厂方法模式简化绘制器创建

随着我们的绘制类越来越多,要搭配出一个我们想要的绘制器就变得复杂起来了。为了简化这些复杂的创建流程,我们可以采用工厂方法模式。

首先写一个绘制器工厂的抽象类。

public abstract class AbsDecoratorFactory {
    public abstract TreeLayout.NodeDecoratorDrawer createDecorator();
}

接下来根据实际需要写一个对应的实现类即可。

public class ClassicDecoratorFactory extends AbsDecoratorFactory {
    private int mLineWidth;
    private int mRadius;
    private int mColor;
    private int mInterval;

    public ClassicDecoratorFactory(int lineWidth, int radius, int interval, int color) {
        mLineWidth = lineWidth;
        mRadius = radius;
        mColor = color;
        mInterval = interval;
    }

    @Override
    public TreeLayout.NodeDecoratorDrawer createDecorator() {
        DirectLineDrawer directLineDrawer = new DirectLineDrawer(mLineWidth,mColor);
        RectFrameDrawer rectFrameDrawer = new RectFrameDrawer(directLineDrawer,mLineWidth,mColor);
        ConnectPointDrawer connectPointDrawer = new ConnectPointDrawer(rectFrameDrawer,mRadius, mColor);
        return connectPointDrawer;
    }
}

可以看到,复杂的绘制器组合搭配过程就被藏在了工厂类里,调用者只需少量代码即可生成目标绘制器。

ClassicDecoratorFactory factory = new ClassicDecoratorFactory(6,12,treeView.getLevelInterval(),Color.WHITE);

treeView.setDecorDrawer(factory.createDecorator());
treeView1.setDecorDrawer(factory.createDecorator());

最后

利用装饰模式和工厂方法模式来优化代码确实是很好的选择,装饰模式增加了树形布局的拓展性,工厂方法模式简化了装饰模式的复杂搭配过程。

由于代码重构,前面几篇文章讲到的连接线绘制的代码被改动了。由于篇幅原因,文章很遗憾没能贴出全部代码,感兴趣的朋友可以到Github项目中阅读完整代码。喜欢这个布局控件的话可以star一下以示鼓励。

下一篇讲解如何给树形布局添加遍历监听功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值