使用lambda實現裝飾者模式 - Voxxed
Decorator模式允許通過使用多個巢狀層包裝它來動態擴充套件現有物件的功能。所有這些層必須實現相同的介面,這樣才能組合它們。
讓我們用一個實際的例子證明這一點:我們需要從年薪總額開始計算工資計算器,並在將其除以12並對其徵收一系列稅後計算每月淨工資。為方便起見,有關這些稅收的業務邏輯已經分組在一組靜態方法中,每個方法都實現了給定稅的應用。
<b>public</b> <b>class</b> Taxes { <b>public</b> <b>static</b> <b>double</b> generalTax(<b>double</b> salary) { <b>return</b> salary * 0.8; } <b>public</b> <b>static</b> <b>double</b> regionalTax(<b>double</b> salary) { <b>return</b> salary * 0.95; } <b>public</b> <b>static</b> <b>double</b> healthInsurance(<b>double</b> salary) { <b>return</b> salary - 200.0; } }
實施必須足夠靈活,才能允許從薪資計算演算法中動態新增和刪除稅。這意味著每個層執行的特定計算可以建模為double的簡單轉換為另一個double。我們計算的每個階段都必須實現以下介面:
<b>interface</b> SalaryCalculator { <b>double</b> calculate(<b>double</b> grossAnnual); }
正如預期的那樣,工資計算過程的第一階段是按12除,從年度工資開始計算月工資。
<b>public</b> <b>class</b> DefaultSalaryCalculator implements SalaryCalculator { @Override <b>public</b> <b>double</b> calculate(<b>double</b> grossAnnual) { <b>return</b> grossAnnual / 12; } }
現在有必要建立一種機制,將第一個SalaryCalculator實施與將應用上述稅收的其他實施相結合。為此,我們可以使用Decorator模式,將其本質封裝到抽象類中。
<b>public</b> <b>abstract</b> <b>class</b> AbstractTaxDecorator implements SalaryCalculator { <b>private</b> <b>final</b> SalaryCalculator salaryCalculator; <b>public</b> AbstractTaxDecorator( SalaryCalculator salaryCalculator ) { <b>this</b>.salaryCalculator = salaryCalculator; } <b>protected</b> <b>abstract</b> <b>double</b> applyTax(<b>double</b> salary); @Override <b>public</b> <b>final</b> <b>double</b> calculate(<b>double</b> gross) { <b>double</b> salary = salaryCalculator.calculate( gross ); <b>return</b> applyTax( salary ); } }
這個類裝飾(包裝)一個SalaryCalculator,但它本身也是一個SalaryCalculator。它有一個抽象方法,該類的每個具體實現都必須提供其特定的業務邏輯。通過這種方式,這個抽象類可以直接實現calculate()方法,方法是將總工資傳遞給它包裝的SalaryCalculator,然後將自己的計算函式應用於結果。在開發了這種抽象之後,現在可以為我們可能希望最終應用於總薪水的每種稅收使用前抽象類的不同實現。特別是,我們將實施適用一般稅收的實施:
<b>public</b> <b>class</b> GeneralTaxDecorator <b>extends</b> AbstractTaxDecorator { <b>public</b> GeneralTaxDecorator( SalaryCalculator salaryCalculator ) { <b>super</b>( salaryCalculator ); } @Override <b>protected</b> <b>double</b> applyTax(<b>double</b> salary) { <b>return</b> Taxes.generalTax( salary ); } }
另一個是區域性的:
<b>public</b> <b>class</b> RegionalTaxDecorator <b>extends</b> AbstractTaxDecorator { <b>public</b> RegionalTaxDecorator( SalaryCalculator salaryCalculator ) { <b>super</b>( salaryCalculator ); } @Override <b>protected</b> <b>double</b> applyTax(<b>double</b> salary) { <b>return</b> Taxes.regionalTax( salary ); } }
第三個涵蓋健康保險:
<b>public</b> <b>class</b> HealthInsuranceDecorator <b>extends</b> AbstractTaxDecorator { <b>public</b> HealthInsuranceDecorator( SalaryCalculator salaryCalculator ) { <b>super</b>( salaryCalculator ); } @Override <b>protected</b> <b>double</b> applyTax(<b>double</b> salary) { <b>return</b> Taxes.healthInsurance( salary ); } }
最後,我們現在準備用以前實現的所有或部分裝飾器組合第一個DefaultSalaryCalculator,並將生成的年薪轉移到生成的SalarayCalculator。
<b>double</b> netSalary = <b>new</b> HealthInsuranceDecorator( <b>new</b> RegionalTaxDecorator( <b>new</b> GeneralTaxDecorator( <b>new</b> DefaultSalaryCalculator() ) ) ).calculate( 30000.00 ));
函式式實現
原始的DefaultSalaryCalculator只是一個將double轉換為另一個double的函式。為了避免任何不必要的原始double的裝箱和拆箱,我們可以使DefaultSalaryCalculator實現DoubleUnaryOperator而不是普通的Function。
<b>public</b> <b>class</b> DefaultSalaryCalculator implements DoubleUnaryOperator { @Override <b>public</b> <b>double</b> applyAsDouble(<b>double</b> grossAnnual) { <b>return</b> grossAnnual / 12; } }
此外,分組到Taxes類中的所有其他靜態方法都具有相同的簽名,然後可以將其視為DoubleUnaryOperator介面的其他實現。現在是時候想知道Decorator模式的本質是什麼了。實際上它提供了一種組合不同計算的方法,但函式組合當然是函數語言程式設計中更自然的東西。令人驚訝的是,我們可以獲得完全相同的結果,這使我們花費了大量精力使用Decorator模式,如下所示:
<b>double</b> netSalary = <b>new</b> DefaultSalaryCalculator() .andThen( Taxes::generalTax ) .andThen( Taxes::regionalTax ) .andThen( Taxes::healthInsurance ) .applyAsDouble( 30000.00 );
請注意,此習慣用法允許按照與基於Decorator的實現相同的方式按需新增和刪除功能。而且,這次計算以與寫入函式相同的順序進行。還可以為我們的使用者提供更好的API,以實現接受總薪水的方法以及要應用於其的函式的變數。
<b>public</b> <b>static</b> <b>double</b> calculate(<b>double</b> gross, DoubleUnaryOperator... fs) { <b>return</b> Stream.of( fs ) .reduce( DoubleUnaryOperator.identity(), DoubleUnaryOperator::andThen ) .applyAsDouble( gross ); }
這裡通過將varargs陣列中的所有函式放入Stream並將函式Stream簡化為單個函式來實現函式組合。現在可以通過這種方式呼叫這個新的靜態方法來計算淨工資。
<b>double</b> netSalary = calculate( 30000.00, <b>new</b> DefaultSalaryCalculator(), Taxes::generalTax, Taxes::regionalTax, Taxes::healthInsurance );