天天看點

Java最佳實踐經驗第1條:用靜态工廠方法代替構造器

用靜态工廠方法代替構造器

      • 優勢
        • 1、靜态工廠方法有名稱
        • 2、不必每次調用它們的時候都建立一個新對象
        • 3、可以傳回原傳回類型的任何子類型
        • 4、所傳回的對象的類可以随着每次調用而發生變化,這取決于靜态工廠方法的參數值
        • 5、方法傳回的對象所屬的類,在編寫包含該靜态工廠方法時候的類時可以不存在
      • 說完了好處,來講講缺點
        • 1、靜态工廠方法最主要的缺點在于,類如果不包含公有的或被保護的構造器,就不能被子類執行個體化
        • 2、第二個缺點在于,程式員很難發現這些靜态方法
      • 靜态工廠方法的慣用名稱
      • 總結

首先舉個例子:

class LazyMan{
    //1、   建立一個私有的靜态的指向自己的變量
    private static LazyMan instance;

    //2、   私有化構造器,如果是第一次調用将建立對象執行個體,以後就不會再次建立
    private LazyMan() {
        System.out.println("-------建立了一個懶漢式LazyMan對象------");
    }

    //3、   建立一個公共的靜态方法,
    public static LazyMan getInstance() {
        if(instance == null) {
            instance = new LazyMan();
        }
        return instance;
    }
}
           

這是個懶漢式的單例設計,他沒有共有的構造器,對外隻開放了一個

getInstance

的方法,這個方法的傳回值類型是其本身,在其他類中需要使用

LazyMan

的對象時,隻需要通過調用

LazyMan.getInstance()

,這就是一個典型的用靜态工廠方法代替構造器的案例實踐。那麼這種方式有一些什麼特點呢?唯物主義價值觀告訴我們,要辯證地看待一件事情,敲代碼也是一樣的,正如沒有完美的程式設計語言一般,再優秀的設計思想也有其糟粕之處。

優勢

1、靜态工廠方法有名稱

儲備知識:擷取方法簽名

javap -s 包名.類名

,方法簽名中帶有參數類型和方法名

Java最佳實踐經驗第1條:用靜态工廠方法代替構造器

當一個類需要多個帶有相同簽名的構造器時,就可以用靜态工廠方法代替構造器,并且仔細地選擇名稱以突出靜态工廠方法之間的差別。

2、不必每次調用它們的時候都建立一個新對象

這個也很好舉例子,典型的應用就是在單例設計中,直接建好了一個對象作為類中的成員屬性放着,等調用

getInstance()

方法的時候把已經建立的對象傳出去,這樣永遠都隻有一個對象在使用。熟悉

Spring

架構的小夥伴應該記得Spring的IOC容器管理也是預設用的單例模式,同時隻存有一個對象,不必每次調用它們的時候都建立一個新對象。

這種方式還有個好處:可以使得不可變類(在後面的部落格裡詳細講)可以使用預先建構好的對象,還記得

Java5

版本中新增的包裝類麼?在Boolean包裝類中就有這麼一條:

public static Boolean valueOf(boolean b) {
	   return b ? Boolean.TRUE : Boolean.FALSE;
}
           

就用到了這種思想:它沒有建立對象,而是用了預先建構好的對象,我們來看下源碼:

Java最佳實踐經驗第1條:用靜态工廠方法代替構造器

其原理很類似于享元模式

(挖個坑,後面補)

3、可以傳回原傳回類型的任何子類型

以這種形式隐藏實作類會使得API更加簡潔,很适用于

基于接口的架構

。這個很好了解:能使用父類的地方都可以使用其子類替代——裡氏替換原則

(挖個坑。後面補五大設計原則和一大設計法則)

。no speaking,just coding,讓我們來寫一個demo試一下:

随便寫個接口

public interface Fruit {
    
}
           

來個類實作接口,用接口的類型來作為靜态工廠方法的傳回值類型,這裡要把構造器私有化防止外部調用

public class Apple implements Fruit{
    private String color;
    private int size;
    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "color='" + color + '\'' +
                ", size=" + size +
                '}';
    }

    public static Fruit getInstance(String color, int size){
        return new Apple(color,size);
    }
}
           

在demo裡調用getInstance方法,看看能否用子類型去接收

public class Demo1 {
    public static void main(String[] args) {
        Apple apple = (Apple) Apple.getInstance("紅色", 100);
        System.out.println(apple);
    }
}
           
