如何更好地建立物件

雨帆發表於2015-03-19

寫Java一類的面相物件語言的程式設計師常常會遇到這麼一個冷笑話:我今年都30了,還沒找到物件,怎麼辦?簡單啊,new 一個物件就好。

當然這只是一個冷笑話,所謂的 new 一個物件,無非不就是呼叫這個類的構造方法去建立物件。乍一看也沒什麼問題,寫個類ABC,用的時候new ABC()就好了。那麼,會想一下,我們讀書的時候,老師一定會說一個類的構造方法可以允許傳入引數,甚至根據傳入引數的不同建立多個構造方法。

學過物件導向的你一定會說,沒錯啊,就是這樣,方法過載,簽名校驗。都是一個方法,Perfect!那麼,我們設想下面這麼一種情況:

假如我們有一個DTO類 CredentialsAuthParam 作為對外介面的傳入引數,作為一個POJO類,我們一般就是定義一堆屬性,然後一堆Getter、Setter,比如,我們可以這麼定義:

package me.yufan.dto;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;

public class CredentialsAuthParam implements Serializable {

    private static final long serialVersionUID = -1L;

    private String source;

    private String validationCode;

    private String operator;

    private String remark;

    public CredentialsAuthParam() {
    }

    public String getValidationCode() {
        return validationCode;
    }

    public void setValidationCode(String validationCode) {
        this.validationCode = validationCode;
    }

    public String getOperator() {
        return operator;
    }

    public void setOperator(String operator) {
        this.operator = operator;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this,
                ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

看著很棒,多麼簡單,用 CredentialsAuthParam 的時候 new 一下,作為傳入引數呼叫介面的方法就好。

那麼,如果我的物件要設定屬性怎麼辦?一個個Setter?為何不能在建立物件時建立呢?比如,構造方法裡面指定引數?沒錯,然後我們就有了下面的一堆構造方法。

public CredentialsAuthParam(String source, String validationCode) {
    this.source = source;
    this.validationCode = validationCode;
}

public CredentialsAuthParam(String source, String validationCod, String operator) {
    this.source = source;
    this.validationCode = validationCode;
    this.operator = operator;
}

public CredentialsAuthParam(String source, String validationCod,
                              String operator, String remark) {
    this.source = source;
    this.validationCode = validationCode;
    this.operator = operator;
    this.remark = remark;
}

那麼問題來了,引數這麼多,又都是String,建立一個物件多麻煩。又比如,我只想指定其中3個屬性,又都是String,但現在已經有了一個為3個String 引數的構造方法。怎麼辦?

Java對方法過載的判斷是按照簽名的型別進行校驗。所以方法引數順序需要開發在使用時自己指定,也許我方法引數是:String source, String validationCode。但結果我因為複製貼上不仔細變成了this.source = validationCode; this.validationCode = source;(別笑,你忙著寫垃圾程式碼的時候就會出錯) 或者構造方法使用者弄混了兩個引數的順序。那麼就會出事啦。

其實如果你看過Java聖經Effective Java的話,一定會注意到裡面說過構造器(Builder),多個引數的構造方法一定要考慮使用構造器。比如,我們可以這麼寫:

package me.yufan.dto;

public class CredentialsAuthParamBuilder {

    private String source = "";
    private String validationCode;
    private String operator = "";
    private String remark = "";

    public CredentialsAuthParamBuilder setSource(String source) {
        this.source = source;
        return this;
    }

    public CredentialsAuthParamBuilder setValidationCode(String validationCode) {
        this.validationCode = validationCode;
        return this;
    }

    public CredentialsAuthParamBuilder setOperator(String operator) {
        this.operator = operator;
        return this;
    }

    public CredentialsAuthParamBuilder setRemark(String remark) {
        this.remark = remark;
        return this;
    }

    public CredentialsAuthParam createCredentialsAuthParam() {
        return new CredentialsAuthParam(source, validationCode, operator, remark);
    }
}

然後將原來類的構造方法定義為Protected,然後建立物件的時候只需要:

new CredentialsRequestParamBuilder()
.setRemark("remark")
.createCredentialsRequestParam();

按照需求,設定幾個屬性就加幾個 set 方法。

接下來,我們說說單例模式:

單例(Singleton),顧名思義,就是隻被例項化一次的類。比如,我在MVC的攔截器中需要呼叫一個公共類,它裡面存放的東西是大家共享的,我可以這麼寫:

public class AuthorityInterceptorHelper {

    private static AuthorityInterceptorHelper instance;

    private AuthorityInterceptorHelper() {

    }

    public static AuthorityInterceptorHelper getInstance() {
        if (instance == null) {
            synchronized (AuthorityInterceptorHelper.class) {
                if (instance == null) {
                    instance = new AuthorityInterceptorHelper();
                }
            }
        }
        return instance;
    }
//    other code ...
}

我首先要在內部定義一個自身的靜態物件,然後將構造方法私有,getInstance()會先去看靜態物件存在否,不存在,先加鎖,也許加鎖期間其他方法先呼叫此方法建立物件,再看看物件是否存在,不存在,建立物件解鎖。

看起來沒什麼問題,程式碼嚴密,十分規範,大家都是這麼寫的。但是,單例了麼?定義為私有的方法一定沒法訪問了麼?反射呢?

其實列舉類便可以輕鬆實現需求,我們只需如下寫法:

public enum AuthorityInterceptorHelper {

    INSTANCE;

//    other function ...
}

相關文章