设计模式——Builder设计模式

什么是Builder设计模式?

又称建造者模式,将构建过程和表示过程进行分离,让(参数)构建过程变得更加的简单和直观。另一种差不多的解释:建造者模式(Builder Pattern),是创造性模式之一,Builder 模式的目的则是为了将对象的构建与展示分离。Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。

Builder设计模式和链式调用的区别

这个完全是两个概念,Builer设计模式是一种设计模式,链式调用只是一种调用方式,
但是一般来讲 Builder 设计模式一般会采用链调用的这种方式,那么并不是所有的链式调用都是 Builer 设计模式

链式调用有一个体现,就是在调用方法的时候返回自身对象,Builder 一般也有一种体现,就是一般都会出现 Builder 对象。

Android中的应用

AlertDialog、Glide、Picasso、Okhttp...

public class MainActivity extends AppCompatActivity {
    ImageView mImageView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Builder设计模式应用场景1:Glide
        String url = "";
        Glide.with(this)
                .load(url)

                .into(mImageView);

        // Builder设计模式应用场景2:Dialog
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle("标题")
                .setMessage("内容")
                .setNegativeButton("确定", null)
                .setPositiveButton("取消", null)
                .create();
        dialog.show();

    }
}

为什么要用Builder构建者模式呢?

参考文章:
https://blog.csdn.net/peter_water/article/details/78415576
https://blog.csdn.net/ljcITworld/article/details/80387378

假如我们有一个dog类:

public class Dog {
    private int identifier;//编号
    private String name;//名字
    private int age;//年龄
    private int weight;//体重
    private String breed;//品种
    private boolean gender;//性别,true:公狗;false:母狗
    private String sickness;//疾病

    public void setIdentifier(int identifier) {
        this.identifier = identifier;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }

    public void setSickness(String sickness) {
        this.sickness = sickness;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "identifier=" + identifier +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                ", breed='" + breed + '\'' +
                ", gender=" + gender +
                ", sickness='" + sickness + '\'' +
                '}';
    }

}

测试:

public class DogTest {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setIdentifier(2222);//因为是一只二哈,2炸了
        dog.setName("sijia");//撕家小能手呀
        dog.setAge(3);
        dog.setWeight(20);
        dog.setBreed("Husky");
        dog.setGender(true);
        dog.setSickness("doubi");//这只哈士奇太逗逼了,还能治吗?
        System.out.println(dog);
    }
}

控制台输出:

Dog{identifier=2222, name='sijia', age=3, weight=20, breed='Husky', gender=true, sickness='doubi'}

上述代码的问题是dog类变量出现好多遍,我们会想能不能有更简洁点的方法,于是我们想到在Dog类增加设置方法setInfo():

    public void setInfo(int identifier, String name, int age, int weight, String breed, boolean gender, String sickness) {
        this.identifier = identifier;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.breed = breed;
        this.gender = gender;
        this.sickness = sickness;
    }

测试:

Dog dog1 = new Dog();
dog1.setInfo(333, "sijia", 3, 20, "Husky", true, "doubi");
System.out.println(dog1);

控制台输出:

Dog{identifier=333, name='sijia', age=3, weight=20, breed='Husky', gender=true, sickness='doubi'}

可以发现我们一行赋值语句搞定了,但是我们还会发现数字和字符串的含义不是一目了然,还是得查看定义。新增参数就得修改定义,调用的地方也得修改,而且如果参数更多的话,看起来会更乱。

因此,这里我们引出Builder设计模式。接下来我们来看些如何实现:

public class Dog {
    private int identifier;//编号
    private String name;//名字
    private int age;//年龄
    private int weight;//体重
    private String breed;//品种
    private boolean gender;//性别,true:公狗;false:母狗
    private String sickness;//疾病

    public Dog(int identifier, String name, int age, int weight, String breed, boolean gender, String sickness) {
        this.identifier = identifier;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.breed = breed;
        this.gender = gender;
        this.sickness = sickness;
    }

    public void setIdentifier(int identifier) {
        this.identifier = identifier;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }

    public void setSickness(String sickness) {
        this.sickness = sickness;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "identifier=" + identifier +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                ", breed='" + breed + '\'' +
                ", gender=" + gender +
                ", sickness='" + sickness + '\'' +
                '}';
    }

    final static class Builder {
        private int identifier;//编号
        private String name;//名字
        private int age;//年龄
        private int weight;//体重
        private String breed;//品种
        private boolean gender;//性别,true:公狗;false:母狗
        private String sickness;//疾病

