遇到多个构造器参数时要考虑用构建器(effective java v2)

内容基于effective java v2

 

举例来说:

一个手机会有很多模块,有些是必选:比如打电话,发短信;有些是可选的:比如WIFI,GPS,摄像头等等。那么Phone的构造函数应该如何实现呢

 

一种方式是重叠构造其模式

提供一个只含有“电话,短信”模块的构造器,然后提供含有一个可选参数的构造器,提供含有两个可选参数的构造器……稍懂点排列组合的人马上就能意识到,这么多构造器,写不起啊!就算写的起也用不起阿,谁知道每个构造器都是干什么的!

我们给出一个含有所有参数的构造器,不想设置的参数可以设置为一个无效的参数等,如果参数不多还好,不然一样会无法控制

 

另一种办法是只提供一个含有必选参数的构造器,其余的参数使用set方法设置。

这样的优势是容易创建、易于阅读。但是会失去一致性,也阻止了把类设计成不可变的,线程安全是个问题

 

第三种方案是使用Builder模式,不直接生成想要的对象,而是生成一个builder,让builder设置好所有需要的参数后build出来一个需要的对象

public class Phone {
        //Phone是我们最终要建立的对象
	private Phone(boolean call, boolean sms){
		this.call = call;
		this.sms = sms;
	}
	
	private boolean call;
	private boolean sms;
	private boolean mms;
	private boolean wifi;
	private boolean gps;
	private boolean camera;
	
	public static class Builder{
		private boolean call;
		private boolean sms;
		private boolean mms;
		private boolean wifi;
		private boolean gps;
		private boolean camera;
		
		public Builder(boolean call, boolean sms){
			this.call = call;
			this.sms = sms;
		}
		
		public Builder setMms(boolean mms){
			this.mms = mms;
			return this;
		}
		
		public Builder setWfi(boolean wifi){
			this.wifi = wifi;
			return this;
		}
		
		public Builder setGps(boolean gps){
			this.gps = gps;
			return this;
		}
		
		public Builder setCamera(boolean camera){
			this.camera = camera;
			return this;
		}
		
		public Phone build(){
			Phone phone = new Phone(call, sms);
			phone.mms = this.mms;
			phone.wifi = this.wifi;
			phone.gps = this.gps;
			phone.camera = this.camera;
			return t;
		}
	}
}

书中的例子在build函数如下

Phone phone = new Phone(this); 

我觉得在build的时候对象t还没有对外开放,所以在构造函数之后复制和在其中复制是一样的。所以没有写成下面这样

private Phone(Builder builder){
	this.call = builder.call;
	this.sms = builder.sms;
}

书中之所以这么写还有一点原因是因为它的变量都为final的,不得不在构造函数中初始化,而我没有此修饰。

 

这里Phone的构造函数设置为private是防止外部任何形式的调用,想生成一个Phone对象,那么就通过Builder吧

 

android构建器的应用最常见的应该是AlertDialog和AlertDialog.Builder了吧

下面是email一个使用AlertDialog的代码片段

Dialog com.android.email.activity.AttachmentInfoDialog.onCreateDialog(Bundle savedInstanceState)

AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
builder.setMessage(infoText);
builder.setNeutralButton(R.string.okay_action, onClickListener);
if (actionText != null && actionIntent != null) {
    builder.setPositiveButton(actionText, onClickListener);
}
return builder.show();

设置标题,设置信息,设置按钮,最后显示出来。

似乎完全符合上面所说的调用方式,那么下面看看内部是如何实现的

public static class Builder {
    private final AlertController.AlertParams P;
    private int mTheme;

    public Builder(Context context) {
        this(context, resolveDialogTheme(context, 0));
    }

    public Builder(Context context, int theme) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
                context, resolveDialogTheme(context, theme)));
        mTheme = theme;
    }
    public Builder setTitle(int titleId) {
        P.mTitle = P.mContext.getText(titleId);
        return this;
    }

    public Builder setTitle(CharSequence title) {
        P.mTitle = title;
        return this;
    }

    ......

    public Builder setIcon(int iconId) {
        P.mIconId = iconId;
        return this;
    }

    public Builder setIcon(Drawable icon) {
        P.mIcon = icon;
        return this;
    }

    ......
	
    public AlertDialog create() {
        final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
        P.apply(dialog.mAlert);
        dialog.setCancelable(P.mCancelable);
        if (P.mCancelable) {
            dialog.setCanceledOnTouchOutside(true);
        }
        dialog.setOnCancelListener(P.mOnCancelListener);
        if (P.mOnKeyListener != null) {
            dialog.setOnKeyListener(P.mOnKeyListener);
        }
        return dialog;
    }

    public AlertDialog show() {
        AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }
}

每个用来初始化的set都是把参数赋给private final AlertController.AlertParams P,然后返回this

能用来创建实例的有两个函数:create()和show(),其中show()调用了create()

实例化的时候调用了下面的构造函数

AlertDialog(Context context, int theme, boolean createContextWrapper) {
	super(context, resolveDialogTheme(context, theme), createContextWrapper);
	mWindow.alwaysReadCloseOnTouchAttr();
	mAlert = new AlertController(getContext(), this, getWindow());
}

其中三个参数都是必选

之后设置其他参数,返回AlertDialog实例

期间调用了apply函数

P.apply(dialog.mAlert);

mAlert是AlertDialog的一个私有成员变量

private AlertController mAlert;

这里还涉及了AlertParams类,我们看看这个类都干了些什么

public static class AlertParams {
    public void apply(AlertController dialog) {
        if (mCustomTitleView != null) {
            dialog.setCustomTitle(mCustomTitleView);
        } else {
            if (mTitle != null) {
                dialog.setTitle(mTitle);
            }
            if (mIcon != null) {
                dialog.setIcon(mIcon);
            }
            if (mIconId >= 0) {
                dialog.setIcon(mIconId);
            }
        }
        if (mMessage != null) {
            dialog.setMessage(mMessage);
        }
        if (mPositiveButtonText != null) {
            dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                    mPositiveButtonListener, null);
        }
        if (mNegativeButtonText != null) {
            dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                    mNegativeButtonListener, null);
        }
        if (mNeutralButtonText != null) {
            dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                    mNeutralButtonListener, null);
        }
        if (mForceInverseBackground) {
            dialog.setInverseBackgroundForced(true);
        }

        if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
            createListView(dialog);
        }
        if (mView != null) {
            if (mViewSpacingSpecified) {
                dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                        mViewSpacingBottom);
            } else {
                dialog.setView(mView);
            }
        }
    }
}

一目了然,就一个apply函数,用来设置AlertController的各种状态

 

 

我们可以在build方法中检查参数,看其是否满足约束条件。

builder的好处很多,其中一条就是不用写若干个方法来对应若干可选参数的组合。

builder也不需要考虑常规set带来的线程安全问题,而且使用set的类无法设计成不可变的。

但是builder也有弊端,比如需要创建Builder之后才能创建最终对象

 

 

转贴请保留以下链接

本人blog地址

http://su1216.iteye.com/

http://blog.csdn.net/su1216/

猜你喜欢

转载自su1216.iteye.com/blog/1636344