两全其美的
使用抽象文檔模式的類型安全視圖
您如何組織對象? 在本文中,我將介紹一種模式,該模式以無類型的方式在您的系統中組織所謂的名詞類,然后使用特征公開數據的類型化視圖。 這使得只需少量的犧牲就可以在Java之類的語言中獲得JavaScript之類的非類型語言的靈活性。
用戶在用戶界面中所做的每種配置,表單中的每種選擇都需要存儲在可從應用程序訪問的某個位置。 它需要以一種可以操作的格式存儲。 教科書中的示例是為系統中每個名詞定義類,并為它們所包含的字段使用getter和setter方法。 做教科書模型的一種更為嚴肅的方法是為每個名詞定義企業bean,并使用注釋對其進行處理。 它可能看起來像這樣:
這些靜態模型有局限性。 隨著系統的發展,您將需要添加更多字段,更改組件之間的關系,并可能出于不同目的創建其他實現。 你知道這個故事。 突然,每個名詞的靜態成分不再那么有趣了。 因此,您開始研究其他開發人員。 他們如何解決這個問題? 在JavaScript等非類型化語言中,您可以使用地圖來解決此問題。 有關組件的信息可以存儲為鍵值對。 如果一個子系統需要存儲一個附加字段,則可以這樣做,而無需事先定義該字段。
var myCar = {model: "Tesla", color: "Black"}; myCar.price = 80000; // A new field is defined on-the-fly它加快了發展速度,但同時付出了巨大的代價。 您會失去類型安全性! 每個真正的Java開發人員的噩夢。 由于您沒有使用組件的結構,因此測試和維護也更加困難。 在Speedment進行的最近一次重構中,我們面對靜態設計與動態設計的這些問題,并提出了一個稱為Abstract Document Pattern的解決方案。
抽象文件模式
此模型中的文檔類似于JavaScript中的Map。 它包含許多未指定值類型的鍵值對。 在這個未類型化的抽象文檔之上,是許多Traits ,它們表示一個類的特定屬性。 特征已使用類型化的方法來檢索它們表示的特定值。 名詞類只是原始文檔接口的抽象基礎實現之上的不同特征的并集。 因為一個類可以從多個接口繼承,所以可以這樣做。
實作
讓我們看一下這些組件的來源。
Document.java
public interface Document {Object put(String key, Object value);Object get(String key);<T> Stream<T> children(String key,Function<Map<String, Object>, T> constructor); }BaseDocument.java
public abstract class BaseDocument implements Document {private final Map<String, Object> entries;protected BaseDocument(Map<String, Object> entries) {this.entries = requireNonNull(entries);}@Overridepublic final Object put(String key, Object value) {return entries.put(key, value);}@Overridepublic final Object get(String key) {return entries.get(key);}@Overridepublic final <T> Stream<T> children(String key,Function<Map<String, Object>, T> constructor) {final List<Map<String, Object>> children = (List<Map<String, Object>>) get(key);return children == null? Stream.empty(): children.stream().map(constructor);} }HasPrice.java
public interface HasPrice extends Document {final String PRICE = "price";default OptionalInt getPrice() {// Use method get() inherited from Documentfinal Number num = (Number) get(PRICE); return num == null? OptionalInt.empty(): OptionalInt.of(num.intValue());} }在這里,我們只公開獲取價格的吸氣劑,但是您當然可以用相同的方法實現一個吸氣劑。 這些值始終可以通過put()方法進行修改,但是您面臨著將值設置為不同于getter期望的類型的風險。
汽車.java
public final class Car extends BaseDocumentimplements HasColor, HasModel, HasPrice {public Car(Map<String, Object> entries) {super(entries);}}如您所見,最終的名詞類很少,但是您仍然可以使用類型化的吸氣劑訪問顏色,型號和價格字段。 向組件添加新值就像將其放入地圖一樣容易,但是除非它是接口的一部分,否則它不會公開。 該模型還適用于分層組件。 讓我們看一下HasWheels特性的外觀。
HasWheels.java
public interface HasWheels extends Document {final String WHEELS = "wheels";Stream<Wheel> getWheels() {return children(WHEELS, Wheel::new);} }就是這么簡單! 我們利用以下事實:在Java 8中,您可以將對象的構造函數作為方法引用來引用。 在這種情況下,Wheel類的構造函數僅采用一個參數Map <String,Object>。 這意味著我們可以將其稱為Function <Map <String,Object>,Wheel>。
結論
這種模式既有優點,也有缺點。 隨著系統的擴展,文檔結構易于擴展和構建。 不同的子系統可以通過特征接口公開不同的數據。 根據使用哪個構造函數生成視圖,可以將同一地圖視為不同類型。 另一個優勢是,整個對象層次結構都存在于一個Map中,這意味著可以使用現有的庫(例如Google的gson工具 )輕松進行序列化和反序列化。 如果希望數據是不可變的,則只需將內部映射包裝在構造函數中的unmodifiableMap()中,即可保護整個層次結構。
一個缺點是它不如常規的bean結構安全。 可以通過多個接口從多個位置修改組件,這可能會使代碼的可測試性降低。 因此,在大規模實施此模式之前,應權衡利弊。
- 如果要查看實際的抽象文檔模式示例,請查看Speedment項目的源代碼,其中該項目管理有關用戶數據庫的所有元數據。
翻譯自: https://www.javacodegeeks.com/2016/02/the-best-of-both-worlds.html
總結
- 上一篇: c语言中strncat函数的用法,str
- 下一篇: 如何在JUnit 5中替换规则