        public Builder identifier(int identifier) {
            this.identifier = identifier;
            return this;
        }

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder weight(int weight) {
            this.weight = weight;
            return this;
        }

        public Builder breed(String breed) {
            this.breed = breed;
            return this;
        }

        public Builder gender(boolean gender) {
            this.gender = gender;
            return this;
        }

        public Builder sickness(String sickness) {
            this.sickness = sickness;
            return this;
        }

        public Dog build() {
            return new Dog(identifier, name, age, weight, breed, gender, sickness);
        }
    }

}

调用:

public class DogTest {
    public static void main(String[] args) {

        Dog dog = new Dog.Builder()
                .age(11)
                .breed("xx")
                .gender(true)
                .identifier(123)
                .name("cookie")
                .sickness("dd")
                .weight(10).build();
        System.out.println(dog);

    }
}

控制台输出

Dog{identifier=123, name='cookie', age=11, weight=10, breed='xx', gender=true, sickness='dd'}

注意:

首先Builder是一个静态内部类,之所以需要静态是因为Builder类需要和外部类Person剥离关系,否则就会耦合在一起,这里的Builder相当于一个独立文件的类一样的效果;其次Person类的构造方法最好是外部类不可调用的,因为我们提供了Builder的方式让外部实现了对Person类的初始化,那么我们就没必要让外部直接通过构造方法的方式创建,所以最好设置为private;再之,Builder类中的每一个方法都需要返回该构造者,因为这样,我们才能在每一次设置时都是针对同一个Builder实例进行实例;最后,在build方法才进行对Person类进行初始化,这样做的好处就是可以避免过早地对Person类初始化,当然你也是可以在Builder的构造函数里就对Person就进行初始化,这个见仁见智。

可以发现经过改造之后有下面有点:

  1. 可读性强
  2. 可选择性强

缺点:

  1. 增加了一个内部类Builder的代码量,即产生多余的 Builder 对象,消耗内存。
Builder UML.png

Product 产品类 : 产品的抽象类;
Builder : 抽象类, 规范产品的组建,一般是由子类实现具体的组件过程;
ConcreteBuilder : 具体的构建器;
Director : 统一组装过程(可省略)。

builer设计模式构建NavigationBar

知道了Builder设计模式之后,我们就用这种设计模式来自己写一个自定义TitleView,效果如下图:


实现效果.png

上面是DefaultNavigationBar,下面是MyNavigationBar。

包结构.png

INavigation接口:(后来发现这个接口不要也行,可能是为了统一要求吧)

public interface INavigation {

    void inflateView();

    void addToRootView();

    void bindParams();
}

AbsNavigationBar 抽象类:

public abstract class AbsNavigationBar<E extends AbsNavigationBar.Builder> implements INavigation {

    E mBuilder;
    View mNavigationBarView;

    public E getBuilder() {
        return mBuilder;
    }

    protected AbsNavigationBar(E builder) {
        this.mBuilder = builder;

        // 下面三个方法,是实现INavigation这个接口的三个方法,其实不写实现也可以的。
        // 创建布局
        inflateView();
        // 加入到父布局
        addToRootView();
        // 绑定数据
        bindParams();
    }

    @Override
    public void inflateView() {
        mNavigationBarView = LayoutInflater.from(mBuilder.mContext).inflate(mBuilder.mLayoutId, mBuilder.mParent, false);

    }

    @Override
    public void addToRootView() {
        mBuilder.mParent.addView(mNavigationBarView, 0);
    }


    @Override
    public void bindParams() {

        // 绑定文本参数
        Map<Integer, CharSequence> textMaps = mBuilder.mTextMaps;
        for (Map.Entry<Integer, CharSequence> entry : textMaps.entrySet()) {
            Integer viewId = entry.getKey();
            CharSequence value = entry.getValue();
            TextView tv = findViewById(viewId);
            tv.setText(value);
        }

        // 绑定点击事件
        Map<Integer, View.OnClickListener> listenerMaps = mBuilder.mOnClickListenerMaps;
        for (Map.Entry<Integer, View.OnClickListener> entry : listenerMaps.entrySet()) {
            Integer viewId = entry.getKey();
            View.OnClickListener listener = entry.getValue();
            findViewById(viewId).setOnClickListener(listener);
        }

    }

