使用lambda實現裝飾者模式 - Voxxed

banq發表於2019-01-27

Decorator模式允許透過使用多個巢狀層包裝它來動態擴充套件現有物件的功能。所有這些層必須實現相同的介面,這樣才能組合它們。
讓我們用一個實際的例子證明這一點:我們需要從年薪總額開始計算工資計算器,並在將其除以12並對其徵收一系列稅後計算每月淨工資。為方便起見,有關這些稅收的業務邏輯已經分組在一組靜態方法中,每個方法都實現了給定稅的應用。

public class Taxes {
    public static double generalTax(double salary) {
        return salary * 0.8;
    }
     
    public static double regionalTax(double salary) {
        return salary * 0.95;
    }
     
    public static double healthInsurance(double salary) {
        return salary - 200.0;
    }
}


實施必須足夠靈活,才能允許從薪資計算演算法中動態新增和刪除稅。這意味著每個層執行的特定計算可以建模為double的簡單轉換為另一個double。我們計算的每個階段都必須實現以下介面:

interface SalaryCalculator {
    double calculate(double grossAnnual);
}

 
正如預期的那樣,工資計算過程的第一階段是按12除,從年度工資開始計算月工資。
 
 

public class DefaultSalaryCalculator implements SalaryCalculator {
    @Override
    public double calculate(double grossAnnual) {
        return grossAnnual / 12;
    }
}

現在有必要建立一種機制,將第一個SalaryCalculator實施與將應用上述稅收的其他實施相結合。為此,我們可以使用Decorator模式,將其本質封裝到抽象類中。

public abstract class AbstractTaxDecorator implements SalaryCalculator {
    private final SalaryCalculator salaryCalculator;
 
    public AbstractTaxDecorator( SalaryCalculator salaryCalculator ) {
        this.salaryCalculator = salaryCalculator;
    }
 
    protected abstract double applyTax(double salary);
 
    @Override
    public final double calculate(double gross) {
        double salary = salaryCalculator.calculate( gross );
        return applyTax( salary );
    }
}


這個類裝飾(包裝)一個SalaryCalculator,但它本身也是一個SalaryCalculator。它有一個抽象方法,該類的每個具體實現都必須提供其特定的業務邏輯。透過這種方式,這個抽象類可以直接實現calculate()方法,方法是將總工資傳遞給它包裝的SalaryCalculator,然後將自己的計算函式應用於結果。在開發了這種抽象之後,現在可以為我們可能希望最終應用於總薪水的每種稅收使用前抽象類的不同實現。特別是,我們將實施適用一般稅收的實施:

public class GeneralTaxDecorator extends AbstractTaxDecorator {
    public GeneralTaxDecorator( SalaryCalculator salaryCalculator ) {
        super( salaryCalculator );
    }
 
    @Override
    protected double applyTax(double salary) {
        return Taxes.generalTax( salary );
    }
}


另一個是區域性的:

public class RegionalTaxDecorator extends AbstractTaxDecorator {
    public RegionalTaxDecorator( SalaryCalculator salaryCalculator ) {
        super( salaryCalculator );
    }
 
    @Override
    protected double applyTax(double salary) {
        return Taxes.regionalTax( salary );
    }
}

第三個涵蓋健康保險:

public class HealthInsuranceDecorator extends AbstractTaxDecorator {
    public HealthInsuranceDecorator( SalaryCalculator salaryCalculator ) {
        super( salaryCalculator );
    }
 
    @Override
    protected double applyTax(double salary) {
        return Taxes.healthInsurance( salary );
    }
}


最後,我們現在準備用以前實現的所有或部分裝飾器組合第一個DefaultSalaryCalculator,並將生成的年薪轉移到生成的SalarayCalculator。

double netSalary = new HealthInsuranceDecorator(
    new RegionalTaxDecorator(
        new GeneralTaxDecorator(
            new DefaultSalaryCalculator()
        )
    )
).calculate( 30000.00 ));


函式式實現
原始的DefaultSalaryCalculator只是一個將double轉換為另一個double的函式。為了避免任何不必要的原始double的裝箱和拆箱,我們可以使DefaultSalaryCalculator實現DoubleUnaryOperator而不是普通的Function。

public class DefaultSalaryCalculator implements DoubleUnaryOperator {
 
    @Override
    public double applyAsDouble(double grossAnnual) {
        return grossAnnual / 12;
    }
}


此外,分組到Taxes類中的所有其他靜態方法都具有相同的簽名,然後可以將其視為DoubleUnaryOperator介面的其他實現。現在是時候想知道Decorator模式的本質是什麼了。實際上它提供了一種組合不同計算的方法,但函式組合當然是函數語言程式設計中更自然的東西。令人驚訝的是,我們可以獲得完全相同的結果,這使我們花費了大量精力使用Decorator模式,如下所示:

double netSalary = new DefaultSalaryCalculator()
        .andThen( Taxes::generalTax )
        .andThen( Taxes::regionalTax )
        .andThen( Taxes::healthInsurance )
        .applyAsDouble( 30000.00 );


請注意,此習慣用法允許按照與基於Decorator的實現相同的方式按需新增和刪除功能。而且,這次計算以與寫入函式相同的順序進行。還可以為我們的使用者提供更好的API,以實現接受總薪水的方法以及要應用於其的函式的變數。

public static double calculate(double gross, DoubleUnaryOperator... fs) {
    return Stream.of( fs )
                 .reduce( DoubleUnaryOperator.identity(),
                          DoubleUnaryOperator::andThen )
                 .applyAsDouble( gross );
}

這裡透過將varargs陣列中的所有函式放入Stream並將函式Stream簡化為單個函式來實現函式組合。現在可以透過這種方式呼叫這個新的靜態方法來計算淨工資。

double netSalary = calculate( 30000.00,
                                  new DefaultSalaryCalculator(),
                                  Taxes::generalTax,
                                  Taxes::regionalTax,
                                  Taxes::healthInsurance );





 

相關文章