设计模式:跟踪复杂过程的部分结果

13

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

我一直在寻找想法、设计模式或一些算法来帮助我,但我不知道该给我的问题起什么术语或名称,因此我找不到任何线索。

问题:

概述:

我需要跟踪对实体集合进行不同更改的过程的部分结果。我需要将这些结果报告给用户,在表格报告中详细说明每个计算“步骤”。同时,我还需要将这个集合持久化到数据库中。

细节:

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

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

那被归为一个集合,就像这样:

List<Salary> thisYearSalaries;

根据一些规则,这组实体可以通过一组“任务”进行修改:

  • 应用一定的税收(来自不同税种的集合)
  • 计算资金金额的未来价值(更多信息
  • 减少一定金额以保持在最大限额以下
  • 等等...

例如:

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;
    (...)
}

问题:

  • “步骤”不是固定的,也许明天会有一个“步骤”改变,出现一个新的“步骤”,或者某些步骤的“意义”改变。在这种情况下,我将需要重构太多的代码。
  • 有些“步骤”可以重复,那么我怎么告诉“方法”在哪个属性中“设置”计算结果呢?

向薪资类添加一个HashMap,可以在其中放置部分结果,并将“key”传递给“step”方法,指示它放置部分结果的位置

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

问题:

  • 在某些地方,我需要将HashMap填充到JPA实体中以便将其保存到数据库中
  • 如果其他开发人员更改键的名称(出于任何原因),则可能会破坏属性的“填充”

最终说明:我的应用程序中有其他类似情况的“相似”实体,因此如果我们可以找到一般解决方案,那将是很棒的:D


编辑:关于如何建模要持久化的数据的新疑问

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

通过更高的抽象级别,我的应用程序做了类似这样的事情:

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

所以,第二步几乎已经解决了。我会使用类似命令模式的东西。我的问题现在是如何建模数据以在关系型数据库中持久化它。因为除了每个操作的部分“结果”之外,还有更多的信息,我不需要向用户显示,但我需要将其存储在数据库中。

我的想法是这样的:

薪资 表:

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

partialResults 表格:

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

问题在于,我的“未来价值”操作输出以下信息:

  • 未来价值的日期
  • 用于更新价值的系数
  • 部分金额

但是,“应用税”操作具有不同的信息:

  • 应用的百分比税率
  • 部分金额

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

4个回答

4
我会将您所做的“操作”编码为对象。当您对数据应用操作时,该操作是一个对象。操作对象保留其自己的某些信息(可能是起始值和结束值),并且它知道如何格式化该值以进行输出。
开始时,您拥有这些操作的队列 - 每个操作略有不同(可能是不同的类,也可能只是在创建它们时具有内部状态差异)。将此队列与数据相结合,并将数据从队列中的一个部分传递到下一个部分,或者可以将其视为迭代队列并将每个操作应用于数据(可能要传递数据)。
完成后,操作对象都记住了它们所做的历史记录,并知道如何格式化它(例如,“扣除%s个百分比的税”),而您的数据已经被改变为正确的值。
这还可以给您提供“解开”功能,您可以将该模式反向应用到以前的值上(在本例中可能没有兴趣,但在某些情况下可能非常有用)。
我认为这被称为“命令”模式。在任何情况下,它都非常有用。
其他好处是您可以使用数据或任意数量的来源选择放入队列中的操作组,从而可以调整计算而无需进行任何代码更改,在许多情况下都非常方便。
类将非常简单,如下所示:
``` public class TaxOperation { private double rate; private double amountTaxed;
public TaxOperation(double rate) { this.rate=rate; }
@Override public double getValue(double initial) { amountTaxed = initial * rate; return initial - amountTaxed; }
@Override public String getDescription() { return "应用了" + rate + "的税率,扣除了" + amountTaxed; } } ```
这似乎非常简单,事实上就是这样,但有时简单的类是最有用的。显然,“getValue”操作不是您想要的,但是您应该能够看到如何使用该模式来推导出您想要的。
还要注意,“操作”可以执行任何操作,例如对于线程非常有用(想象一下您的操作需要几秒钟或几分钟,您只需将它们的队列提交给处理器并让它们运行)。

谢谢您的评论,它真的很有用和启发性。现在我对如何保存部分结果如果我使用这种模式有一些疑问,我已经更新了问题并提供了更多细节。谢谢! - Matyf
您可以直接将“Queue”中的对象集持久化--可以存储到数据库中,也可以序列化到文件中。如果您要从原始数据中分离它们,可能需要在内部存储更多信息,例如“Start”和“End”值,以便于跟踪。可以将起始/结束值存储在“Operation”抽象基类中以隐藏某些冗余代码。 - Bill K
我不明白你的想法。此外,组织的要求是我必须使用关系型数据库。应用程序还使用Hibernate作为ORM将实体映射到数据库。 - Matyf
1
@Matyf,你可以像通常使用注释或其他方式一样,使用Hibernate将操作类映射到数据库表。 - artbristol

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 模式,该模式使用接口负责创建和保存对象。

希望这会对您有所帮助。 :)


2
有一些你误解了,我不需要部分值的总和,我需要的是金额的不同部分“状态”。例如:原始金额:100。含税金额:110。受最大限制的含税金额:90。受第二个税额限制的含税金额:95。应用程序使用此最后一个值进行其他计算,例如平均值等。但对于我的应用程序的最后一个“屏幕”,我必须向用户显示计算的详细信息。想象一种带有每个“步骤”细节的电子表格,以获得最后的金额。 - Matyf
另外,我尝试使用策略模式来实现您的建议,但是我遇到了一个问题:我的不同“税收应用”策略(实际上,这些并不像示例那么简单)需要不同的“参数”,因此,我无法为策略模式创建通用接口。这是我面临的另一个问题。 - Matyf
我刚刚注意到我的代码有一些错误,我刚刚编辑了原始帖子并进行了修复。此外,我添加了一个快速示例,展示我需要的报告类型。感谢@Idipaolo的帮助。 - Matyf
我现在明白了问题,抱歉! - Idipaolo
谢谢您的评论,它真的很有用和启发性。现在我对如何保存部分结果如果我使用这种模式有一些疑问,我已经更新了问题并提供了更多细节。谢谢! - Matyf

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等等。

谢谢您的评论,它真的很有用和启发性。现在我对如何保存部分结果如果我使用这种模式有一些疑问,我已经更新了问题并提供了更多细节。谢谢! - Matyf
组织的要求是需要将信息存储在关系型数据库中,而不是Json或其他格式。此外,应用程序使用Hibernate将实体映射到数据库。因此,我的选择受到限制 :( - Matyf

0

不禁想到这对你会很有用,jbatch


我的进程不是批处理。它是在线的。 - Matyf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接