Java构建器的基本使用

       我们知道,构建java实例有很多方法;最常见的就是通过构造器方式,还有就是前几篇说的静态工厂方法的方式,今天我们就来说一说通过构建器创建java对象的方式。

1,构造器创建对象存在的问题

     

      因为今天我们要介绍通过构建器创建对象,那么它必然为了解决某些问题而出现和存在的,那我们就来看看传统的构造器创建对象会有哪些问题:

  • 类中成员变量很多,不同的成员组合可以得到不同种类的对象,这样对应的构造器就会很多(重叠构造器),比如:

public class Television {

    private int width;
    private int height;
    private float weight=0.0f;
    private String color="black";
    private String brand="panda";
    private String serialNum="ID1000";
  
    public Television(int width,int height) {
        this.width=width;
        this.height=height;
    }
    
    public Television(int width,int height,float weight) {
        this.width=width;
        this.height=height;
        this.weight=weight;
    }
    public Television(int width, int height, float weight, String color, String brand, String serialNum) {
        this.width = width;
        this.height = height;
        this.weight = weight;
        this.color = color;
        this.brand = brand;
        this.serialNum = serialNum;
    }
}

我这里的成员变量给的较少,构造器也没有写全。

  • 成员变量类型和含义类似时,很难区别构造器的选择,即便调用错误,也不会报错,所以会导致很多怪异的无法排查的问题

比如:一个电视机的长,宽,高都是int类型参数,当你调用一个只有长和宽的构造器时把高当中长传进去之后,这样的问题很难排查。

2,初步的解决办法和弊端

      到这里很多人想,这里的难点主要是在成员变量太多的时候,构造器太多;客户端代码编写起来很麻烦而且容易出错,出现错误也不好修改。简单一想,好像我们可以很简单的通过JavaBeans方式来规避这个问题。

  • 创建对象都是通过默认无参的构造器或者默认有参的构造器
  • 通过setter来设置成员变量的值

这样做,确实有以下优点:

  • 容易阅读
  • 对象创建简单

比如上述代码就可以修改为:

public class Television {

    private int width;
    private int height;
    private float weight=0.0f;
    private String color="black";
    private String brand="panda";
    private String serialNum="ID1000";
    
    public Television(int width,int height) {
        this.width=width;
        this.height=height;
    }
 

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public float getWeight() {
        return weight;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getSerialNum() {
        return serialNum;
    }

    public void setSerialNum(String serialNum) {
        this.serialNum = serialNum;
    }

}

这种方式确实解决了构造器调用困难的问题,也更加易于阅读;但是与此同时有产生了新的问题:

  • 这种方式不能保证对象的不变性,这个很容易理解:

      Television tv3 = new Television(width, height);
       //tv3变化了
        tv3.setBrand("nb");

  • 这种方式不能保证对象的一致性,于是就会产生数据安全问题;如下所示,对象存在时可以随时随地修改,很容易导致数据不一致问题(安全问题),就会出现数据安全问题

        Television tv3 = new Television(width, height);
        tv3.setBrand("nb");
        tv3.setColor("red");
        tv3.setSerialNum("ID1200");

3,构建器的引入和使用

      在尝试了使用JavaBeans模式解决出现问题之后,现在就需要一种构建对象的方式满足:

  • 达到构造器那样的数据安全性
  • 达到JavaBeans模式的可读性和灵活性

答案自然是我们今天要结束的主角构建器,其实使用构建器很简单,关键是是否选择使用它;那么我们就来看看使用它的基本思想和步骤。

  • 类中添加一个静态内部类,其中含有和要创建对象的类有一样的参数,还有一系列类似setter功能的方法;保证了灵活性
  • 该静态内部类有一个转换本类参数至外部类参数的接口(配合外部类的私有构造器);保证了安全性
  • 客户端通过创建内部类来初始化要创建对象的成员变量,然后调用内部类接口获取外部类实例对象

还是上面那一个例子:  

package hfut.edu;
/**
* Date:2018年9月25日 下午6:59:23
* Author:why
*/

public class TelevisionWithBuilder {
    
    //the required parameters
    private int width;
    private int height;
    //the optional parameters
    private float weight;
    private String color;
    private String brand;
    private String serialNum;
    
    
    //the constructor used for transfer the Builder's parameter to the Object that we want
    //to create
    private TelevisionWithBuilder(Builder builder) {
        this.width=builder.width;
        this.height=builder.height;
        this.weight=builder.weight;
        this.color=builder.color;
        this.brand=builder.brand;
        this.serialNum=builder.serialNum;    
    }
    
    
    
    public static class Builder{
        private int width;
        private int height;
        private float weight=0.0f;
        private String color="black";
        private String brand="panda";
        private String serialNum="ID1000";
        
        //the default public constructor
        public Builder(int width,int height ) {
            this.width=width;
            this.height=height;
        }
        
        //following is the Builder's functional method 
        public Builder setWeight(float weight) {
            this.weight=weight;
            return this;
        }
        public Builder setColor(String color) {
            this.color=color;
            return this;
        }
        public Builder setBrand(String brand) {
            this.brand=brand;
            return this;
        }
        public Builder setSerialNum(String serialNum) {
            this.serialNum=serialNum;
            return this;
        }
        
        //use the private constructor to build the project
        public TelevisionWithBuilder build() {
            return new TelevisionWithBuilder(this);
        }
        
    }


    @Override
    public String toString() {
        return "TelevisionWithBuilder [width=" + width + ", height=" + height + ", weight=" + weight + ", color="
                + color + ", brand=" + brand + ", serialNum=" + serialNum + "]";
    }

}

代码里很明朗:

