背景
我正在开发一个应用程序模块,根据当前配置的国家/地区,该模块具有用于贷款计算的不同域类实现。
我正在努力解决一个类,它具有所有国家共享的主逻辑(一种父类)和一些派生类,它们在行为和计算逻辑上有一些细微的差异。
- 主课程 *
<?php
namespace We\Domain\Loans;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use We\Domain\Constants\CreditStructureConstants;
use We\Domain\Constants\RoundingPrecisionConstants;
class LoanOptionPlan
{
use LoanCalculator;
protected $integerAmountPrecision = RoundingPrecisionConstants::INTEGER_AMOUNT;
protected $ratePrecision = RoundingPrecisionConstants::RATE;
protected $principalAmount;
protected $negativePrincipalWithFeesAmount;
protected $repaymentAmount;
protected $lastDueDate;
protected $termTotalDays;
protected $grossInterestRate;
protected $netInterestRate;
protected $annualAmortizationRate;
protected $termAmortizationRate;
protected $totalFinanceCharge;
protected $annualPercentageRate;
protected $installmentsQuantity;
protected $installmentAmount;
protected $grossInterestAmount;
protected $interestTaxesAmount;
protected $insuranceAmount;
protected $cashOutFeeAmount;
protected $cashInFeeAmount;
protected $feeTotalAmount;
protected $installments;
protected $id;
public function __construct(
LoanOption $option, int $installmentsQuantity, ?int $planId = NULL
)
{
$firstDueDate = $option->getFirstDueDate();
$principalAmount = $option->getPrincipalAmount();
$grossInterestRate = $option->getGrossInterestRate();
$taxesCoefficient = $option->getTaxesCoefficient();
$termTotalDays = $this->calculateTermTotalDays(
$firstDueDate, $option->getRequestDate(), $installmentsQuantity
);
$cashOutFeeAmount = $this->calculateCashoutFeeAmount(
$option->getCashOutMaxRate(), $option->getCashOutMinRate(), $termTotalDays, $installmentsQuantity,
$principalAmount
);
$netInterestRate = $this->calculateNetInterestRate($grossInterestRate, $taxesCoefficient);
$annualAmortizationRate = $this->calculateAnnualAmortizationRate(
$netInterestRate, $option->getAnnualNetInsuranceRate()
);
$installmentTermDurationAverage = $this->calculateInstallmentTermDurationAverage(
$termTotalDays, $installmentsQuantity
);
$termAmortizationRate = $this->calculateTermAmortizationRate(
$annualAmortizationRate, $installmentTermDurationAverage
);
$principalWithFeesAmount = $this->calculatePrincipalWithFeesAmount(
$principalAmount, $cashOutFeeAmount
);
$negativePrincipalWithFeesAmount = $this->calculateNegativePrincipalWithFeesAmount(
$principalWithFeesAmount
);
$installmentAmount = $this->calculateInstallmentAmount(
$termAmortizationRate, $installmentsQuantity, $negativePrincipalWithFeesAmount
);
$cashFlowValues = $this->calculateCashFlowValues($negativePrincipalWithFeesAmount);
$this->principalAmount = $principalAmount;
$this->negativePrincipalWithFeesAmount = $negativePrincipalWithFeesAmount;
$this->repaymentAmount = $this->calculateRepaymentAmount($installmentAmount, $installmentsQuantity);
$this->lastDueDate = $firstDueDate->copy()->addMonthsNoOverflow($installmentsQuantity);
$this->termTotalDays = $termTotalDays;
$this->grossInterestRate = $grossInterestRate;
$this->netInterestRate = $netInterestRate;
$this->annualAmortizationRate = $annualAmortizationRate;
$this->termAmortizationRate = $termAmortizationRate;
$this->totalFinanceCharge = $this->calculateTotalFinanceCharge(
$cashFlowValues, $termTotalDays, $installmentsQuantity
);
$this->annualPercentageRate = $this->calculateAnnualPercentageRate($cashFlowValues);
$this->installmentsQuantity = $installmentsQuantity;
$this->installmentAmount = $installmentAmount;
$this->cashOutFeeAmount = $cashOutFeeAmount;
$this->feeTotalAmount = $cashOutFeeAmount;
$this->addInstallments($option, $installmentsQuantity);
$this->id = $planId;
}
protected function convertToArray() : array
{
foreach ($this->installments as $installment) {
$installments[] = $installment->convertToArray();
}
return [
'plan_id' => $this->id,
'installmentsQuantity' => $this->installmentsQuantity,
'original_amount' => $this->principalAmount,
'amount' => $this->principalAmount,
'final_amount' => $this->repaymentAmount,
'days' => $this->termTotalDays,
'payment' => $this->installmentAmount,
'interestRate' => $this->grossInterestRate,
'cft' => $this->totalFinanceCharge,
'apr' => $this->annualPercentageRate,
'cashout_fee' => $this->cashOutFeeAmount,
'cashin_fee' => $this->cashInFeeAmount,
'administrative_fee' => $this->feeTotalAmount,
'interest_amount' => $this->grossInterestAmount,
'tax_amount' => $this->interestTaxesAmount,
'insurance_amount' => $this->insuranceAmount,
'installmentsOptions' => !empty($installments) ? $installments : []
];
}
public function getPrincipalAmount(): float
{
return $this->principalAmount;
}
public function getNegativePrincipalWithFeesAmount() : float
{
return $this->negativePrincipalWithFeesAmount;
}
public function getRepaymentAmount(): float
{
return $this->repaymentAmount;
}
public function getLastDueDate() : Carbon
{
return $this->lastDueDate;
}
public function getTermTotalDays() : int
{
return $this->termTotalDays;
}
public function getGrossInterestRate() : float
{
return round($this->grossInterestRate, $this->ratePrecision);
}
public function getNetInterestRate() : float
{
return round($this->netInterestRate, $this->ratePrecision);
}
public function getAnnualAmortizationRate() : float
{
return $this->annualAmortizationRate;
}
public function getTermAmortizationRate() : float
{
return $this->termAmortizationRate;
}
public function getTotalFinanceCharge() : float
{
return round($this->totalFinanceCharge, $this->ratePrecision);
}
public function getAnnualPercentageRate() : float
{
return round($this->annualPercentageRate, $this->ratePrecision);
}
public function getInstallmentsQuantity() : int
{
return $this->installmentsQuantity;
}
public function getInstallmentAmount() : float
{
return $this->installmentAmount;
}
public function getGrossInterestAmount() : float
{
return $this->grossInterestAmount;
}
public function getInterestTaxesAmount() : float
{
return $this->interestTaxesAmount;
}
public function getInsuranceAmount() : float
{
return $this->insuranceAmount;
}
public function getCashOutFeeAmount() : float
{
return $this->cashOutFeeAmount;
}
public function getCashInFeeAmount() : float
{
return $this->cashInFeeAmount;
}
public function getFeeTotalAmount() : float
{
return round($this->feeTotalAmount, $this->integerAmountPrecision);
}
public function getInstallments() : Collection
{
return $this->installments;
}
public function getId() : ?int
{
return $this->id;
}
protected function createInstallment(LoanOption $option, int $installmentNumber) : void
{
$this->installments->push(new LoanOptionPlanInstallment($option, $this, $installmentNumber));
}
private function addInstallments(LoanOption $option, int $installmentsQuantity)
{
for (
$installmentNumber = CreditStructureConstants::FIRST_INSTALLMENT_NUMBER;
$installmentNumber <= $installmentsQuantity; $installmentNumber++
) {
$this->createInstallment($option, $installmentNumber);
$this->sumInstallmentComponents($this->installments->last());
}
}
private function sumInstallmentComponents(ArLoanOptionPlanInstallment $installment)
{
$cashInFeeAmount = $installment->getCashInFeeAmount();
$this->grossInterestAmount += $installment->getGrossInterestAmount();
$this->interestTaxesAmount += $installment->getInterestTaxesAmount();
$this->insuranceAmount += $installment->getInsuranceAmount();
$this->cashInFeeAmount += $cashInFeeAmount;
$this->feeTotalAmount += $cashInFeeAmount;
}
}
- 派生类 *
<?php
namespace We\Domain\Loans;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use We\Domain\Constants\CreditStructureConstants;
use We\Domain\Constants\RoundingPrecisionConstants;
class UyLoanOptionPlan extends LoanOptionPlan
{
private $indexedUnitExchangeRate;
public function __construct(
LoanOption $option, int $installmentsQuantity, float $indexedUnitExchangeRate, ?int $planId = NULL
)
{
$this->indexedUnitExchangeRate = $indexedUnitExchangeRate;
parent::__construct($option, $installmentsQuantity, $planId);
}
protected function createInstallment(LoanOption $option, int $installmentNumber) : void
{
$this->installments->push(new UyLoanOptionPlanInstallment($option, $this, $installmentNumber));
}
}
我想知道是否有一种方法可以重新实现或更改主类中调用的“calculateCashoutFeeAmount”和“calculateInstallmentAmount”函数引用,并使用相应的“子”实现(将具有不同的签名),而无需复制构造函数逻辑。计算函数是从trait中使用的,所以派生类将在内部使用不同的trait。
我不知道我的设计是否可以接受,我已经想了一整天了,但我还没有得出一个结论。一个来自“父”类的抽象方法可能是一种可能性,但是我不能从那个类创建示例。我面临的另一个问题是,许多计算相互依赖(Matroska风格),因此可能会发生鸡生蛋还是蛋生蛋的情况。
我应该改变设计,还是可以在不重复代码的情况下解决?
任何建议或意见将不胜感激。
1条答案
按热度按时间qnakjoqk1#
你可以通过使用接口或抽象类,或者使用依赖注入来解决这个问题。在这个场景中,
LoanOptionPlan
类和UyLoanOptionPlan
类之间似乎存在关系。但是,当像calculateCashoutFeeAmount
和calculateInstallmentAmount
这样的方法的签名发生变化时,这种关系就会破裂。此类包含太多属性,无法理解。我认为这是一个更大的问题。要管理类中过多的属性,您可以使用DTO对数据进行分组,将相关属性模块化为子类,并通过使用Facade设计模式隐藏不必要的属性来降低复杂性。
我可以根据你的评论给予一个例子,但你应该根据你的工作来更广泛地思考。这个例子可能给予不了你一个解决方案,我只是根据你的代码思考。首先,您可以创建一个接口来封装计算函数。这个接口将作为实现它的类必须遵守的契约。举例来说:
接下来,创建一个名为
LoanCalculator
的类来实现这个接口:现在,在
LoanOptionPlan
类中,不再使用这些计算函数作为属性,而是将它们作为服务注入。这可以通过依赖注入来实现。举例来说:在
UyLoanOptionPlan
中,适当地实现calculateCashoutFeeAmount
和calculateInstallmentAmount
方法:这样,当你想改变计算函数的逻辑时,你只需要创建一个实现
LoanCalculatorInterface
的新类。这种方法使您的代码更加灵活、可读和易于维护。