Java和Ceylon物件的構造和驗證
本文由碼農網 – civic5216原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
當變換Java程式碼為Ceylon程式碼時,有時候我會遇到一些Java類構造器混淆了驗證與初始化的情形。讓我們使用一個簡單但是人為的程式碼例子來說明我想闡述的意思。
一些壞程式碼
考慮下面的Java類。(夥計,不要在家裡寫這樣的程式碼)
public class Period { private final Date startDate; private final Date endDate; //returns null if the given String //does not represent a valid Date private Date parseDate(String date) { ... } public Period(String start, String end) { startDate = parseDate(start); endDate = parseDate(end); } public boolean isValid() { return startDate!=null && endDate!=null; } public Date getStartDate() { if (startDate==null) throw new IllegalStateException(); return startDate; } public Date getEndDate() { if (endDate==null) throw new IllegalStateException(); return endDate; } }
嘿,我之前已經警告過,它是人為的。但是,在實際Java程式碼中找個像這樣的東西實際上並非不常見。
這裡的問題在於,即使輸入引數(在隱藏的parseDate()方法中)的驗證失敗了,我們還是會獲得一個Period的例項。但是我們獲取的那個Period不是一個“有效的”狀態。嚴格地說,我的意思是什麼呢?
好吧,假如一個物件不能有意義地響應公用操作時,我會說它處於一個非有效狀態。在這個例子裡,getStartDate() 和getEndDate()會丟擲一個IllegalStateException異常,這就是我認為不是“有意義的”一種情況。
從另外一方面來看這個例子,在設計Period時,我們這兒出現了型別安全的失敗。未檢查的異常代表了型別系統中的一個“漏洞”。因此,一個更好的Period的型別安全的設計,會是一個不使用未檢查的異常—在這個例子中意味著不丟擲IllegalStateException異常。
(實際上,在真實程式碼中,我更有可能遇到一個getStartDate() 方法它不檢查null ,在這個程式碼行之後就會導致一個NullPointerException異常,這就更加糟糕了。)
我們能夠很容易地轉換上面的Period類成為Ceylon形式的類:
shared class Period(String start, String end) { //returns null if the given String //does not represent a valid Date Date? parseDate(String date) => ... ; value maybeStartDate = parseDate(start); value maybeEndDate = parseDate(end); shared Boolean valid => maybeStartDate exists && maybeEndDate exists; shared Date startDate { assert (exists maybeStartDate); return maybeStartDate; } shared Date endDate { assert (exists maybeEndDate); return maybeEndDate; } }
當然了,這段程式碼也會遇到與原始Java程式碼同樣的問題。兩個assert符號衝著我們大喊,在程式碼的型別安全中有一個問題。
使Java程式碼變得更好
Java裡我們怎麼改進這段程式碼呢?好吧,這兒就是一個例子關於Java飽受詬病的已檢查異常會是一個非常合理的解決方法!我們可以稍微修改下Period來從它的構造器中丟擲一個已檢查的異常:
public class Period { private final Date startDate; private final Date endDate; //throws if the given String //does not represent a valid Date private Date parseDate(String date) throws DateFormatException { ... } public Period(String start, String end) throws DateFormatException { startDate = parseDate(start); endDate = parseDate(end); } public Date getStartDate() { return startDate; } public Date getEndDate() { return endDate; } }
現在,使用這個解決方案,我們就不會獲取一個處於非有效狀態的Period,例項化Period的程式碼會由編譯器負責去處理無效輸入的情形,它會捕獲一個DateFormatException異常。
try { Period p = new Period(start, end); ... } catch (DateFormatException dfe) { ... }
這是一個對已檢查異常不錯的、完美的、正確的使用,不幸的是我幾乎很少看到Java程式碼像上面這樣使用已檢查異常。
使Ceylon程式碼變得更好
那麼Ceylon怎麼樣呢?Ceylon沒有已檢查異常,因而我們需要尋找一個不同的解決方式。典型地,在Java呼叫一個函式會丟擲一個已檢查異常的情形中,Ceylon會呼叫函式返回一個聯合型別。因為,一個類的初始化不返回除了類自己外的任何型別,我們需要提取一些混合的初始化/驗證的邏輯來使其成為一個工廠函式。
//returns DateFormatError if the given //String does not represent a valid Date Date|DateFormatError parseDate(String date) => ... ; shared Period|DateFormatError parsePeriod (String start, String end) { value startDate = parseDate(start); if (is DateFormatError startDate) { return startDate; } value endDate = parseDate(end); if (is DateFormatError endDate) { return endDate; } return Period(startDate, endDate); } shared class Period(startDate, endDate) { shared Date startDate; shared Date endDate; }
根據型別系統,呼叫者有義務去處理DateFormatError:
value p = parsePeriod(start, end); if (is DateFormatError p) { ... } else { ... }
或者,如果我們不關心給定日期格式的實際問題(這是有可能的,假定我們工作的初始化程式碼丟失了那個資訊),我們可以使用Null而不是DateFormatError:
//returns null if the given String //does not represent a valid Date Date? parseDate(String date) => ... ; shared Period? parsePeriod(String start, String end) => if (exists startDate = parseDate(start), exists endDate = parseDate(end)) then Period(startDate, endDate) else null; shared class Period(startDate, endDate) { shared Date startDate; shared Date endDate; }
至少可以說,使用工廠函式的方法是優秀的,因為通常來說在驗證邏輯和物件初始化之間它具有更好的隔離。這點在Ceylon中特別有用,在Ceylon中,編譯器在物件初始化邏輯中新增了一些非常嚴厲的限制,以保證物件的所有領域僅被賦值一次。
譯文連結:http://www.codeceo.com/article/java-ceylon-object-verify.html
英文原文:Object Construction and Validation
翻譯作者:碼農網 – civic5216
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- Java--構造器和構造方法Java構造方法
- 物件導向和構造器物件
- OC物件是結構體的驗證物件結構體
- java反射構建物件和方法的反射呼叫Java反射物件
- 十七、物件的構造物件
- 構造器引用和直接用new建立物件區別物件
- Oracle基礎結構之OS驗證和口令檔案驗證Oracle
- FireFox和IE下使用Date來構造新Date物件的BUGFirefox物件
- JavaScript驗證碼生成和驗證效果JavaScript
- Effective Java - 構造器私有、列舉和單例Java單例
- java裡的物件和類Java物件
- Java 物件和類Java物件
- Java 類和物件Java物件
- Java物件和類Java物件
- java7-2 構造程式碼塊的概述和講解Java
- HTTPS 和 加密 和 AFNetworking 證書驗證HTTP加密
- 實驗5——類和物件物件
- 實驗2 類和物件物件
- 淺談JVM記憶體結構 和 Java記憶體模型 和 Java物件模型JVM記憶體Java模型物件
- K重交叉驗證和網格搜尋驗證
- PHP算式驗證碼和漢字驗證碼的實現方法PHP
- 複習JAVA面相物件(類和物件)Java物件
- ITL中xid 和 uba的驗證!
- 驗證碼的識別和運用
- Java中構造方法,構造程式碼塊和靜態程式碼塊執行順序詳解Java構造方法
- JVM裡物件的佈局和結構和訪問JVM物件
- Django(59)驗證和授權Django
- Struts 驗證框架 配置和使用框架
- java構造器Java
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- 3.java類和物件Java物件
- Java 學習:物件和類Java物件
- Java基礎| 類和物件Java物件
- 初識Java類和物件Java物件
- Java類和物件 小白版Java物件
- 使用NLP和ML來提取和構造Web資料Web
- Laravel 驗證類 實現 路由場景驗證 和 控制器場景驗證Laravel路由
- Android註冊功能--電話驗證和郵箱驗證Android