Builder 模式

Posted gxl1995

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Builder 模式相关的知识,希望对你有一定的参考价值。

概述

建造者模式(Builder Pattern),是创造性模式之一,Builder 模式的目的则是为了将对象的构建与展示分离。Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。

定义

将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

使用场景

  1. 相同的方法,不同的执行顺序,产生不同的事件结果时;
  2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;
  3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
  4. 当初始化一个对象特别复杂,比如参数非常多,且很多参数都具有默认值时。

uml图

技术分享图片

角色介绍:

  • Builder(抽象建造者)
    它为创建一个产品对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildXXX(),它们用于创建复杂对象的各个部件;另一类方法是getXXX(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

  • ConcreteBuilder(具体建造者)
    它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

  • Product(产品角色)
    它是被构建的复杂对象,包含多个组成部件,具体建造者ConcreteBuilder创建该产品的内部表示并定义它的装配过程。

  • Director(指挥者)
    指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象,然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

例子

这里就以经典的组装电脑为例,我们把电脑的组装过程分为构建主机,设置显示器,设置操作系统三个部分。

抽象product:

/**
 * 计算机抽象类,Product角色
 */
public abstract class Computer {

    private String mBoard;
    private String mDisplay;

    public String mOS;

    protected Computer() {

    }

    /**
     * 设置主板
     */
    public void setBoard(String board) {
        mBoard = board;
    }

    /**
     * 设置显示器
     */
    public void setDisplay(String display) {
        mDisplay = display;
    }

    /**
     * 设置操作系统
     */
    public abstract void setOS();

    @Override
    public String toString() {
        return "Computer{" +
                "mBoard=‘" + mBoard + ‘\\‘‘ +
                ", mDisplay=‘" + mDisplay + ‘\\‘‘ +
                ", mOS=‘" + mOS + ‘\\‘‘ +
                ‘}‘;
    }
}

具体的product:

/**
 * 具体的Computer类,xiaomicomputer
 */
public class XiaMiComputer extends Computer {
    @Override
    public void setOS() {
        mOS = "Window 10";
    }
}

抽象builder

public abstract class Builder {
    /**
     * 设置主板
     */
    public abstract void buildBoard(String board);

    /**
     * 设置显示器
     */
    public abstract void buildDisplay(String display);

    /**
     * 设置操作系统
     */
    public abstract void buildOS();

    /**
     * 创建电脑
     */
    public abstract Computer create();
}

具体的builder

public class ComputerBuilder extends Builder {
    private Computer mComputer=new XiaMiComputer();

    @Override
    public void buildBoard(String board) {
        mComputer.setBoard(board);
    }

    @Override
    public void buildDisplay(String display) {
        mComputer.setDisplay(display);
    }

    @Override
    public void buildOS() {
        mComputer.setOS();
    }

    @Override
    public Computer create() {
        return mComputer;
    }
}

Director类,负责构造Computer

public class Director {
    private final Builder mBuilder;
    public Director(Builder builder) {
        mBuilder = builder;
    }
    public void construct(String board, String display) {
        mBuilder.buildBoard(board);
        mBuilder.buildDisplay(display);
        mBuilder.buildOS();
    }
}

main方法中测试

public static void main(String[] args) {
        Builder builder = new ComputerBuilder();
        Director director = new Director(builder);
        director.construct("因特尔主板", "retina显示器");
        System.out.println("::"+builder.create().toString());
    }

总结

上面就是经典的builder设计模式,但是在实际开发过程中,Director经常会被省略。直接使用一个Builder 来进行对象的组装,这个Builder 通常为链式调用,它的关键点是每一个 setter 方法都返回自身,也就是return this,这样使得setter方法可以链式调用,代码如下。

new TestBuilder().setA("A").setB("B").create();

通过这种形式不仅去除了Director角色,整个结构也更加简单,也能对Product对象的组装过程由更精细的控制。

实战

android中用到builder模式的有很多,其中最著名的可能就是AlertDialog,开源中的有Okhttp等都用到了builder设计模式,具体的源码自己去看吧!

这次就实战就通过Builder模式来打造通用的navigationbar,就是我们项目中经常用到的toolbar,我们通过链式调用可以达到可以一行代码实现任何toolbar效果。

先画一下uml图
技术分享图片

INavigation

public interface INavigation {
    /**
     * 创建navigation bar view
     */
    View createView();

    /**
     * 添加到父布局
     */
    void attachParent(View parentView);

    /**
     * 设置参数
     */
    void addParams();
}

AbsNavigation,这个是基础的navigation,其他的扩展navigation都要继承它

public class AbsNavigation implements INavigation {
    private Builder mBuilder;
    private View mNavigationView;

    protected AbsNavigation(Builder builder) {
        this.mBuilder = builder;
        View navigationView = createView();
        attachParent(navigationView);
        addParams();
    }

