构造函数必须没有代码
構造函數中應完成多少工作? 在構造函數內部進行一些計算然后封裝結果似乎是合理的。 這樣,當對象方法需要結果時,我們將準備好它們。 聽起來是個好方法? 不,這不對。 這是一個壞主意,原因有一個:它阻止了對象的組合并使它們不可擴展。
殺死比爾 2(2004)由昆汀·塔倫蒂諾(Quentin Tarantino)
假設我們正在制作一個代表一個人的名字的接口:
interface Name {String first(); }很簡單,對不對? 現在,讓我們嘗試實現它:
public final class EnglishName implements Name {private final String name;public EnglishName(final CharSequence text) {this.parts = text.toString().split(" ", 2)[0];}@Overridepublic String first() {return this.name;} }這怎么了 更快吧? 它僅將名稱分成幾部分,然后將其封裝。 然后,無論我們調用first()方法有多少次,它都將返回相同的值,并且無需再次進行拆分。 但是,這是有缺陷的想法! 讓我向您展示正確的方法并說明:
public final class EnglishName implements Name {private final CharSequence text;public EnglishName(final CharSequence txt) {this.text = txt;}@Overridepublic String first() {return this.text.toString().split("", 2)[0];} }這是正確的設計。 我可以看到你在微笑,所以讓我證明我的觀點。
不過,在開始驗證之前,讓我請您閱讀本文: 可組合裝飾器與命令式實用方法 。 它解釋了靜態方法和可組合裝飾器之間的區別。 上面的第一個代碼段看起來非常像一個對象,它非常接近命令式實用程序方法。 第二個例子是一個真實的對象。
在第一個示例中,我們正在濫用new運算符,并將其轉換為靜態方法,該方法會在此時此刻為我們進行所有計算。 這就是命令式編程的目的。 在命令式編程中,我們立即執行所有計算并返回完全準備好的結果。 相反,在聲明式編程中,我們嘗試盡可能長時間地延遲計算。
讓我們嘗試使用我們的EnglishName類:
final Name name = new EnglishName(new NameInPostgreSQL(/*...*/) ); if (/* something goes wrong */) {throw new IllegalStateException(String.format("Hi, %s, we can't proceed with your application",name.first())); }在此代碼段的第一行中,我們只是創建一個對象的實例并將其標記為name 。 我們還不想進入數據庫并從那里獲取全名,將其拆分為多個部分,然后將其封裝在name 。 我們只想創建一個對象的實例。 這種解析行為對我們來說將是一個副作用,在這種情況下,將減慢應用程序的速度。 如您所見,如果出現問題,我們可能只需要name.first() ,而我們需要構造一個異常對象。
我的觀點是,在構造函數內部進行任何計算都是一種不好的做法,必須避免這樣做,因為它們是副作用,對象所有者不要求這樣做。
您可能會問,重用name期間的性能如何? 如果我們創建了EnglishName的實例,然后調用了name.first()五次,則最終將對String.split()方法進行五次調用。
為了解決這個問題,我們創建了另一個類,一個可組合的decorator ,它將幫助我們解決這個“重用”問題:
public final class CachedName implements Name {private final Name origin;public CachedName(final Name name) {this.origin = name;}@Override@Cacheable(forever = true)public String first() {return this.origin.first();} }我正在使用jcabi-aspects的Cacheable批注,但是您可以使用Java(或其他語言)可用的任何其他緩存工具,例如Guava Cache :
public final class CachedName implements Name {private final Cache<Long, String> cache =CacheBuilder.newBuilder().build();private final Name origin;public CachedName(final Name name) {this.origin = name;}@Overridepublic String first() {return this.cache.get(1L,new Callable<String>() {@Overridepublic String call() {return CachedName.this.origin.first();}});} }但是請不要使CachedName可變并且延遲加載-這是一種反模式,我之前在“ 對象應該是不可變的”中已經討論過。
這是我們的代碼現在的外觀:
final Name name = new CachedName(new EnglishName(new NameInPostgreSQL(/*...*/)) );這是一個非常原始的示例,但我希望您能理解。
在此設計中,我們基本上將對象分為兩部分。 第一個知道如何從英文名稱中獲取名字。 第二個知道如何將計算結果緩存到內存中。 現在,作為這些類的用戶,我將決定如何正確使用它們。 我將決定是否需要緩存。 這就是對象構成的全部內容。
讓我重申一下,構造函數中唯一允許的語句是賦值。 如果您需要在此處放置其他內容,請開始考慮進行重構-您的課程肯定需要重新設計。
翻譯自: https://www.javacodegeeks.com/2015/05/constructors-must-be-code-free.html
總結
以上是生活随笔為你收集整理的构造函数必须没有代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 坐班是什么意思 坐班解释
- 下一篇: 户外狩猎的常见技巧 户外狩猎的常见小技巧