13

我面临一个编程问题,我不知道如何以面向对象灵活的方式解决。我想到了一些不好的解决方案,但我正在寻找一个好的解决方案。我用 Java 开发,所以我更喜欢 Java 的想法,但是任何面向对象的想法都是受欢迎的。

我一直在寻找可以帮助我的想法、设计模式或一些算法,但我不知道哪个术语或名称可以解决我的问题,所以我找不到任何线索。

问题:

概括:

我需要跟踪对实体集合进行不同更改的过程的部分结果。我需要这个来向用户报告表格报告中每个计算“步骤”的详细信息。而且我还需要将此集合保存在数据库中。

细节:

我维护的软件有一个类似于这个的实体:

public class Salary {
    private Date date;
    private BigDecimal amount;
}

这被分组在一个集合中,如下所示:

List<Salary> thisYearSalaries;

这组实体可以根据一些规则由一组“任务”修改:

  • 应用某种税(来自一组不同的税)
  • 计算金额的未来价值(更多信息
  • 折扣金额以保持低于最大值
  • ETC...

例如:

public void processSalaries(List<Salary> theSalaries) {
    applyTax(theSalaries, Taxes.TAX_TYPE_1);
    (...)
    getFutureValue(theSalaries, someFutureDate);
    (...)
    restrictToAMaximum(theSalaries, Maximum.MARRIED_MAXIMUM);
    (...)
    applyTax(theSalaries, TAXES.TAX_TYPE_3);
    (...)
}

public void applyTax(List<Salary> theSalaries, Tax taxToApply) {
    for(Salary aSalary : theSalaries) {
        aSalary.setAmount(calculateAmountWithTax(aSalary.getAmount(), taxToApply);
    }
}

(...)

我需要处理这个工资集合,更改金额,但保留金额的所有中间“状态”,在包含如下列的表中向用户显示:

示例报告:( 问题只针对前4行数据,其余不关注)

报告示例

我的想法:

为类 Salary 上的每个“部分结果”添加一个属性

public class Salary {
    (...)
    private BigDecimal originalAmount;
    private BigDecimal amountAfterFirstTax;
    private BigDecimal amountAfterMaximumRestriction;
    (...)
}

问题:

  • “步”并不死板,也许明天一个“步”变了,又出现了一个新的“步”,或者某个步的“意义”发生了变化。在这种情况下,我将需要过多地重构代码。
  • 一些“步骤”可以重复,那么,我如何告诉“方法”,哪个属性必须“设置”计算结果?

向 Salary 类添加一个 HashMap,我可以在其中放置部分结果,并将“key”传递给“step”方法,它必须在其中放置部分结果

public class Salary {
    (...)
    HashMap<String, BigDecimal> partialResults;
    (...)
}

问题:

  • 在某些地方,我需要将 HashMap 填充到 JPA 实体以将其保存在我的数据库中
  • 如果另一个开发人员更改了密钥的名称(无论出于何种原因),属性的“填充”可能会被破坏

最后说明:我的应用程序中还有其他“类似”实体的类似情况,所以如果我们能找到一个通用的解决方案,那就太好了:D


编辑:关于如何对数据进行建模以持久化的新疑问

所有的想法都是相似的并且非常有用。它们都与命令模式有关,我认为这是很好的解决方案。但现在我有一些新的疑问:

通过更高级别的抽象,我的应用程序执行以下操作:

  1. 用户输入工资列表
  2. 该应用程序通过不同的“步骤”处理这些薪水
  3. 使用 LAST 工资金额计算平均值并继续进行其他计算
  4. 经过一些屏幕和一些处理后,我向用户显示了一份包含最终工资金额和所有中间步骤的报告。然后我保存这些中间信息用于审计目的

所以,第二步就基本解决了。我会使用类似命令模式的东西。我现在的问题是如何对数据进行建模以将其保存在关系数据库中。因为,除了每个操作的部分“结果”之外,还有更多的信息我不需要显示给用户,但我需要将其存储在数据库中。

我的想法是这样的:

薪资表:

id
date // The date of the Salary
finalAmount // The final amount after all the calculations ends

部分结果表:

id
salaryId // The id of the salary which this element represent a partial result
amount // Partial amount
operationCode // Type of operation
operationDescription 

但问题是,我的“未来价值”操作作为输出具有以下信息:

  • 终值日期
  • 用于更新值的系数
  • 部分金额

但是“申请税”操作有不同的信息:

  • % 应用税
  • 部分金额

那么,如何为不同的操作保存不同的“输出”信息呢?

4

4 回答 4

4

我会将您执行的“操作”编码为对象。您拥有数据,当您对其应用操作时,该操作就是一个对象。操作对象包含它自己的一些信息(可能是起始值和结束值),并且它知道如何格式化该值以进行输出。

当您开始时,您会列出这些操作的队列——每个操作都略有不同(可能是不同的类,可能只是在创建它们时具有内部状态差异)。您将此队列与您的数据结合起来,并将数据从队列中的一个片段传递到下一个片段,或者您可以将其视为遍历队列并将每个操作应用于数据(您可能传入)。

完成后,每个操作对象都会记住他们所做的历史并知道如何对其进行格式化(例如,“扣除 %s 的税款”),并且您的数据已更改为正确的值。

这也可以为您提供“展开”,您可以在其中以相反的顺序应用模式以备份到先前的值(您可能对这里没有兴趣,但在某些情况下可能会很棒)。

我相信这被称为“命令”模式。在任何情况下都非常有用。

另一件好事 - 您放入队列的操作组可以使用数据或任意数量的源进行选择,从而允许您在许多情况下调整计算而无需更改任何代码。

这些类将非常简单,例如:

公开课 TaxOperation
{
    私人双倍费率;
    私人双税;

    公共税收运营(双倍税率)
    {
        this.rate=rate;
    }

    @覆盖
    公共双getValue(双初始)
    {
        税额 = 初始 * 税率;
        返回初始-amountTaxed;
    }

    @覆盖
    公共字符串 getDescription()
    {
        return "应用了 "+rate+" 的税款,扣除了 "+amountTaxed"
    }
}

这看起来很简单,确实如此,但有时简单的类做得最多。显然 getValue 操作不是您想要的,但您应该能够看到如何使用该模式得出您想要的。

另请注意,“操作”可以做任何事情——例如,它可以非常适合线程(想象您的操作需要几秒钟或几分钟,您只需将它们的队列提交给处理器并让它们离开)。

于 2013-09-14T02:06:17.873 回答
1

这样的事情怎么样。

public abstract SalaryComputation implements Serializable {

      private Salary salary;

      public SalaryComputation(Salary salary}
      {
         this.salary = salary;
      }

      public Salary getSalary(){
         return salary
      }

      // get the name of the computation to print 
      public abstract String getComputationName(); 

      // get the result of the computation 
      public abstract BigDecmial getAmount();
}

然后你可以有这个接口的不同实现

public SalaryTaxComputation extends SalaryComputation
{
    public String getComputationName() { 
        return "Tax 10%";
    }

    public BigDecimal getAmount()
    {
         // put the computation code here
    }
}

然后,您可以使用List<SalaryComputation>存储您对薪水所做的所有计算的列表,并List<List<SalaryComputaino>>存储报告。

要保存计划,您可以使 SalaryComputation 实现序列化,然后所有子类都将是可序列化的。这应该允许您将结果保存为数据库或磁盘文件的二进制数组。主要问题是在添加和删除字段时管理版本。您还可以发明自己的文件格式来存储这些对象,可能是 JSON 对象,并使用 Jackson 之类的东西来序列化/反序列化它们,或者如果您想存储到 XML 则使用 JAXB ......等等。

于 2013-09-14T01:42:06.913 回答
1

您可以创建一个名为SalaryReport的类,该类由SalaryReportRow的许多对象组成。

SalaryReport 中,您有一个方法名称calculate () ,它执行所有算法。SalaryReportRow可以专门用于满足您需求的多种类型的行。

class SalaryReport {

    private List<SalaryReportRow> rows;
    private List<SalaryCalculator> calculators;

    private HashMap<SalaryCalculator,List<SalaryReportRow>> results;

    ...

    public float getResults() {

    }

    public void applyCalculators(){
        for(SalaryCalculator s : calculators){
            results.put(s,s.execute(rows));
        }
    }

    public void setSalaryCalculators(List<SalaryCalculator> calculators){
        this.calculators = calculators;
    }

    public float getCalculation(SalaryCalculator s){
            return results.get(s);
    }
    ...

}

在计算税收时,您可以使用策略模式来使您的代码更加灵活。如果您必须结合许多策略,您还可以使用复合模式与策略或责任链与策略。

abstract class SalaryCalculator implements Calculator {

        //Order of children matters
    protected List<SalaryCalculator> children = new ArrayList<SalaryCalculator>;

    public List<SalaryReportRow> execute(List<SalaryReportRow> rows){

        List<SalaryReportRow> result_rows = new ArrayList<SalaryReportRow> (rows);

        for(SalaryCalculator s: children){
            result_rows = s.execute(result_rows);
        }

        return result_rows;
    }

}

class TaxCalculator extends SalaryCalculator {

    public List<SalaryReportRow> execute(List<SalaryReportRow> rows){
        List<SalaryReportRow> result_rows = super.execute(rows);

        //
    }

}


class MaxSalaryCalculator extends SalaryCalculator {

    public List<SalaryReportRow> execute(List<SalaryReportRow> rows){
        List<SalaryReportRow> result_rows = super.execute(rows);

        ...
    }

}

class MaxSalaryWithTaxCalculator extends SalaryCalculator {

    public MaxSalaryWithTaxCalculator() {
        children.add(new MaxSalaryCalculator());
        children.add(new TaxCalculator());
    }

    public List<SalaryReportRow> execute(List<SalaryReportRow> rows){
        List<SalaryReportRow> result_rows = super.execute(rows);

        ...
    }
}

所以SalaryReport将有一个名为setSalaryCalculators(List)的方法(您也可以一次使用多个算法),其中 List 是一个包含所有应用策略的列表。

您将拥有更多SalaryReportRows

  1. 一个用于税收。
  2. 一是赚的钱。
  3. ...

因此,您可以轻松计算所有步骤。

当您与存储处理程序通信时,我建议使用带有接口的DAO 模式,该接口负责创建和保存对象。

我希望这会有所帮助。:)

于 2013-09-14T00:54:17.607 回答
0

不禁认为这对你有用,jbatch

于 2013-09-14T22:31:13.713 回答