1

I have a class Installment and a method executeTransaction. The field totalBalance represents the difference between total due and total paid. Inside method executeTransaction the installment object is modified using setters. And after every setter the updateTotalBalance is called.

    public class Installment {
        private BigDecimal principalDue;
        private BigDecimal principalPaid;
        private BigDecimal interestDue;
        private BigDecimal interestPaid;
        private BigDecimal feeDue;
        private BigDecimal feePaid;
        private BigDecimal penaltyDue;
        private BigDecimal penaltyPaid;
        private BigDecimal totalBalance;

        public void updateTotalBalance() {
             this.totalBalance = this.principalDue.subtract(this.penaltyPaid)
                .add(this.interestDue).subtract(this.interestPaid)
                .add(this.feeDue).subtract(this.feePaid)
                .add(this.penaltyDue).subtract(this.penaltyPaid);
        }

        //seters
        //getters
    }

Transaction method:


    public void executeTransaction(Installment installment){
        //code
        installment.setPrincipalPaid(bigDecimalValue);
        installment.updateTotalBalance();
        //code
        installment.setPenaltyDue(bigDecimalValue);
        installment.updateTotalBalance();
    }

I was thinking about putting the updateTotalBalance inside the setters, but for me both of these approaches seem contradictory with the best design principles. Q: I want to know if there are better solutions to update a field in a class when others fields are modified.

bubbles
  • 2,597
  • 1
  • 15
  • 40
  • the most popular solution calls 'update' ones - after all sets are done. If this code's client are going to set something else on top (let assume you domain logic supposes such a behaviour) then 'update' call becomes a burden of this client. All ideas (the original as well) need synch-work to yet be done (if there is such a req). – lotor Oct 04 '19 at 09:38

2 Answers2

2

totalBalance should not be a normal field because it makes Installment illogical data structure.

Illogical data structure is one which can, by its definition, take on nonsensical values.

In your case, Installment is illogical because totalBalance field may contain an incorrect value, so you have to replace this field with method getTotalBalance.

If you don't want totalBalance being recalculated each time, you can apply lazy loading idiom. Here is how you can do it:

public class Installment {
    private BigDecimal principalDue;
    private BigDecimal principalPaid;
    private BigDecimal interestDue;
    private BigDecimal interestPaid;
    private BigDecimal feeDue;
    private BigDecimal feePaid;
    private BigDecimal penaltyDue;
    private BigDecimal penaltyPaid;
    private final ResettableLazyHolder<BigDecimal> totalBalance =
            new ResettableLazyHolder<>(this::calculateTotalBalance);

    private BigDecimal calculateTotalBalance() {
        return principalDue.subtract(penaltyPaid)
                .add(interestDue).subtract(interestPaid)
                .add(feeDue).subtract(feePaid)
                .add(penaltyDue).subtract(penaltyPaid);
    }

    // Always assign fields with setters even in private context
    public void setPrincipalDue(BigDecimal principalDue) {
        // add this line to each setter
        totalBalance.reset();
        this.principalDue = principalDue;
    }

    public BigDecimal getTotalBalance() {
        return totalBalance.get();
    }

    // other getters and setters
}

Where ResettableLazyHolder is:

public class ResettableLazyHolder<T> {
    private boolean initialized = false;
    private T value;
    private final Supplier<? extends T> initializer;

    public ResettableLazyHolder(Supplier<? extends T> initializer) {
        this.initializer = initializer;
    }

    // add synchronized if you need thread-safety
    public T get() {
        // it's not enough to check that value == null because null can be a valid value
        if(!initialized) {
            value = initializer.get();
            initialized = true;
        }
        return value;
    }

    public void reset() {
        initialized = false;
        value = null; // releases value for GC
    }
}
IlyaMuravjov
  • 2,352
  • 1
  • 9
  • 27
1

You can follow the single responsibility principle by isoliting (delegating) the calculation in another object and keep the Installment as a simple POJO.

public class Calculator {

    public BigDecimal balance(Installment installment) {
        return installment.getPrincipalDue().subtract(installment.getPenaltyPaid())
                .add(installment.getInterestDue()).subtract(installment.getInterestPaid())
                .add(installment.getFeeDue()).subtract(installment.getFeePaid())
                .add(installment.getPenaltyDue()).subtract(installment.getPenaltyPaid());
    }
}
import java.math.BigDecimal;

@Value
public class Installment {
    private BigDecimal principalDue;
    private BigDecimal principalPaid;
    private BigDecimal interestDue;
    private BigDecimal interestPaid;
    private BigDecimal feeDue;
    private BigDecimal feePaid;
    private BigDecimal penaltyDue;
    private BigDecimal penaltyPaid;
}
bubbles
  • 2,597
  • 1
  • 15
  • 40