变压器图案
Transformer模式是Java(以及可能僅具有使用場(chǎng)所差異和不變參數(shù)類(lèi)型的其他OO語(yǔ)言)的設(shè)計(jì)模式,可幫助子類(lèi)型層次結(jié)構(gòu)內(nèi)的對(duì)象將自己流暢地轉(zhuǎn)換為任何類(lèi)型的對(duì)象。
語(yǔ)境
我一直在關(guān)注與Jim Laskey發(fā)行的JDK-8203703相關(guān)的OpenJDK線(xiàn)程( 9月18-21日 , 11月12-13日, 11月13-30日, 12月3-4日 ),然后我想到了一個(gè)主意。 讓我回顧一下討論的相關(guān)部分。
String.transform的建議
根據(jù)JDK-8203703的提案可歸結(jié)為以下新增內(nèi)容:
public final class String implements /*...*/ CharSequence {// ...public <R> R transform(Function<? super String, ? extends R> f) {return f.apply(this);}// ... }如您所見(jiàn),此方法僅自身調(diào)用給定的Function即可。 但是,它對(duì)于鏈接實(shí)用程序方法非常有用,例如Apache Commons的 StringUtils中的方法:
String result = string.toLowerCase().transform(StringUtils::stripAccents).transform(StringUtils::capitalize);通常,我們必須寫(xiě):
String result = StringUtils.capitalize(StringUtils.stripAccents(string.toLowerCase()));考慮CharSequence.transform
在某個(gè)時(shí)候,艾倫·貝特曼(Alan Bateman) 提出了在CharSequence中潛在定義transform 的問(wèn)題 :
<R> R transform(Function<? super CharSequence, ? extends R> f)這將具有能夠在任何CharSequence上應(yīng)用基于CharSequence的實(shí)用程序方法(例如StringUtils.isNumeric )的好處,例如:
boolean isNumeric = charSequence.transform(s -> StringUtils.defaultIfBlank('0')).transform(StringUtils::isNumeric);但是,正如RémiForax所指出的 ,此簽名的問(wèn)題在于:
- 如果它是由String 繼承的:大多數(shù)實(shí)用程序方法都將String作為參數(shù)–這樣的方法將不起作用(例如StringUtils :: capitalize ),
- 如果它被String 覆蓋 :由于以下原因,無(wú)法進(jìn)行有用的覆蓋:
- Function<? super String, R>
結(jié)果, CharSequence.transform的主題已被刪除。
問(wèn)題
總而言之,問(wèn)題在于能夠進(jìn)行轉(zhuǎn)換 :
- 一個(gè)CharSequence ,使用Function即需要CharSequence或Object ( ? super CharSequence ),
- 一個(gè)String ,使用接受String或其任何父類(lèi)型( ? super String )的Function 。
當(dāng)我在這里查看這些下 限時(shí) ,我意識(shí)到我已經(jīng)看到了這種問(wèn)題(參見(jiàn)Filterer Pattern )。
因此,這個(gè)問(wèn)題歸結(jié)為:如何協(xié)變地指定Function的反 變界。
解
Java不支持逆變參數(shù)類(lèi)型 ,并且它的語(yǔ)法也沒(méi)有提供一種方法來(lái)協(xié)變( ? extends )指定在單個(gè)聲明中綁定的逆變( ? super )。 然而,有可能做到這一點(diǎn)在兩個(gè)分開(kāi)的宣言,通過(guò)中間輔助類(lèi)型的裝置。
假設(shè)我們要為泛型Function<? super T, ? extends R> Function<? super T, ? extends R> Function<? super T, ? extends R> ,我們需要:
- 將上面的Function參數(shù)移動(dòng)到參數(shù)為T(mén)的輔助接口中 ,
- 將此輔助接口與上限 ( ? extends T )一起用作返回類(lèi)型。
變壓器接口
我定義了這樣的幫助程序接口(我稱(chēng)之為T(mén)ransformer ),如下所示:
@FunctionalInterface interface Transformer<T> {<R> R by(Function<? super T, ? extends R> f); }可轉(zhuǎn)換的接口
定義了Transformer ,我們可以定義以下稱(chēng)為T(mén)ransformable基本接口:
interface Transformable {Transformer<?> transformed(); }該接口本身并不能做很多事情,但我將其視為以下方面的規(guī)范 :
- 子類(lèi)型實(shí)現(xiàn)者 :它提醒他們使用適當(dāng)?shù)纳舷薷采w已transformed方法,并加以實(shí)現(xiàn),
- 子類(lèi)型用戶(hù) :提醒他們可以調(diào)用transformed().by(f) 。
總結(jié)起來(lái),這對(duì)( Transformer & Transformable )讓我們替換:
- obj.transform(function)
- 使用: obj.transformed().by(function)
樣例實(shí)施
回到String之前,讓我們看看實(shí)現(xiàn)這兩個(gè)接口有多么容易:
class Sample implements Transformable {@Overridepublic Transformer<Sample> transformed() {return this::transform; // method reference}private <R> R transform(Function<? super Sample, ? extends R> f) {return f.apply(this);} }如您所見(jiàn),所需要的只是對(duì)transform的方法引用 。
transform方法被設(shè)為私有,因此當(dāng)子類(lèi)型定義自己的(適當(dāng)?shù)?#xff0c; 下界 ) transform時(shí),它們之間不會(huì)發(fā)生沖突。
上下文中的解決方案
上下文中的實(shí)現(xiàn)
它如何應(yīng)用于CharSequence和String ? 首先,我們將CharSequence擴(kuò)展為T(mén)ransformable :
public interface CharSequence extends Transformable {// ...@OverrideTransformer<? extends CharSequence> transformed();// ... }然后,我們transformed在String實(shí)現(xiàn)transformed ,返回對(duì)public transform方法的方法引用(已在JDK 12中添加 ):
public final class String implements /*...*/ CharSequence {// ...@Overridepublic Transformer<String> transformed() {return this::transform;}// ... }請(qǐng)注意,我們對(duì)transformed的返回類(lèi)型進(jìn)行了協(xié)變更改: Transformer<? extends CharSequence> Transformer<? extends CharSequence> → Transformer<String> 。
相容性風(fēng)險(xiǎn)
我認(rèn)為添加CharSequence.transformed的兼容性風(fēng)險(xiǎn)很小。 僅對(duì)于那些已經(jīng)具有無(wú)參數(shù)transformed方法的CharSequence子類(lèi),它可能會(huì)破壞向后兼容性(這似乎不太可能)。
上下文中的用法
對(duì)于使用String不會(huì)改變,因?yàn)橛泻粲鯖](méi)有一點(diǎn)transformed().by()在transform()
但是,通用CharSequence的用法將需要訴諸transformed().by()因?yàn)樗赡苡泻芏鄬?shí)現(xiàn),因此transform方法必須是private :
boolean isNumeric = charSequence.transformed().by(s -> StringUtils.defaultIfBlank('0')).transformed().by(StringUtils::isNumeric);性能
如果您不熟悉JVM (最常表示HotSpot )及其JIT編譯器的工作方式,那么您可能想知道這種明顯的額外對(duì)象創(chuàng)建( Transformer in transformed )是否不會(huì)影響性能。
幸運(yùn)的是,由于有了轉(zhuǎn)義分析 *和標(biāo)量替換 ,該對(duì)象從未在堆上分配。 答案是:不會(huì),不會(huì)。
* 此Wikipedia條目包含錯(cuò)誤的陳述:“ 因此,編譯器可以安全地在堆棧上分配這兩個(gè)對(duì)象。 ”正如 AlekseyShipilёv解釋的那樣 ,Java不會(huì)在堆棧上分配整個(gè)對(duì)象。
基準(zhǔn)測(cè)試
如果您需要證明,這里有一些基準(zhǔn)(使用AlekseyShipilёv出色的JMH基準(zhǔn)線(xiàn)束 )。 因?yàn)槲也荒?#xff08;容易),添加必要的方法,以String ,我創(chuàng)建了一個(gè)簡(jiǎn)單的包裝過(guò)String ,并實(shí)現(xiàn)了在它之上的標(biāo)桿。
基準(zhǔn)測(cè)試toLowerCase()操作:
- 在兩個(gè)字符串上:
- "no change" (無(wú)操作)
- "Some Change"
- 使用三種通話(huà)類(lèi)型:
- 直接(基準(zhǔn))
- transform()
- transformed().by()
您可以在GitHub gist中找到此基準(zhǔn)測(cè)試的完整源代碼。
結(jié)果如下(在Oracle JDK 8上運(yùn)行,花費(fèi)了50分鐘):
Benchmark (string) Mode Cnt Score Error UnitsTransformerBenchmark.baseline no change avgt 25 22,215 ± 0,054 ns/op TransformerBenchmark.transform no change avgt 25 22,540 ± 0,039 ns/op TransformerBenchmark.transformed no change avgt 25 22,565 ± 0,059 ns/opTransformerBenchmark.baseline Some Change avgt 25 63,122 ± 0,541 ns/op TransformerBenchmark.transform Some Change avgt 25 63,405 ± 0,196 ns/op TransformerBenchmark.transformed Some Change avgt 25 62,930 ± 0,209 ns/op如您所見(jiàn),對(duì)于這兩個(gè)字符串,這三種調(diào)用類(lèi)型之間沒(méi)有性能差異。
摘要
我意識(shí)到, Transformable可能太“奢侈”了,無(wú)法真正將其納入JDK。 實(shí)際上,即使僅由CharSequence和String返回的Transformer也不值得。 這是因?yàn)閷?duì)CharSequence的一元運(yùn)算似乎并不常見(jiàn)(例如StringUtils僅包含少數(shù)幾個(gè))。
但是,我發(fā)現(xiàn)“ Transformer和“ Transformable的基本概念很誘人。 因此,我希望您喜歡閱讀,并在某些情況下會(huì)發(fā)現(xiàn)它很有用
翻譯自: https://www.javacodegeeks.com/2019/02/transformer-pattern.html
總結(jié)
- 上一篇: WTF连接池
- 下一篇: lucene 多个分词查找_使用Luce