Шаблон проектирования для отслеживания частичных результатов сложного процесса

Я столкнулся с проблемой программирования, которую не знаю, как решить объектно-ориентированный и гибкий способ. Я имею в виду некоторые плохие решения, но я ищу хорошее. Я занимаюсь разработкой на 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 строк данных, не обращайте внимания на остальные)

Report Example

Мои идеи:

Добавьте атрибут для каждого "частичного результата" в классе Salary

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

Проблемы:

  • "Шаги" не жесткие, может быть, завтра один "шаг" изменится, появится новый или "значение" какого-то шага изменится. В этом случае мне нужно будет слишком много реорганизовать код.
  • Некоторые "шаги" могут быть повторены, поэтому, как я могу сказать "методу", в каком атрибуте нужно "установить" результат вычисления?

Добавьте HashMap в класс Salary, в который я могу поместить частичные результаты, и передайте методу "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

частичные результаты Таблица:

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

Но проблема в том, что моя операция "будущей стоимости" имеет в качестве вывода следующую информацию:

  • Дата будущей стоимости
  • Коэффициент, используемый для обновления значения
  • Частичная сумма

Но операция "применить налог" имеет другую информацию:

  • % Налога
  • Частичная сумма

Итак, как я могу сохранить различную "выходную" информацию для разных операций?

Ответ 1

Я бы закодировал "Операции", которые вы выполняете как объекты. У вас есть данные, когда вы применяете к ней операцию, эта операция является объектом. объект операции содержит некоторую информацию о нем (возможно, начальное и конечное значение), и он знает, как отформатировать это значение для вывода.

Когда вы начинаете, у вас очередь этих операций - каждая из них немного отличается (возможно, разные классы, возможно, только с внутренними различиями состояний при их создании). Вы объединяете эту очередь с вашими данными и передаете данные из одной части в очередь в следующую, или можете думать об этом как об итерации по очереди и применяя каждую операцию к данным (которые вы, вероятно, проходите).

Когда вы закончите, каждый объект операции запоминает историю того, что они сделали, и знает, как его форматировать (например, "вычесть% s процентов для налога" ), и ваши данные были изменены на правильное значение.

Это также может дать вам "Unwinds", где вы можете применить шаблон в обратном порядке для возврата к предыдущему значению (что, вероятно, не представляет для вас интереса здесь, но в некоторых случаях может быть потрясающим).

Я считаю, что это называется шаблоном "Command". Это ужасно полезно в любом случае.

Другая приятная вещь - группа операций, которые вы размещаете в очереди, может быть выбрана с помощью данных или любого количества источников, позволяющих во многих случаях корректировать ваши вычисления без каких-либо изменений кода.

Классы были бы ужасно просты, что-то вроде:

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
    }
}

Это кажется очень простым, и это так, но иногда простые классы делают больше всего. Очевидно, что операция getValue не то, что вы хотите, но вы должны уметь видеть, как получить то, что вы хотите, используя этот шаблон.

Также обратите внимание, что "Операция" может делать что угодно - например, это может быть удивительно хорошо для потоковой передачи (представьте, что ваши операции занимают секунды или минуты, вы просто отправляете их в очередь на процессор и отпускаете их).

Ответ 2

Вы можете создать класс с именем 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:

  • Один для налогов.
  • Один заработанные деньги.
  • ...

Итак, вы можете легко вычислить все этапы.

Когда вы общаетесь с обработчиками хранилища, я предлагаю использовать шаблон DAO с интерфейсами, которые отвечают за создание и сохранение объектов.

Надеюсь, это будет полезно.:)

Ответ 3

Как насчет чего-то подобного.

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>> для хранения отчета.

Чтобы сохранить планы, вы можете сделать сериализацию SalaryComput, тогда все подклассы будут сериализованы. Это должно позволить вам сохранить результаты, поскольку двоичные массивы - это база данных или файлы на диске. Основной улов - управлять версиями при добавлении и удалении полей. Вы также можете придумать свой собственный формат файла для хранения этих объектов, возможно, как объект JSON, и использовать что-то вроде Джексона для сериализации/десериализации их или JAXB, если вы хотите хранить в XML... и т.д.

Ответ 4

Не могу не подумать, что это было бы полезно для вас, jbatch.