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