    public <V extends View> V findViewById(int viewId) {
        return (V) mNavigationBarView.findViewById(viewId);
    }

    // 构建AbsNavigationBar还有存储参数
    public static abstract class Builder<T extends Builder> {
        public Context mContext;
        public int mLayoutId;
        public ViewGroup mParent;
        Map<Integer, CharSequence> mTextMaps;
        Map<Integer, View.OnClickListener> mOnClickListenerMaps;

        public Builder(Context context, int layoutId, ViewGroup parent) {
            mContext = context;
            mLayoutId = layoutId;
            mParent = parent;
            mTextMaps = new HashMap<>();
            mOnClickListenerMaps = new HashMap<>();
        }

        // 用来创建 AbsNavigationBar
        public abstract AbsNavigationBar build();

        public T setText(int viewId, String msg) {//使用泛型解决报错问题
            mTextMaps.put(viewId, msg);
            return (T) this;
        }

        public T setOnClickListener(int viewId, View.OnClickListener listener) {
            mOnClickListenerMaps.put(viewId, listener);
            return (T) this;
        }
    }
}

MyNavigationBar 初步实现的类:

public class MyNavigationBar extends AbsNavigationBar {

    protected MyNavigationBar(AbsNavigationBar.Builder builder) {
        super(builder);
    }

    public static class Builder extends AbsNavigationBar.Builder<MyNavigationBar.Builder> {

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

        @Override
        public AbsNavigationBar build() {
            return new MyNavigationBar(this);
        }
    }

}

DefaultNavigationBar默认的NavigationBar:

public class DefaultNavigationBar extends AbsNavigationBar<DefaultNavigationBar.Builder> {
    protected DefaultNavigationBar(Builder builder) {
        super(builder);
    }


    @Override
    public void bindParams() {
        super.bindParams();
        //findViewById(R.id.tv_left).setVisibility(getBuilder().mLeftVisible);//使用泛型解决报错问题
        findViewById(R.id.tv_left).setVisibility(getBuilder().mLeftVisible);
    }

    public static class Builder extends AbsNavigationBar.Builder<DefaultNavigationBar.Builder> {

        int mLeftVisible = View.VISIBLE;

        public Builder(Context context, ViewGroup parent) {
            super(context, R.layout.layout_default_title, parent);
        }

        @Override
        public AbsNavigationBar build() {
            return new DefaultNavigationBar(this);
        }

        public Builder setTitle(String title) {
            setText(R.id.tv_title, title);
            return this;
        }

        public Builder setLeftText(String title) {
            setText(R.id.tv_left, title);
            return this;
        }

        public Builder setRightText(String title) {
            setText(R.id.tv_right, title);
            return this;
        }

        public Builder setRightListener(View.OnClickListener listener) {
            setOnClickListener(R.id.tv_right, listener);
            return this;
        }

        public Builder hideLeftText() {
            mLeftVisible = View.INVISIBLE;
            return this;
        }
    }

}

MainActivity类:

public class MainActivity extends AppCompatActivity {
    ViewGroup rootView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_builder);
        rootView = findViewById(R.id.ll_root);

        // 用作演示的NavigationBar
        new MyNavigationBar.Builder(this, R.layout.layout_title, rootView)
                .setText(R.id.tv_title, "首页")
                .setOnClickListener(R.id.tv_left, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, "返回", Toast.LENGTH_LONG).show();
                    }
                })
                .build();

        //默认的NavigationBar
        new DefaultNavigationBar.Builder(this, rootView)
                .setTitle("这是默认标题")
                .setLeftText("返回")
                .setRightText("确认")
                .hideLeftText()
                .setRightListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, "确认", Toast.LENGTH_LONG).show();
                    }
                })
                .build();

    }


}

布局文件很简单,就不贴出来了。

总结:

在写代码的时候,一个是高扩展,并不是要把所有的内容和出现的问题都想到,而是在新增加功能的时候可以保证原来代码不变,对于开发者来说需要就好,最少知识原则,使用者并不想关注太多。

END.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容

  • Builder(建造者)设计模式的定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。...
    请叫我张懂阅读 725评论 0 2
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,752评论 2 9
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,741评论 0 14
  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,440评论 1 24
  • 我发现,在某一个方向能够专注下去的人,必然有一天会有一定的造诣的!前端如此,安卓开发如此,书序挖掘如此,产品如此,...
    Michael翔阅读 211评论 3 1