Java最佳實踐經驗第1條:用靜态工廠方法代替構造器

4、所傳回的對象的類可以随着每次調用而發生變化,這取決于靜态工廠方法的參數值

這句話有2層含義:第一,傳回對象的類可以是已聲明的傳回類型的所有子類型,這都是允許的。第二,方法的重載于傳回值類型無關,我們可以定義多個不同參數的靜态工廠方法,對應傳回不同的對象。

5、方法傳回的對象所屬的類,在編寫包含該靜态工廠方法時候的類時可以不存在

聽起來比較繞,其實很好了解,這一條對應了前2條,舉個例子就明白了,比如我現在寫了一個接口

Fruit

,在接口裡聲明了一個靜态工廠方法:

public interface Fruit {
    static Fruit getInstance(String color, int size) {
        return null;
    }
}
           

這裡有個點要提醒下:在java8之前接口中不能存在

static

關鍵字,通常采用

将靜态方法放在一個不可執行個體化的伴生類

中的形式加以存儲。最典型的例子就是

Java Collections Framework

,所有對集合接口的工具實作都是通過靜态工廠方法在一個不可執行個體化的類

java.util.Collections

中導出。

讓我們回來繼續之前的話題,在寫

Fruit

接口的時候,我還沒有想好要用什麼類來實作它,現在我想好了,我們讓一個

Apple

類來實作

Fruit

接口:

public class Apple implements Fruit{
    private String color;
    private int size;
    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "color='" + color + '\'' +
                ", size=" + size +
                '}';
    }

    public static Apple getInstance(String color, int size){
        return new Apple(color,size);
    }
}
           

檢驗結果,同樣可以正常傳回一個

Apple

類的對象:

public class Demo1 {
    public static void main(String[] args) {
        Apple apple = Apple.getInstance("紅色", 100);
        System.out.println(apple);
    }
}
//輸出
Apple{color='紅色', size=100}
           

這大大增加了靈活性,并且符合

開閉原則

說完了好處,來講講缺點

1、靜态工廠方法最主要的缺點在于,類如果不包含公有的或被保護的構造器,就不能被子類執行個體化

例如要想将

Collection Framework

中的任何便利的實作類子類化,這是不可能的。但是這樣也許會因禍得福,因為它鼓勵了程式員使用複合(組合)(composition),而不是繼承,這正是不可變類型所需要的,也防止了繼承的濫用。

2、第二個缺點在于,程式員很難發現這些靜态方法

在API文檔中,它們沒有像構造器那樣在API文檔中明确辨別出來,是以對于提供了靜态工廠方法而不是構造器的類來說,要想查明如何執行個體化一個類是非常困難的。Javadoc工具總有一天會注意到靜态工廠方法。同時我們現在可以通過遵守标準的命名習慣,來彌補這一劣勢。下面提供了一些慣用名稱。

靜态工廠方法的慣用名稱

1、from:類型轉換方法,它隻有單個參數,傳回該類型的一個相對應的執行個體,例如:

Date d = Date.from(instant);

2、of:聚合方法,帶有多個參數,傳回該類型的一個執行個體病合并,例如:

Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING);

3、valueOf:比from和of更繁瑣的一種替代方法,例如:

BigInteger prime = BigInteger .valueOf(Integer.MAX_VALUE);

4、instance或者getInstance:傳回的執行個體是通過方法的參數來描述的(如有),但是不能說與參數具有相同的值,例如:

StackWalker luke = StackWalker .getInstance(options);

5、create或者newInstance:像instance或者getInstance一樣,但create或者newInstance能夠確定每次調用都傳回一個新的執行個體,例如:

Object newArray = Array.newInstance(classObject,arrayLen);

6、getType:像getInstance一樣,但是在工廠方法傳回值處于不同的類中的時候使用,Type表示工廠方法所傳回的對象類型,例如:

FileStore fs = Files.getFileStore(path);

7、newType:像newInstance一樣,但是在工廠方法傳回值處于不同的類中的時候使用,Type表示工廠方法所傳回的對象類型,例如:

BufferReader br = File.newBufferedReader(path);

8、type:getType和newType的簡化版,例如:

List<Complaint> litany = Collections.list(legacyLitany);

總結

靜态工廠方法和公有構造器各有用處,我們需要了解他們各自的長處。靜态工廠經常更加合适,是以切忌第一反應就是提供公有的構造器,而不先考慮靜态工廠。