使用lambda实现装饰者模式 - Voxxed


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 );