public boolean hasAnyProcessingOrder(List<Order> orders) {
for (Order order : orders) {
if ("processing".equals(order.getStatus())) {
return true;
}
}
return false;
}
public boolean hasAnyProcessingOrder(List<Order> orders) {
return orders.stream().anyMatch(order -> "processing".equals(order.getStatus()));
}
public boolean hasSpecificOrder(List<Order> orders) {
for (Order order : orders) {
if ("processing".equals(order.getStatus())) {
if (order.getAmount() > 1000) {
if (order.getOrderDate().isEqual(LocalDate.now())) {
order.setStatus("processed");
System.out.println("Order has been processed: " + order);
}
}
}
}
}
public boolean hasSpecificOrder(List<Order> orders) {
for (Order order : orders) {
if (!"processing".equals(order.getStatus())) continue;
if (order.getAmount() <= 1000) continue;
if (!order.getOrderDate().isEqual(LocalDate.now())) continue;
order.setStatus("processed");
System.out.println("Order has been processed: " + order);
}
}
public class OrderService {
private List<Order> orders;
public boolean hasSpecificOrder() {
return orders.stream().anyMatch(order ->
"processing".equals(order.getStatus()) &&
order.getAmount() > 1000 &&
order.getOrderDate().isEqual(LocalDate.now())
);
}
}
public class ReportService {
private List<Order> orders;
public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
return orders.stream().filter(order ->
"processing".equals(order.getStatus()) &&
order.getAmount() > 1000 &&
order.getOrderDate().isEqual(LocalDate.now())
).collect(Collectors.toList());
}
}
public class Orders {
private List<Order> orders;
public Orders(List<Order> orders) {
this.orders = orders;
}
public boolean hasSpecificOrder() {
return orders.stream().anyMatch(this::matchesCriteria);
}
public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
return orders.stream().filter(this::matchesCriteria).collect(Collectors.toList());
}
private boolean matchesCriteria(Order order) {
return "processing".equals(order.getStatus()) &&
order.getAmount() > 1000 &&
order.getOrderDate().isEqual(LocalDate.now());
}
}
public class OrderService {
private Orders orders;
public boolean hasSpecificOrder() {
return orders.hasSpecificOrder();
}
}
public class ReportService {
private Orders orders;
public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
return orders.getProcessingOrdersFromTodayWithHighAmount();
}
}
public class OrderService {
private List<Order> orders = new ArrayList<>();
private int totalAmount = 0;
public boolean addOrder(Order order) {
if (!isValid(order)) {
throw new IllegalArgumentException();
}
int amountAfterDiscount = order.getPrice();
if (order.isDiscountApplicable()) {
amountAfterDiscount = applyDiscount(order.getPrice());
}
if (totalAmount + amountAfterDiscount <= 10000) {
totalAmount += amountAfterDiscount;
orders.add(order);
return true;
} else {
return false;
}
}
private boolean isValid(Order order) {
return order.getId() >= 0 && !order.getName().isEmpty();
}
private int applyDiscount(int price) {
return price - 100;
}
}
public class SeasonalOrderService {
private OrderService orderService = new OrderService();
public boolean addSeasonalOrder(Order order) {
if (order.getId() < 0 || order.getName().isEmpty()) {
throw new IllegalArgumentException();
}
int amountAfterDiscount = order.getPrice();
if (order.isDiscountApplicable()) {
amountAfterDiscount = orderService.applyDiscount(order.getPrice()); // Problem here
}
if (orderService.totalAmount + amountAfterDiscount <= 12000) { // Problem here
orderService.totalAmount += amountAfterDiscount; // Problem here
orderService.orders.add(order); // Problem here
return true;
} else {
return false;
}
}
}
SeasonalOrderService
가 OrderService
의 내부 로직과 상태를 변경하고 있습니다.// 전략 패턴(기본 할인 정책과 여름 할인 정책이 개념(할인 정책)은 같으나 비즈니스 로직이 다름.)
interface DiscountPolicy {
int MIN_AMOUNT = 0; // 최소 할인 금액
int applyDiscount(int price);
}
// 할인 정책 1 (기본 할인 정책)
class FlatDiscountPolicy implements DiscountPolicy {
private final int discountAmount;
FlatDiscountPolicy(int discountAmount) {
this.discountAmount = discountAmount;
}
@Override
public int applyDiscount(int price) {
return Math.max(price - discountAmount, MIN_AMOUNT);
}
}
// 할인 정책 2 (여름 할인 정책)
class PercentageDiscountPolicy implements DiscountPolicy {
private final double discountPercentage;
PercentageDiscountPolicy(double discountPercentage) {
this.discountPercentage = discountPercentage;
}
@Override
public int applyDiscount(int price) {
return (int) Math.max(price * (1 - discountPercentage), MIN_AMOUNT);
}
}
// OrderBasicSaleService , OrderSummerSaleService 동일 코드 분리
class OrderProcessor {
private final DiscountPolicy discountPolicy;
OrderProcessor(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public int processOrder(Order order) {
int amountAfterDiscount = order.getPrice();
if (order.isDiscountApplicable()) {
amountAfterDiscount = discountPolicy.applyDiscount(order.getPrice());
}
return amountAfterDiscount;
}
}
// 기본 할인
class OrderBasicSaleService {
private final OrderProcessor orderProcessor; // 컴포지션(has-a 관계)
OrderBasicSaleService() {
this.orderProcessor = new OrderProcessor(new FlatDiscountPolicy(100));
}
public boolean addOrder(Order order) {
int finalAmount = orderProcessor.processOrder(order); // 위임(orderProcessor에게 위임함.)
return true;
}
}
// 여름 할인
class OrderSummerSaleService {
private final OrderProcessor orderProcessor; // 컴포지션(has-a 관계)
OrderSummerSaleService() {
this.orderProcessor = new OrderProcessor(new PercentageDiscountPolicy(0.1));
}
public boolean addOrder(Order order) {
int finalAmount = orderProcessor.processOrder(order); // 위임(orderProcessor에게 위임함.)
return true;
}
}
OrderProcessor
를 변경하는 것이 아닌 새로운 클래스를 만들고 생성자를 통해 생성하면 된다.OrderProcessor
는 할인 로직을 처리하는 책임만 가지고 있으므로 서비스 클래스들은 주문 처리 로직에만 집중하면 된다.OrderService
, SeasonalOrderService
두 클래스는 거의 유사한 코드를 가지고 있다.OrderService
, SeasonalOrderService
를 컴포지션를 사용해서 묶었다고 가정 했을 때, 갑자기 SeasonalOrderService
의 할인 정책이 바뀌게 된다면 오히려 중복을 제거하려 했다가 더 큰 공사를 하게 될 것이다.String name1 = "John";
Optional<String> opt1 = Optional.ofNullable(name1); // opt1은 "John" 값을 포함하는 Optional이다.
String name2 = null;
Optional<String> opt2 = Optional.ofNullable(name2); // opt2는 값이 없는 Optional로, Optional.empty()와 같다.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
}
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
// 1. 로그 남기기
System.err.println("An error occurred: " + e.getMessage());
// 2. 사용자에게 의미 있는 메시지 표시
System.out.println("You tried to divide by zero. Please provide a valid denominator.");
// 3. 프로그램의 흐름 조정
// 예를 들면, 다시 입력을 받는다거나 기본값을 설정할 수 있다.
}
class OrderService {
public void add(Order o) { /*...*/ }
public void update(Order o) { /*...*/ }
public void get(int id) { /*...*/ }
public void del(int id) { /*...*/ }
}
class OrderService {
public void addNewOnlineOrder(Order onlineOrder) { /*...*/ }
public void updateUnshippedOrder(Order unshippedOrder) { /*...*/ }
public void retrieveOrderById(int orderId) { /*...*/ }
public void deleteCanceledOrder(int canceledOrderId) { /*...*/ }
}
// 주문 생성과 관련된 로직만 담당하는 서비스
class OrderCreationService {
public void addNewOnlineOrder(Order onlineOrder) {
// 온라인 주문 추가 로직
}
}
// 주문 상태와 관련된 업데이트 로직만 담당하는 서비스
class OrderUpdateService {
public void updateUnshippedOrder(Order unshippedOrder) {
// 발송되지 않은 주문 업데이트 로직
}
public void deleteCanceledOrder(int canceledOrderId) {
// 취소된 주문 삭제 로직
}
}
// 주문 조회와 관련된 로직만 담당하는 서비스
class OrderRetrievalService {
public Order retrieveOrderById(int orderId) {
// 주문 ID를 통한 주문 조회 로직
return new Order(); // 예시입니다. 실제 로직에 따라 변경이 필요합니다.
}
}
규칙 | 이유 |
---|---|
로직을 변경할 때는 주석도 함께 변경해야 함. | 주석을 제대로 변경하지 않으면, 실제 로직과 달라져 주석을 읽는 사람에게 혼란을 줌. |
로직의 내용을 단순하게 설명하기만 하는 주석은 달지 않음. | 실질적으로 가독성을 높이지 않고, 주석 유지보수가 힘듦. |
가독성이 나쁜 로직에 설명을 추가하는 주석은 달지 않음. 대신 로직의 가독성을 높여야함. | 주석 유지 보수가 힘들고, 갱신되지 않아 낡은 주석이 될 가능성이 높음. |
로직의 의도와 사양을 변경할 때 주의할 점을 주석으로 달아야함. | 유지 보수와 사양 변경에 도움이 됨. |
/**
* 주문 항목을 추가한다.
*
* @param orderId 주문 ID
* @param orderItem 추가할 주문 항목
* @return 추가된 후의 전체 주문
* @throws IllegalArgumentException 주문 ID가 유효하지 않은 경우 스로우
* @throws IllegalStateException 주문 상태가 추가를 허용하지 않는 경우 스로우
*/
public Order addOrderItem(final int orderId, final OrderItem orderItem) {
Order order = orderRepository.findById(orderId);
if (order == null) {
throw new IllegalArgumentException("유효하지 않은 주문 ID입니다.");
}
if (!order.isAddable()) {
throw new IllegalStateException("현재 주문 상태에서는 항목을 추가할 수 없습니다.");
}
order.addOrderItem(orderItem);
orderRepository.save(order);
return order;
}
public class OrderItems {
private List<OrderItem> items;
public OrderItems(List<OrderItem> items) {
this.items = new ArrayList<>(items);
}
// 내부 컬렉션에 대한 직접적인 접근을 허용하는 getter
public List<OrderItem> getItems() {
return items; // 외부에서 items를 직접 수정할 수 있음
}
// 내부 컬렉션을 변경할 수 있는 setter
public void setItems(List<OrderItem> items) {
this.items = items; // 외부로부터 받은 리스트로 내부 상태 변경
}
public BigDecimal calculateTotalPrice() {
// 아이템 가격의 총합을 계산하여 반환하는 로직 구현
return new BigDecimal("0"); // 임시 코드
}
}
public class Order {
private final OrderItems orderItems;
public Order(OrderItems orderItems) {
this.orderItems = orderItems;
}
// Getter 사용
public OrderItems getOrderItems() {
return orderItems;
}
}
public class OrderService {
public void processOrder(Order order) {
BigDecimal totalPrice = order.getOrderItems().calculateTotalPrice();
}
}
OrderService
에서 order.getOrderItems().calculateTotalPrice();
를 호출하면서, Order
의 내부 상태에 대해 두 단계에 걸쳐 접근하고 있다.setItems()
메서드를 통해 외부에서 List<OrderItem>
를 변경할 수 있게 하여 OrderItems
의 상태를 언제든지 변경할 수 있다.OrderItems
클래스에서 getItems()
메서드를 통해 내부의 List<OrderItem>
에 대한 직접 접근을 허용하고 있습니다.List
의 내용이 변경될 수 있다는 것을 의미하며, 이는 객체의 캡슐화 원칙을 위반하는 것입니다.public class OrderItems {
private final List<OrderItem> items;
public OrderItems(List<OrderItem> items) {
// 내부 리스트를 불변으로 만들어 외부에서 변경할 수 없도록 함
this.items = Collections.unmodifiableList(new ArrayList<>(items));
}
public BigDecimal calculateTotalPrice() {
// 아이템 가격의 총합을 계산하여 반환하는 로직 구현
BigDecimal total = BigDecimal.ZERO;
for (OrderItem item : items) {
total = total.add(item.getPrice());
}
return total;
}
// OrderItems의 다른 동작을 위한 메서드들 (가변성을 방지하기 위해 내부 상태 변경 메서드는 제공하지 않음)
}
public class Order {
private final OrderItems orderItems;
public Order(OrderItems orderItems) {
this.orderItems = orderItems;
}
public BigDecimal calculateTotalPrice() {
// 직접 OrderItems 객체를 노출하지 않고 필요한 동작을 수행
return orderItems.calculateTotalPrice();
}
}
public class OrderService {
public void processOrder(Order order) {
// Order 객체를 통해 필요한 정보를 얻음
BigDecimal totalPrice = order.calculateTotalPrice();
}
}
OrderItems
는 내부 리스트에 대한 직접 접근을 허용하지 않으므로, 객체의 상태를 보호하고, 객체의 행동을 통해서만 상태가 변경될 수 있도록 한다.OrderItems
의 내부 상태가 불변으로 만들어져 있기 때문에, 객체가 생성된 후에는 그 상태가 변경되지 않습니다.OrderService
는 Order
객체를 통해서만 필요한 작업을 수행하므로, Order
의 내부 구조에 대해 알 필요가 없습니다.addOrderItem(OrderItem item)
: 주문에 새 항목을 추가합니다.removeOrderItem(OrderItem item)
: 주문에서 항목을 제거합니다.calculateTotalAmount()
: 주문의 총 금액을 계산합니다.