해당 포스팅은 인프런 백기선님의 '리팩토링'을 학습 후 정리한 내용입니다.
public class PriceOrder {
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = quantity * shippingPerCase;
final double price = basePrice - discount + shippingCost;
return price;
}
}
public record Product(double basePrice, double discountThreshold, double discountRate) {
}
public record ShippingMethod(double discountThreshold, double discountedFee, double feePerCase) {
}
PriceOrder 클래스의 priceOrder() 함수에서 냄새가 난다!
해당 함수에서 주문에 값을 매기는 일을 하지만 내부에서 할인가격과 할인된 가격을 구하는 두가지 일을 같이한다.
서로 다른 일을 하는 코드를 각기 다른 함수로 분리하자! 단계를 쪼개보자!
public class PriceOrder {
public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
final PriceData priceData = calculatePriceData(product, quantity);
return applyShipping(priceData, shippingMethod);
}
private PriceData calculatePriceData(Product product, int quantity) {
final double basePrice = product.basePrice() * quantity;
final double discount = Math.max(quantity - product.discountThreshold(), 0)
* product.basePrice() * product.discountRate();
final PriceData priceData = new PriceData(basePrice, discount, quantity);
return priceData;
}
private double applyShipping(PriceData priceData, ShippingMethod shippingMethod) {
final double shippingPerCase = (priceData.basePrice() > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = priceData.quantity() * shippingPerCase;
final double price = priceData.basePrice() - priceData.discount() + shippingCost;
return price;
}
}
public record PriceData(double basePrice, double discount, int quantity) {
}
수정한 부분과 새로 작성한 코드 이외에는 모두 동일하다.
- PriceOrder() 함수의 내부 코드에서 서로 다른 일을 하는 코드를 함수로 분리하였다.
- 또한 함수의 추상화 단계를 하나로 하여 priceOrder() 함수는 하나의 일을 하는 함수가 되었다.
public class Account {
private int daysOverdrawn;
private AccountType type;
public Account(int daysOverdrawn, AccountType type) {
this.daysOverdrawn = daysOverdrawn;
this.type = type;
}
public double getBankCharge() {
double result = 4.5;
if (this.daysOverdrawn() > 0) {
result += this.overdraftCharge();
}
return result;
}
private int daysOverdrawn() {
return this.daysOverdrawn;
}
private double overdraftCharge() {
if (this.type.isPremium()) {
final int baseCharge = 10;
if (this.daysOverdrawn <= 7) {
return baseCharge;
} else {
return baseCharge + (this.daysOverdrawn - 7) * 0.85;
}
} else {
return this.daysOverdrawn * 1.75;
}
}
}
public class AccountType {
private boolean premium;
public AccountType(boolean premium) {
this.premium = premium;
}
public boolean isPremium() {
return this.premium;
}
}
Account 클래스 overdraftCharge() 함수에서 AccountType 클래스에 대한 의존이 있다.
해당 함수를 AccountType 클래스로 옮기자!
public class Account {
private int daysOverdrawn;
private AccountType type;
public Account(int daysOverdrawn, AccountType type) {
this.daysOverdrawn = daysOverdrawn;
this.type = type;
}
public double getBankCharge() {
double result = 4.5;
if (this.daysOverdrawn() > 0) {
result += this.type.overdraftCharge(this.daysOverdrawn);
}
return result;
}
private int daysOverdrawn() {
return this.daysOverdrawn;
}
}
public class AccountType {
private boolean premium;
public AccountType(boolean premium) {
this.premium = premium;
}
public boolean isPremium() {
return this.premium;
}
public double overdraftCharge(int daysOverdrawn) {
if (this.isPremium()) {
final int baseCharge = 10;
if (daysOverdrawn <= 7) {
return baseCharge;
} else {
return baseCharge + (daysOverdrawn - 7) * 0.85;
}
} else {
return daysOverdrawn * 1.75;
}
}
}
- overdraftCharge() 함수를 AccountType 클래스로 옮겼다.
- 이를 위해 인텔리제이에서 move instance method 기능을 사용하였다.
- 반대로 overDraftCharge() 함수가 account 를 많이 참조한다면 옮기자!
public class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
public String telephoneNumber() {
return this.officeAreaCode + " " + this.officeNumber;
}
public String name() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String officeAreaCode() {
return officeAreaCode;
}
public void setOfficeAreaCode(String officeAreaCode) {
this.officeAreaCode = officeAreaCode;
}
public String officeNumber() {
return officeNumber;
}
public void setOfficeNumber(String officeNumber) {
this.officeNumber = officeNumber;
}
}
Person 클래스에서 officeAreaCode, officeNumber 필드가 밀접한 관계를 가지고 있어 책임을 분리 시켜줄 필요가 있다.
새로운 클래스를 만들어 책임을 분산 시킬 수 있다.
public class Person {
private TelePhoneNumber telePhoneNumber;
private String name;
public Person(TelePhoneNumber telePhoneNumber, String name) {
this.telePhoneNumber = telePhoneNumber;
this.name = name;
}
public String telephoneNumber() {
return telePhoneNumber.toString();
}
public String name() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TelePhoneNumber getTelePhoneNumber() {
return telePhoneNumber;
}
}
public class TelePhoneNumber {
private String areaCode;
private String number;
public TelePhoneNumber(String areaCode, String number) {
this.areaCode = areaCode;
this.number = number;
}
public String getAreaCode() {
return areaCode;
}
public void setAreaCode(String areaCode) {
this.areaCode = areaCode;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "TelePhoneNumber{" +
"areaCode='" + areaCode + '\'' +
", number='" + number + '\'' +
'}';
}
}
밀접한 officeAreaCode, officeNumber 두 필드를 필드로 하는 클래스를 만들어 책임을 분리하였다.