成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

第2項(xiàng):當(dāng)面臨多個(gè)參數(shù)的構(gòu)造器時(shí)考慮使用構(gòu)建器

KavenFan / 1782人閱讀

摘要:因此,最好一開始就考慮使用構(gòu)造器。與使用傳統(tǒng)的重疊構(gòu)造器模式相比,使用模式的客戶端代碼更易于閱讀和編寫,構(gòu)建器也比更加安全。

??靜態(tài)工廠和構(gòu)造器有個(gè)共同的局限性:他們都不能很好地?cái)U(kuò)展到大量的可選參數(shù)??紤]用一個(gè)類表示包裝食品外面顯示的營(yíng)養(yǎng)成分標(biāo)簽。這些標(biāo)簽中有幾個(gè)域是必需的:每份的含量、每罐的含量以及每份的卡路里,還有超過20個(gè)可選域:總脂肪、飽和脂肪量、轉(zhuǎn)化脂肪、膽固醇、鈉等等。大多數(shù)產(chǎn)品在某幾個(gè)可選域中都會(huì)有非零的值。

??對(duì)于這樣的類,應(yīng)該采用哪種構(gòu)造器或者靜態(tài)方法來(lái)編寫呢?程序猿一向習(xí)慣采用重疊構(gòu)造器(telescoping constructor)模式,在這種模式下,提供一個(gè)只有必要參數(shù)的構(gòu)造器,第二個(gè)構(gòu)造器有一個(gè)可選參數(shù),第三個(gè)有兩個(gè)可選參數(shù),以此類推,最后一個(gè)構(gòu)造器包含所有可選參數(shù)。下面有個(gè)示例,為了簡(jiǎn)單起見,它顯示四個(gè)可選域:

// Telescoping constructor pattern - does not scale well!
public class NutritionFacts {
    private final int servingSize; // (mL) required
    private final int servings; // (per container) required
    private final int calories; // (per serving) optional
    private final int fat; // (g/serving) optional
    private final int sodium; // (mg/serving) optional
    private final int carbohydrate; // (g/serving) optional

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

??當(dāng)你想要?jiǎng)?chuàng)建實(shí)例的時(shí)候,就利用參數(shù)列表最短的構(gòu)造器,但該列表中包含了要設(shè)置的所有參數(shù):NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);這個(gè)構(gòu)造器調(diào)用通常需要許多你本不想設(shè)置的參數(shù),但還是不得不為它們傳遞值。在這個(gè)例子中,我們給fat傳遞了一個(gè)值為0。如果“僅僅”是這6個(gè)參數(shù),看起來(lái)還不算太糟,問題是隨著參數(shù)數(shù)目的增加,它很快就失去了控制。

??總的來(lái)說(shuō),使用重疊構(gòu)造器模式是可行的,但是當(dāng)有很多參數(shù)的時(shí)候就很難編寫客戶端代碼,也很難去閱讀它們。如果讀者想要知道這些值代表什么意思,就必須仔細(xì)地?cái)?shù)著這些參數(shù)來(lái)探個(gè)究竟。一長(zhǎng)串類型相同的參數(shù)會(huì)導(dǎo)致一些微妙的錯(cuò)誤,如果客戶端不小心顛倒了期中兩個(gè)參數(shù)的順序,編譯器也不會(huì)報(bào)錯(cuò),但是程序在運(yùn)行的時(shí)候就會(huì)出現(xiàn)錯(cuò)誤的行為。