  • 一系列属性名称的方法对应上面步骤1中介绍的灵活性
  • build方法对应上面步骤2中介绍的安全性

4,Android中构建器示例

其中在Android中有不少构建器的例子,很多的开源框架也都有使用到,今天就简单介绍一下Android中的AlertDialog的构建器

首先简单看一下AlertDialog的API部分介绍:

嵌套类摘要
static class AlertDialog.Builder

AlertDialog.Builder的API部分介绍:

方法摘要
 AlertDialog create()
          Creates a AlertDialog with the arguments supplied to this builder.
 AlertDialog.Builder setAdapter(ListAdapter adapter, DialogInterface.OnClickListener listener)
          Set a list of items, which are supplied by the given ListAdapter, to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setCancelable(boolean cancelable)
          Sets whether the dialog is cancelable or not default is true.
 AlertDialog.Builder setCursor(Cursor cursor, DialogInterface.OnClickListener listener, String labelColumn)
          Set a list of items, which are supplied by the given Cursor, to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setCustomTitle(View customTitleView)
          Set the title using the custom view customTitleView.
 AlertDialog.Builder setIcon(Drawable icon)
          Set the Drawable to be used in the title.
 AlertDialog.Builder setIcon(int iconId)
          Set the resource id of the Drawable to be used in the title.
 AlertDialog.Builder setInverseBackgroundForced(boolean useInverseBackground)
          Sets the Dialog to use the inverse background, regardless of what the contents is.
 AlertDialog.Builder setItems(CharSequence[] items, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setItems(int itemsId, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setMessage(CharSequence message)
          Set the message to display.
 AlertDialog.Builder setMessage(int messageId)
          Set the message to display using the given resource id.
 AlertDialog.Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, DialogInterface.OnMultiChoiceClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the negative button of the dialog is pressed.
 AlertDialog.Builder setNegativeButton(int textId, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the negative button of the dialog is pressed.
 AlertDialog.Builder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the neutral button of the dialog is pressed.
 AlertDialog.Builder setNeutralButton(int textId, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the neutral button of the dialog is pressed.
 AlertDialog.Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener)
          Sets the callback that will be called if the dialog is canceled.
 AlertDialog.Builder setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener)
          Sets a listener to be invoked when an item in the list is selected.
 AlertDialog.Builder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener)
          Sets the callback that will be called if a key is dispatched to the dialog.
 AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the positive button of the dialog is pressed.
 AlertDialog.Builder setPositiveButton(int textId, DialogInterface.OnClickListener listener)
          Set a listener to be invoked when the positive button of the dialog is pressed.
 AlertDialog.Builder setRecycleOnMeasureEnabled(boolean enabled)
           
 AlertDialog.Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setSingleChoiceItems(int itemsId, int checkedItem, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, DialogInterface.OnClickListener listener)
          Set a list of items to be displayed in the dialog as the content, you will be notified of the selected item via the supplied listener.
 AlertDialog.Builder setTitle(CharSequence title)
          Set the title displayed in the Dialog.
 AlertDialog.Builder setTitle(int titleId)
          Set the title using the given resource id.
 AlertDialog.Builder setView(View view)
          Set a custom view to be the contents of the Dialog.
 AlertDialog.Builder setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom)
          Set a custom view to be the contents of the Dialog, specifying the spacing to appear around that view.
 AlertDialog show()
          Creates a AlertDialog with the arguments supplied to this builder and Dialog.show()'s the dialog.

从API不难看出,它和我们上面分析的都是大同小异,下面就来看看这个AlertDialog.Builder是如何创建AlertDialog的:

/**
 * Creates an {@link AlertDialog} with the arguments supplied to this
 * builder.
 * <p>
 * Calling this method does not display the dialog. If no additional
 * processing is needed, {@link #show()} may be called instead to both
 * create and display the dialog.
 */
public AlertDialog create() {
    // Context has already been wrapped with the appropriate theme.
    final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
    P.apply(dialog.mAlert);
    dialog.setCancelable(P.mCancelable);
    if (P.mCancelable) {
        dialog.setCanceledOnTouchOutside(true);
    }
    dialog.setOnCancelListener(P.mOnCancelListener);
    dialog.setOnDismissListener(P.mOnDismissListener);
    if (P.mOnKeyListener != null) {
        dialog.setOnKeyListener(P.mOnKeyListener);
    }
    return dialog;
}

这里面的P你可以理解为一个用于保存通过Builder中属性方法设置的属性值的一个实体,其类型为:

AlertController.AlertParams

暂时也不需要纠结太深入的去理解;我们可以简单看一个例子就明白了,比如,我们想设置AlertDialog的Title,首先通过设置到该属性值到AlertDialog.Builder中的用于保存这些参数值的P中,然后通过create方法把AlertDialog.Builder中的P的参数转给AlertDialog即可;和我们上面的其实是一模一样的。

/**
 * Set the title displayed in the {@link Dialog}.
 *
 * @return This Builder object to allow for chaining of calls to set methods
 */
public Builder setTitle(CharSequence title) {
    P.mTitle = title;
    return this;
}

5,使用建议

转至《Effective Java》中文版 第2版

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择;特别是当对象的大多数参数是可选的时候。

关于使用泛型类根据需求使用Builder返回不同的对象,这里就不在举例介绍了。

猜你喜欢

转载自blog.csdn.net/hfut_why/article/details/82846123