I'm facing a programming problem that I don't know how to solve in a object oriented and flexible way. I have in mind some bad solutions, but I'm searching for a good one. I develop in Java, so I prefer Java ideas, but any object oriented idea is welcome.
I've been searching for ideas, design patterns, or some algorithm that can help me, but I don't know which terminology or name give to my problem, so I couldn't find any clue.
Problem:
Summary:
I need to track partial results of a process that makes different changes to a collection of entities. I need this to report to the user, the detail of each calculation "step" in a table report. And also I need to persist this collection in a database.
Detail:
The software I maintain has an entity similar to this one:
public class Salary {
private Date date;
private BigDecimal amount;
}
That is grouped in a Collection, like this:
List<Salary> thisYearSalaries;
This set of entities can be modified by a set of "tasks" depending on some rules:
For example:
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);
}
}
(...)
What I need is to process this collection of salaries, making changes to the amount of money but preserving all the intermediate "states" of the money amount, to show it to the user in a table with columns like these:
Example Report: (the question is about the first 4 rows of data only, don't pay attention on the rest)
My Ideas:
Add an attribute for each "partial result" on the class Salary
public class Salary {
(...)
private BigDecimal originalAmount;
private BigDecimal amountAfterFirstTax;
private BigDecimal amountAfterMaximumRestriction;
(...)
}
Problems:
Add a HashMap to the Salary class where I can put the partial results, and pass to the "step" method the "key" where it have to put the partial result
public class Salary {
(...)
HashMap<String, BigDecimal> partialResults;
(...)
}
Problems:
Final Note: There are other similar situations with other "similar" entities in my application, so it would be great if we can find a general solution for this :D
Edit: New doubts about how to model the data to persist it
All the ideas are similar and really useful. All of them are related to the Command Pattern and I think that are great solutions. But now I have some new doubts:
With a higher level of abstraction, my application does something like this:
So, the second step is almost solved. I would use something like the Command Pattern. My problem now is how can I model the data to persist it in a relational database. Because, besides the partial "result" of each operation, there is more information that I don't need to show to the user, but I need to store it in a database.
My idea is something like this:
Salaries Table:
id
date // The date of the Salary
finalAmount // The final amount after all the calculations ends
partialResults Table:
id
salaryId // The id of the salary which this element represent a partial result
amount // Partial amount
operationCode // Type of operation
operationDescription
But the problem is, my "future value" operation has, as an output, this information:
But "apply tax" operation has different information:
So, how can I save different "output" information for different operations?
I would code the "Operations" you do as objects. You have the data, when you apply an operation to it, that operation is an object. the operation object holds some information of it's own (Perhaps a starting and ending value) and it knows how to format that value for output.
When you start, you a queue of these operations--each one slightly different (possibly different classes, possibly just with internal state differences when they are created). You combine this queue with your data and pass the data from one piece in the queue to the next, or you can think of it as iterating over the queue and applying each operation to the data (which you probably pass in).
When you are done, the operation objects each remember the history of what they did and know how to format it (for instance, "deducted %s percent for tax") and your data has been mutated to the correct value.
This can also give you "Unwinds" where you could apply the pattern in the reverse order to back up to a previous value (Which probably has no interest to you here, but in some cases can be amazing).
I believe this is called the "Command" pattern. It's terribly useful in any case.
The other nice thing--the group of operations you place into the queue can be selected with data or any number of sources allowing you to adjust your calculations without any code changes in many cases.
The classes would be terribly simple, something like:
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 "A tax of "+rate+" was applied which deducted "+amountTaxed } }
This seems really simple, and it is, but sometimes the simple classes do the most. Obviously the getValue operation isn't what you want, but you should be able to see how to derive what you want using that pattern.
Also note that an "Operation" can do anything--for instance it can be amazingly good for threading (Imagine your operations take seconds or minutes, you just submit a queue of them to a processor and let them go).
You can create a class named SalaryReport which is composed of many objects of SalaryReportRow.
In a SalaryReport you have a method name calculate() which executes all the algorithms. SalaryReportRow can be specialized in many type of rows that suit your needs.
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);
}
...
}
When calculating taxes you can use a Strategy Pattern to mantain your code more flexible. If you have to combine many strategies you can use also a Composite Pattern with Strategy or a Chain of Responsibility with Strategy.
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);
...
}
}
So the SalaryReport will have a method called setSalaryCalculators(List) (you can use also more than one algorithms at once), where List is a list that contains all the strategies applied.
You will have more SalaryReportRows:
So you can easily calculate all steps.
When you communicate with storage handlers I suggest to use a DAO pattern with interfaces which is responsible of creating and saving objects.
I hope this will be helpful. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With