??遇到許多構(gòu)造器參數(shù)的時(shí)候,還有第二種代替方法,即JavaBean模式,在這種模式下,調(diào)用一個(gè)無(wú)參構(gòu)造器來(lái)創(chuàng)建對(duì)象,然后調(diào)用setter方法來(lái)設(shè)置每個(gè)必要的參數(shù),以及每個(gè)相關(guān)的可選參數(shù):

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // Required; no default value
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    public NutritionFacts() { }
    // Setters
    public void setServingSize(int val) { servingSize = val; }
    public void setServings(int val) { servings = val; }
    public void setCalories(int val) { calories = val; }
    public void setFat(int val) { fat = val; }
    public void setSodium(int val) { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

??這種模式彌補(bǔ)了重疊構(gòu)造器模式的不足。說(shuō)得明白一點(diǎn),就是創(chuàng)建實(shí)例很容易,這樣產(chǎn)生的代碼讀起來(lái)也很容易。

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

??遺憾的是,這種JavaBean模式自身有很嚴(yán)重的缺點(diǎn)。因?yàn)闃?gòu)造過程被分到了幾個(gè)調(diào)用中,在構(gòu)造的過程中JavaBean可能處于不一致的狀態(tài)。類無(wú)法通過檢驗(yàn)構(gòu)造器參數(shù)的有效性來(lái)保證一致性。試圖使用處于不一致狀態(tài)的對(duì)象,將會(huì)導(dǎo)致失敗,這種失敗與包含錯(cuò)誤的代碼大相徑庭,因此它調(diào)試起來(lái)十分困難。與此相關(guān)的另一點(diǎn)不足在于,JavaBean模式阻止了把類做成了不可變的可能(第17項(xiàng)),這就需要程序猿付出額外的努力來(lái)保證它的線程安全。

??在構(gòu)造器完成構(gòu)造對(duì)象之前進(jìn)行加鎖,完成構(gòu)造之后進(jìn)行解鎖,這就能彌補(bǔ)以上的不足之處,但是這種方式十分笨拙,在實(shí)踐中很少使用。此外,它甚至?xí)谶\(yùn)行時(shí)出現(xiàn)錯(cuò)誤,因?yàn)榫幾g器無(wú)法確保程序猿會(huì)在使用構(gòu)造器之前進(jìn)行加鎖操作。

??幸運(yùn)的是,還有第三種替代方法,結(jié)合了重疊構(gòu)造器的安全性和JavaBean模式的可讀性。這就是Builder模式 [Gamma95] 的一種形式。不直接生成想要的對(duì)象,而是讓客戶端調(diào)用一個(gè)帶有所有必需參數(shù)的構(gòu)造器方法(或者靜態(tài)工廠方法)去獲得一個(gè)builder對(duì)象,然后客戶端在builder對(duì)象上調(diào)用類似于setter的方法來(lái)設(shè)置每個(gè)相關(guān)的可選參數(shù)。最后,客戶端調(diào)用無(wú)參的build方法來(lái)生成不可變的對(duì)象。這個(gè)builder通常是它構(gòu)建的類的靜態(tài)成員類(第24項(xiàng))。下面就是它的示例:

// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;
        // Optional parameters - initialized to default values
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

??NutritionFacts是不可變的,所有默認(rèn)參數(shù)值都多帶帶放在一個(gè)地方。buildersetter方法返回的是builder本身,以便可以把調(diào)用連接起來(lái)。下面是客戶端代碼:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

??這樣的客戶端代碼是很容易編寫的,更重要的是,閱讀起來(lái)很容易。Builder模式模仿了Python和Scala中的命名可選參數(shù)。

??為簡(jiǎn)潔起見,省略了有效性檢查。 要盡快檢測(cè)無(wú)效參數(shù),請(qǐng)?jiān)跇?gòu)建器的構(gòu)造函數(shù)和方法中檢查參數(shù)有效性。 檢查構(gòu)建方法調(diào)用的構(gòu)造函數(shù)中涉及多個(gè)參數(shù)的不變量。 要確保這些不變量不受攻擊,請(qǐng)?jiān)趶臉?gòu)建器復(fù)制參數(shù)后對(duì)對(duì)象字段執(zhí)行檢查(第50項(xiàng))。 如果檢查失敗,則拋出IllegalArgumentException(第72項(xiàng)),其詳細(xì)消息指示哪些參數(shù)無(wú)效(第75項(xiàng))。

??Builder模式非常適合類層次結(jié)構(gòu)。 使用并行的構(gòu)建器層次結(jié)構(gòu),每個(gè)構(gòu)建器都嵌套在相應(yīng)的類中。 抽象類有抽象構(gòu)建器; 具體課程有混凝土建造者。例如,在代表各種批薩的層次結(jié)構(gòu)的根部考慮使用一個(gè)抽象類:

// Builder pattern for class hierarchies
public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set toppings;
    abstract static class Builder> {
        EnumSet toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        abstract Pizza build();
        // Subclasses must override this method to return "this"
        protected abstract T self();
    }

    Pizza(Builder builder) {
        toppings = builder.toppings.clone(); // See Item 50
    }
}

??注意下這個(gè)Pizza類,Builder是具有遞歸類型參數(shù)的通用類型(第30項(xiàng))。 這與抽象方法self一起允許方法鏈在子類中正常工作,而不需要強(qiáng)制轉(zhuǎn)換。Java缺乏自我類型這一事實(shí)的解決方法被稱為模擬自我類型習(xí)語(yǔ)(This workaround for the fact that Java lacks a self type is known as the simulated self-type idiom.)。

??這是Pizza的兩個(gè)具體子類,其中一個(gè)代表標(biāo)準(zhǔn)的紐約式披薩,另一個(gè)代表calzone。 前者具有所需的大小參數(shù),而后者允許你指定醬汁應(yīng)該在內(nèi)部還是外部:

public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;
    public static class Builder extends Pizza.Builder {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

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

        @Override protected Builder self() { 
            return this;
        }
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder {
        private boolean sauceInside = false; // Default
        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

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

        @Override protected Builder self() { 
            return this;
        }
    }

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }
}

??請(qǐng)注意,每個(gè)子類的構(gòu)建器中的構(gòu)建方法被聲明為返回正確的子類:NyPizza.Builder的構(gòu)建方法返回NyPizza,而Calzone.Builder中的構(gòu)建方法返回Calzone。這種技術(shù),其中子類方法聲明的返回類型是在超類中聲明的返回類型的子類型,稱為協(xié)變返回類型,它允許客戶使用這些構(gòu)建器而無(wú)需進(jìn)行創(chuàng)建。

??這些“分層構(gòu)建器”的客戶端代碼基本上與簡(jiǎn)單的NutritionFacts構(gòu)建器的代碼相同。為簡(jiǎn)潔起見,下面顯示的示例客戶端代碼假定枚舉常量上的靜態(tài)導(dǎo)入:

NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();

??構(gòu)建器相對(duì)于構(gòu)造函數(shù)的一個(gè)小優(yōu)點(diǎn)是構(gòu)建器可以有多個(gè)可變參數(shù),因?yàn)槊總€(gè)參數(shù)都是在自己的方法中指定的。除此之外,構(gòu)建器可以將傳遞給一個(gè)方法的多個(gè)參數(shù)通過多次調(diào)用方法的方式聚合到一個(gè)字段中,就如之前addTopping方法中所演示的那樣。

??Builder模式非常靈活。 可以重復(fù)使用單個(gè)構(gòu)建器來(lái)構(gòu)建多個(gè)對(duì)象。 可以在構(gòu)建方法的調(diào)用之間調(diào)整構(gòu)建器的參數(shù),以改變創(chuàng)建的對(duì)象。構(gòu)建器可以在創(chuàng)建對(duì)象時(shí)自動(dòng)填充某些字段,例如每次創(chuàng)建對(duì)象時(shí)增加的序列號(hào)。

??Builder模式也有缺點(diǎn)。 要?jiǎng)?chuàng)建對(duì)象,必須先創(chuàng)建其構(gòu)建器。 雖然在實(shí)踐中創(chuàng)建此構(gòu)建器的成本不太可能明顯,但在性能關(guān)鍵的情況下可能會(huì)出現(xiàn)問題。此外,Builder模式比重疊構(gòu)造函數(shù)的模式更冗長(zhǎng),因此只有在有足夠的參數(shù)(例如四個(gè)或更多)時(shí)才值得去使用它。 但請(qǐng)記住,你可能希望在將來(lái)添加更多的參數(shù)。但是如果你從構(gòu)造函數(shù)或靜態(tài)工廠開始并在類進(jìn)化到參數(shù)數(shù)量失控時(shí)才切換到構(gòu)建器,那些過時(shí)的構(gòu)造器和靜態(tài)工廠就會(huì)顯得非常不協(xié)調(diào)。因此,最好一開始就考慮使用構(gòu)造器。