    public void addParams() {
        //设置点击事件
        Map<Integer, View.OnClickListener> clickListeners = mBuilder.mClickListeners;
        for (Map.Entry<Integer, View.OnClickListener> entry : clickListeners.entrySet()) {
            View view = findViewById(entry.getKey());
            view.setOnClickListener(entry.getValue());
        }
        //处理设置text
        Map<Integer, CharSequence> texts = mBuilder.mTexts;
        for (Map.Entry<Integer, CharSequence> entry : texts.entrySet()) {
            TextView textView = (TextView) findViewById(entry.getKey());
            textView.setText(entry.getValue());
        }
    }

    /**
     * 根据id查找控件
     */
    public View findViewById(int viewId) {
        View view = mNavigationView.findViewById(viewId);
        return view;
    }

    /**
     * 添加到父容器中
     */
    public void attachParent(View navigationView) {
        ViewGroup parentView = mBuilder.mParentView;
        if (parentView == null) {
            // 获取activity的根布局,View源码
            ViewGroup rootView = ((Activity) (mBuilder.mContext))
                    .findViewById(android.R.id.content);
            //获取根布局
            parentView = (ViewGroup) rootView.getChildAt(0);
            parentView.addView(navigationView, 0);
        } else {
            parentView.addView(navigationView, 0);
        }

    }

    /**
     * 创建NavigationView
     *
     * @return
     */
    public View createView() {
        mNavigationView = LayoutInflater.from(mBuilder.mContext).inflate(mBuilder.mLayoutId, null);
        return mNavigationView;
    }

    /**
     * 获取builder
     */
    public Builder getBuilder() {
        return mBuilder;
    }

    public static abstract class Builder<T extends Builder> {
        private Context mContext;
        private ViewGroup mParentView;
        private int mLayoutId;

        private Map<Integer, View.OnClickListener> mClickListeners;
        private Map<Integer, CharSequence> mTexts;


        public Builder(Context context, int layoutId, ViewGroup parentView) {
            this.mContext = context;
            this.mLayoutId = layoutId;
            this.mParentView = parentView;
            if (mClickListeners == null) {
                mClickListeners = new HashMap<>();
            }
            if (mTexts == null) {
                mTexts = new HashMap<>();
            }
        }

        public Builder(Context context, int layoutId) {
            this(context, layoutId, null);
        }

        /**
         * 构造
         */
        public abstract T create();

        /**
         * 设置view的点击事件
         */
        public T setOnClickListener(int viewId, View.OnClickListener listener) {
            mClickListeners.put(viewId, listener);
            return (T) this;
        }

        /**
         * 给text控件设置文本
         */
        public T setText(int viewId, CharSequence text) {
            mTexts.put(viewId, text);
            return (T) this;
        }

    }
}

万能头部布局的NaviagtionBar,当自定义布局时将布局id传入进来

package com.gxl.day9.navigation;

import android.content.Context;
import android.view.ViewGroup;

/**
 * Created by Administrator on 2018/5/10/010.
 * 指定布局的navigation bar
 */

public class NavigationBar extends AbsNavigation {

    protected NavigationBar(Builder builder) {
        super(builder);
    }
   @Override
    public void addParams() {
        super.addParams();
        //设置自定义属性
    }

    public static class Builder extends AbsNavigation.Builder<Builder> {

        public Builder(Context context, int layoutId, ViewGroup parentView) {
            super(context, layoutId, parentView);
        }

        public Builder(Context context, int layoutId) {
            super(context, layoutId);
        }

        @Override
        public Builder create() {
            NavigationBar navigationBar = new NavigationBar(this);
            return this;
        }
    }
}

注意的功能,在AbsNavigation中都写好了,还有一些需要扩展的可以在addParams方法中进行设置

一般情况下都可以使用的DefaultNavigation

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

import com.gxl.day9.R;

/**
 * Created by Administrator on 2018/5/11/011.
 */

public class DefaultNavigation extends AbsNavigation {

    protected DefaultNavigation(Builder builder) {
        super(builder);
    }

    @Override
    public void addParams() {
        super.addParams();
        //处理自己的逻辑
        Builder builder= (Builder) getBuilder();
        if (builder.mIsHideRight) {
            findViewById(R.id.right_icon).setVisibility(View.GONE);
        }
    }

    public static class Builder extends AbsNavigation.Builder<Builder> {
        private Boolean mIsHideRight;
        public Builder(Context context) {
            super(context, R.layout.navigation_bar);
        }

        public Builder(Context context, ViewGroup parentView) {
            super(context, R.layout.navigation_bar, parentView);
        }

        @Override
        public Builder create() {
            new DefaultNavigation(this);
            return this;
        }

        /**
         * 隐藏右边
         */
        public Builder hideRight() {
            mIsHideRight = true;
            return this;
        }
    }
}

可以看到,在Builder方法中,添加一些新的功能,在addParams方法中对这些功能进行处理。

附件列表

     






    以上是关于Builder 模式的主要内容,如果未能解决你的问题,请参考以下文章

    Builder设计模式,模板设计模式,Adapter设计模式笔记

    builder模式-积木系列

    变种 Builder 模式:优雅的对象构建方式

    [05]Go设计模式:建造者模式(Builder Pattern)

    设计模式Builder模式

    23种设计模式之建造者模式代码实例