-
Notifications
You must be signed in to change notification settings - Fork 0
영양성분 계산 로직 리팩토링
koo995 edited this page Sep 21, 2024
·
12 revisions
위의 요구사항에 따르면 다이어리를 작성할때, 섭취식품의 정보를 무게(gram)로 선택하거나 식품의 기본단위((1)개, (1)컵, (1)잔, (1)인분)를 선택하냐에 따라서 영양정보를 알맞게 계산해줘야 한다.
예를 들어, 사용자가 사과를 섭취했을때, 사과 1개를 섭취할 수 있거나 257g을 섭취할 수 있다.
또한 영양성분의 알맞은 계산을 위해서 소수점 2자리 까지 표현하더라도 불필요한 소수점표현은 없애야할 필요가 있다.
예를 들어 칼로리는 245.55kcal, 250kcal 와 같이 표현되어야지 250.10kcal 처럼 불필요한 소수점 자리가 있으면 보기 안좋다.
식품의 영양성분은 NutritionFacts 라는 클래스가 나타낸다. 그리고 기본단위로 선택했을때 영양성분을 나타내는 NutritionFactsPerOneServing클래스, gram단위로 섭취했을 때의 영양성분을 나타내는 NutritionFactsPerGram클래스를 정의하였다.
@ToString
@Getter
@NoArgsConstructor
public class NutritionFacts {
private BigDecimal productTotalCalories; // 제품의 총 칼로리
private BigDecimal productTotalCarbohydrate;
private BigDecimal productTotalProtein;
private BigDecimal productTotalFat;
private BigDecimal productServingSize; // 서빙사이즈
private String productServingUnit; // 식품의 기본 섭취 단위
private BigDecimal productTotalWeightGram; // 식품의 총 중량
@Builder
public NutritionFacts(BigDecimal productTotalCalories, BigDecimal productTotalCarbohydrate, BigDecimal productTotalProtein, BigDecimal productTotalFat, BigDecimal productServingSize, String productServingUnit, BigDecimal productTotalWeightGram) {
this.productTotalCalories = productTotalCalories;
this.productTotalCarbohydrate = productTotalCarbohydrate;
this.productTotalProtein = productTotalProtein;
this.productTotalFat = productTotalFat;
this.productServingSize = productServingSize;
this.productServingUnit = productServingUnit;
this.productTotalWeightGram = productTotalWeightGram;
}
// 단위선택을 gram 으로 선택했을때 사용할 메서드
public NutritionFactsPerGram calculateNutritionFactsPerGram() {
return NutritionFactsPerGram.builder()
.productCaloriesPerGram(stripIfNecessary(
productTotalCalories.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
.productCarbohydratePerGram(stripIfNecessary(
productTotalCarbohydrate.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
.productProteinPerGram(stripIfNecessary(
productTotalProtein.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
.productFatPerGram(stripIfNecessary(
productTotalFat.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
.build();
}
// 기본단위를 선택했을때 사용할 메서드
public NutritionFactsPerOneServing calculateNutritionFactsPerOneServingUnit() {
return NutritionFactsPerOneServing.builder()
.productCaloriesPerOneServing(stripIfNecessary(
productTotalCalories.divide(productServingSize, SCALE, ROUNDING_MODE)))
.productCarbohydratePerOneServing(stripIfNecessary(
productTotalCarbohydrate.divide(productServingSize, SCALE, ROUNDING_MODE)))
.productProteinPerOneServing(stripIfNecessary(
productTotalProtein.divide(productServingSize, SCALE, ROUNDING_MODE)))
.productFatPerOneServing(stripIfNecessary(
productTotalFat.divide(productServingSize, SCALE, ROUNDING_MODE)))
.build();
}
// 불필요한 소수점을 제거하기 위한 메서드
private BigDecimal stripIfNecessary(BigDecimal value) {
BigDecimal strippedValue = value.stripTrailingZeros();
return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
}
}
@Getter
@ToString
public class NutritionFactsPerGram {
private BigDecimal productCaloriesPerGram;
private BigDecimal productCarbohydratePerGram;
private BigDecimal productProteinPerGram;
private BigDecimal productFatPerGram;
@Builder
private NutritionFactsPerGram(BigDecimal productCaloriesPerGram, BigDecimal productCarbohydratePerGram, BigDecimal productProteinPerGram, BigDecimal productFatPerGram) {
this.productCaloriesPerGram = productCaloriesPerGram;
this.productCarbohydratePerGram = productCarbohydratePerGram;
this.productProteinPerGram = productProteinPerGram;
this.productFatPerGram = productFatPerGram;
}
}
@Getter
@ToString
public class NutritionFactsPerOneServing {
private BigDecimal productCaloriesPerOneServing;
private BigDecimal productCarbohydratePerOneServing;
private BigDecimal productProteinPerOneServing;
private BigDecimal productFatPerOneServing;
@Builder
private NutritionFactsPerOneServing(BigDecimal productCaloriesPerOneServing, BigDecimal productCarbohydratePerOneServing, BigDecimal productProteinPerOneServing, BigDecimal productFatPerOneServing) {
this.productCaloriesPerOneServing = productCaloriesPerOneServing;
this.productCarbohydratePerOneServing = productCarbohydratePerOneServing;
this.productProteinPerOneServing = productProteinPerOneServing;
this.productFatPerOneServing = productFatPerOneServing;
}
}
먼저, 각 단위에 맞는 연산을 위한 전략 인터페이스를 정의한다.
public interface NutritionCalculationStrategy {
CalculatedNutrition calculate(NutritionFacts nutritionFacts, ProductIntakeInfo productIntakeInfo);
}
@RequiredArgsConstructor
@Component
public class NutritionCalculator {
private final ProductRepository productRepository;
private final NutritionCalculationStrategyFactory strategyFactory; // 사용자가 선택한 단위의 연산에 해당하는 전략을 가져오는 팩토리
public CalculatedNutrition calculate(ProductIntakeInfo productIntakeInfo) {
Product product = productRepository.findById(productIntakeInfo.getProductId())
.orElseThrow(() -> new BusinessException(INVALID_PRODUCT_ID));
NutritionFacts nutritionFacts = product.getNutritionFacts();
// productIntakeInfo 안에 있는 사용자가 선택한 단위를 가져온다.
NutritionCalculationStrategy strategy = strategyFactory.getStrategy(productIntakeInfo.getServingUnit());
return strategy.calculate(nutritionFacts, productIntakeInfo);
}
}