??簡(jiǎn)而言之,如果類的構(gòu)造器或者靜態(tài)工廠方法中具有多個(gè)參數(shù),設(shè)計(jì)這種類時(shí),Builder模式就是種不錯(cuò)的選擇,特別是當(dāng)大多數(shù)參數(shù)都是可選的時(shí)候。與使用傳統(tǒng)的重疊構(gòu)造器模式相比,使用Builder模式的客戶端代碼更易于閱讀和編寫,構(gòu)建器也比JavaBean更加安全。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73875.html

相關(guān)文章

  • Effective Java 三版 全文翻譯

    摘要:本章中的大部分內(nèi)容適用于構(gòu)造函數(shù)和方法。第項(xiàng)其他方法優(yōu)先于序列化第項(xiàng)謹(jǐn)慎地實(shí)現(xiàn)接口第項(xiàng)考慮使用自定義的序列化形式第項(xiàng)保護(hù)性地編寫方法第項(xiàng)對(duì)于實(shí)例控制,枚舉類型優(yōu)先于第項(xiàng)考慮用序列化代理代替序列化實(shí)例附錄與第版中項(xiàng)目的對(duì)應(yīng)關(guān)系參考文獻(xiàn) effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個(gè)人業(yè)余翻譯,不合理的地方,望指正,感激...

    galois 評(píng)論0 收藏0
  • 細(xì)說(shuō) Javascript 對(duì)象篇(二) : 原型對(duì)象

    摘要:并沒有類繼承模型,而是使用原型對(duì)象進(jìn)行原型式繼承。我們舉例說(shuō)明原型鏈查找機(jī)制當(dāng)訪問一個(gè)對(duì)象的屬性時(shí),會(huì)從對(duì)象本身開始往上遍歷整個(gè)原型鏈,直到找到對(duì)應(yīng)屬性為止。原始類型有以下五種型。此外,試圖查找一個(gè)不存在屬性時(shí)將會(huì)遍歷整個(gè)原型鏈。 Javascript 并沒有類繼承模型,而是使用原型對(duì)象 prototype 進(jìn)行原型式繼承。 盡管人們經(jīng)常將此看做是 Javascript 的一個(gè)缺點(diǎn),然...

    lansheng228 評(píng)論0 收藏0
  • Effective Java 3rd.Edition 翻譯

    摘要:推薦序前言致謝第一章引言第二章創(chuàng)建和銷毀對(duì)象第項(xiàng)用靜態(tài)工廠方法代替構(gòu)造器第項(xiàng)遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器第項(xiàng)用私有構(gòu)造器或者枚舉類型強(qiáng)化屬性第項(xiàng)通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力第項(xiàng)優(yōu)先考慮依賴注入來(lái)引用資源第項(xiàng)避免創(chuàng)建不必要的對(duì)象 推薦序 前言 致謝 第一章 引言 第二章 創(chuàng)建和銷毀對(duì)象 第1項(xiàng):用靜態(tài)工廠方法代替構(gòu)造器 第2項(xiàng):遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器 第...

    KoreyLee 評(píng)論0 收藏0
  • 二章 創(chuàng)建和銷毀對(duì)象

    摘要:一個(gè)類可以提供一個(gè)公共靜態(tài)工廠方法,它僅僅是一第項(xiàng)遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器靜態(tài)工廠和構(gòu)造器有個(gè)共同的局限性他們都不能很好地?cái)U(kuò)展到大量的可選參數(shù)。 ??本章涉及創(chuàng)建和銷毀對(duì)象,包括何時(shí)以及如何創(chuàng)建它們,何時(shí)以及如何避免創(chuàng)建它們,如何確保它們被及時(shí)銷毀,以及如何管理在銷毀之前必須進(jìn)行的清理操作。 第1項(xiàng):用靜態(tài)工廠方法代替構(gòu)造器 ??類允許客戶端獲取實(shí)例的傳統(tǒng)方法是提供公共構(gòu)造...

    Jeffrrey 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<