Java 8函數語言程式設計模式:使用列舉的方法
假設有三種電影型別,每種型別都有自己的計算公式,該公式是根據借出的天數計算價格:
<b>class</b> Movie { enum Type { REGULAR, NEW_RELEASE, CHILDREN } <b>private</b> <b>final</b> Type type; <b>public</b> Movie(Type type) { <b>this</b>.type = type; } <b>public</b> <b>int</b> computePrice(<b>int</b> days) { <b>switch</b> (type) { <b>case</b> REGULAR: <b>return</b> days + 1; <b>case</b> NEW_RELEASE: <b>return</b> days * 2; <b>case</b> CHILDREN: <b>return</b> 5; <b>default</b>: <b>throw</b> <b>new</b> IllegalArgumentException(); <font><i>// Always have this here!</i></font><font> } } } </font>
測試:
System.out.println(<b>new</b> Movie(Movie.Type.REGULAR).computePrice(2)); System.out.println(<b>new</b> Movie(Movie.Type.NEW_RELEASE).computePrice(2)); System.out.println(<b>new</b> Movie(Movie.Type.CHILDREN).computePrice(2));
結果:
3
4
5
上面程式碼中的問題可能是switch:無論何時向列舉新增新值,都需要搜尋所有開關並確保處理新情況。
但這很脆弱。會彈出IllegalArgumentException,簡而言之,雖然任何人都可以閱讀此程式碼,但它有點冒險。
避免風險的一種方法是OOP解決方案:
<b>abstract</b> <b>class</b> Movie { <b>public</b> <b>abstract</b> <b>int</b> computePrice(<b>int</b> days); } <b>class</b> RegularMovie <b>extends</b> Movie { <b>public</b> <b>int</b> computePrice(<b>int</b> days) { <b>return</b> days+1; } } <b>class</b> NewReleaseMovie <b>extends</b> Movie { <b>public</b> <b>int</b> computePrice(<b>int</b> days) { <b>return</b> days*2; } } <b>class</b> ChildrenMovie <b>extends</b> Movie { <b>public</b> <b>int</b> computePrice(<b>int</b> days) { <b>return</b> 5; } }
如果您建立一種新型別的影片,實際上是一個新的子類,但是繼承問題來了,如果你想按照另一個標準對電影進行分類,比如發行年份怎麼辦?或者幾個月後你會如何處理從電影Type.NEW_RELEASE到Type.REGULAR電影的“降級” ?
讓我們尋找其他方法來實現它,使用列舉抽象方法實現此邏輯。
這裡如果直接提出更改需求:“ 公式中的因子:新發行電影的價格(在我們的示例中為2)必須通過資料庫更新。”
這意味著我必須從一些注入的儲存庫中獲取此因子。但是,由於我不能在我的Movie實體中注入repos ,讓我們將邏輯移到一個單獨的類中:
<b>public</b> <b>class</b> PriceService { <b>private</b> <b>final</b> NewReleasePriceRepo repo; <b>public</b> PriceService(NewReleasePriceRepo repo) { <b>this</b>.repo = repo; } <b>public</b> <b>int</b> computeNewReleasePrice(<b>int</b> days) { <b>return</b> (<b>int</b>) (days * repo.getFactor()); } <b>public</b> <b>int</b> computeRegularPrice(<b>int</b> days) { <b>return</b> days + 1; } <b>public</b> <b>int</b> computeChildrenPrice(<b>int</b> days) { <b>return</b> 5; } <b>public</b> <b>int</b> computePrice(Movie.Type type, <b>int</b> days) { <b>switch</b> (type) { <b>case</b> REGULAR: <b>return</b> computeRegularPrice(days); <b>case</b> NEW_RELEASE: <b>return</b> computeNewReleasePrice(days); <b>case</b> CHILDREN: <b>return</b> computeChildrenPrice(days); <b>default</b>: thrownew IllegalArgumentException(); } } }
switch又回來聊,帶來了固有的風險
<b>public</b> <b>class</b> Movie { <b>public</b> enum Type { REGULAR(PriceService::computeRegularPrice), NEW_RELEASE(PriceService::computeNewReleasePrice), CHILDREN(PriceService::computeChildrenPrice); <b>public</b> <b>final</b> BiFunction<PriceService, Integer, Integer> priceAlgo; <b>private</b> Type(BiFunction<PriceService, Integer, Integer> priceAlgo) { <b>this</b>.priceAlgo = priceAlgo; } } ... }
我在每個列舉值中儲存一個方法引用到相應的例項方法PriceService.
這樣不需要switch:
<b>class</b> PriceService { ... <b>public</b> <b>int</b> computePrice(Movie.Type type, <b>int</b> days) { <b>return</b> type.priceAlgo.apply(<b>this</b>, days); } }
於我以靜態方式(from PriceService::)引用例項方法,因此我需要PriceService在呼叫時提供例項作為第一個引數,這裡使用了this,這樣,我可以從列舉值定義的靜態上下文中有效地引用任何[Spring] bean的方法。
您可以使用方法引用將型別特定的邏輯掛鉤到列舉,以確保每個列舉值與相應的邏輯位相關聯。