소프트웨어는 변경에 유연하게 대처할 수 있어야 한다
하나의 모듈(함수 or 클래스)이 다양하게 변경되어야 하는 경우
서로 다른 문제는 서로 다른 모듈에서 해결
상품에 대한 할인, 배송의 역할을 각 각의 메서드로 분리하고
메서드에 사용되는 많은 매개변수를 중간 데이터를 만들어 모은다
class Refactoring {
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;
}
}
class Refactoring {
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();
return new PriceData(basePrice, discount, quantity);
}
private double applyShipping(PriceData priceData, ShippingMethod shippingMethod) {
final double shippingPerCase = (priceData.basePrice() > shippingMethod.discountThreshold()) ?
shippingMethod.discountedFee() : shippingMethod.feePerCase();
final double shippingCost = priceData.quantity() * shippingPerCase;
return priceData.basePrice() - priceData.discount() + shippingCost;
}
}
public record PriceData(double basePrice, double discount, int quantity) {}
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;
}
}
}
class AccountType {
private boolean premium;
public AccountType(boolean premium) {
this.premium = premium;
}
public boolean isPremium() {
return this.premium;
}
}
class Account {
private int daysOverdrawn;
private AccountType 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;
}
}
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;
}
}
}
Person 클래스를 보면 역할이 다른 오피스 코드, 번호의 필드와 메서드들이 포함되어 있다
별도의 클래스를 생성하여 밀접한 필드와 메서드를 추출한다
class Person {
private String name;
private String officeAreaCode;
private String officeNumber;
public String telephoneNumber() {
return this.officeAreaCode + " " + this.officeNumber;
}
// ...getter, setter
}
class Person {
private final TelephoneNumber telephoneNumber;
private String name;
public Person(TelephoneNumber telephoneNumber, String name) {
this.telephoneNumber = telephoneNumber;
this.name = name;
}
public String telephoneNumber() {
return this.telephoneNumber.toString();
}
public String name() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TelephoneNumber getTelephoneNumber() {
return telephoneNumber;
}
}
class TelephoneNumber {
private String areaCode;
private String number;
public TelephoneNumber(String areaCode, String officeNumber) {
this.areaCode = areaCode;
this.number = officeNumber;
}
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;
}
}