diff --git a/builder/README.md b/builder/README.md index ccfc378d6e36..33294aebd094 100644 --- a/builder/README.md +++ b/builder/README.md @@ -129,6 +129,111 @@ Use the Builder pattern when * [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) * [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) +## 실무 예제 (Practical Examples) + +### 1. UserDTO - 사용자 정보 DTO + +API 요청/응답에서 사용되는 복잡한 사용자 정보 객체를 Builder 패턴으로 구성합니다. + +```java +UserDTO user = UserDTO.builder() + .id(1L) + .username("john.doe") + .email("john@example.com") + .firstName("John") + .lastName("Doe") + .age(30) + .phoneNumber("010-1234-5678") + .address("123 Main St, Seoul") + .addRole("USER") + .addRole("ADMIN") + .active(true) + .build(); + +System.out.println(user.getFullName()); // "John Doe" +System.out.println(user.hasRole("ADMIN")); // true +``` + +**실무 활용:** +- API 요청/응답 DTO 생성 +- 데이터베이스 엔티티에서 DTO로 변환 +- 테스트 데이터 생성 +- 복잡한 사용자 프로필 구성 + +### 2. HttpRequest - HTTP 요청 빌더 + +REST API 호출을 위한 HTTP 요청을 체계적으로 구성합니다. + +```java +HttpRequest request = HttpRequest.builder() + .post() + .url("https://api.example.com/users") + .addHeader("Content-Type", "application/json") + .addHeader("Authorization", "Bearer token123") + .addQueryParam("page", "1") + .addQueryParam("size", "10") + .jsonBody("{\"name\": \"John Doe\"}") + .timeout(5000) + .build(); + +String response = request.execute(); +``` + +**실무 활용:** +- REST API 클라이언트 구현 +- HTTP 라이브러리 래퍼 +- 마이크로서비스 간 통신 +- 테스트용 HTTP 요청 생성 + +### 3. EmailMessage - 이메일 메시지 빌더 + +복잡한 이메일 메시지를 단계적으로 구성합니다. + +```java +EmailMessage email = EmailMessage.builder() + .from("sender@example.com") + .to("recipient@example.com") + .addCc("manager@example.com") + .addBcc("admin@example.com") + .subject("Welcome to our service!") + .body("
Thank you for joining us.
") + .html(true) + .priority(Priority.HIGH) + .addAttachment("/path/to/document.pdf") + .build(); + +boolean sent = email.send(); +System.out.println("Total recipients: " + email.getTotalRecipientCount()); +``` + +**실무 활용:** +- 이메일 발송 시스템 +- 알림 서비스 +- 마케팅 이메일 생성 +- 시스템 알림 메일 + +## Builder 패턴의 실무 장점 + +1. **가독성**: 복잡한 객체 생성 과정을 명확하게 표현 +2. **유연성**: 선택적 파라미터를 쉽게 처리 +3. **불변성**: Immutable 객체 생성으로 Thread-safe 보장 +4. **유효성 검증**: build() 메서드에서 일괄 유효성 검증 +5. **유지보수성**: 새로운 속성 추가가 용이 + +## 테스트 실행 + +각 실무 예제에는 comprehensive한 테스트 코드가 포함되어 있습니다: + +```bash +# 모든 테스트 실행 +mvn test + +# 특정 테스트만 실행 +mvn test -Dtest=UserDTOTest +mvn test -Dtest=HttpRequestTest +mvn test -Dtest=EmailMessageTest +``` + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/builder/src/main/java/com/iluwatar/builder/EmailMessage.java b/builder/src/main/java/com/iluwatar/builder/EmailMessage.java new file mode 100644 index 000000000000..40d5aa8d2fe7 --- /dev/null +++ b/builder/src/main/java/com/iluwatar/builder/EmailMessage.java @@ -0,0 +1,396 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.builder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 실무 예제: 이메일 메시지 빌더 + * + *EmailMessage는 이메일을 구성하는 Builder 패턴 예제입니다. + * 복잡한 이메일 메시지를 단계적으로 구성할 수 있습니다. + * + *
+ * EmailMessage email = EmailMessage.builder()
+ * .from("sender@example.com")
+ * .to("recipient@example.com")
+ * .subject("Welcome to our service!")
+ * .body("Thank you for joining us.")
+ * .addCc("manager@example.com")
+ * .addBcc("admin@example.com")
+ * .priority(Priority.HIGH)
+ * .html(true)
+ * .build();
+ *
+ * // 이메일 전송 시뮬레이션
+ * boolean sent = email.send();
+ *
+ *
+ * HttpRequest는 HTTP 요청을 구성하는 Builder 패턴 예제입니다. + * 복잡한 HTTP 요청을 단계적으로 구성할 수 있습니다. + * + *
+ * HttpRequest request = HttpRequest.builder()
+ * .method(HttpMethod.POST)
+ * .url("https://api.example.com/users")
+ * .addHeader("Content-Type", "application/json")
+ * .addHeader("Authorization", "Bearer token123")
+ * .body("{\"name\": \"John Doe\"}")
+ * .timeout(5000)
+ * .build();
+ *
+ * // 요청 실행 시뮬레이션
+ * String response = request.execute();
+ *
+ *
+ * UserDTO는 사용자 정보를 전송하기 위한 객체로, Builder 패턴을 사용하여 + * 복잡한 사용자 정보를 단계적으로 구성할 수 있습니다. + * + *
+ * UserDTO user = UserDTO.builder()
+ * .id(1L)
+ * .username("john.doe")
+ * .email("john@example.com")
+ * .firstName("John")
+ * .lastName("Doe")
+ * .age(30)
+ * .phoneNumber("010-1234-5678")
+ * .addRole("USER")
+ * .addRole("ADMIN")
+ * .build();
+ *
+ *
+ * This is HTML
") + .html(true) + .build(); + + // Then + assertTrue(email.isHtml()); + } + + @Test + void testEmailPriority() { + // Given & When + EmailMessage email = EmailMessage.builder() + .from("sender@example.com") + .to("recipient@example.com") + .subject("Urgent") + .body("This is urgent") + .priority(Priority.URGENT) + .build(); + + // Then + assertEquals(Priority.URGENT, email.getPriority()); + } + + @Test + void testEmailWithAttachments() { + // Given & When + EmailMessage email = EmailMessage.builder() + .from("sender@example.com") + .to("recipient@example.com") + .subject("Email with attachments") + .body("Please find attachments") + .addAttachment("/path/to/file1.pdf") + .addAttachment("/path/to/file2.docx") + .build(); + + // Then + assertTrue(email.hasAttachments()); + assertEquals(2, email.getAttachments().size()); + assertTrue(email.getAttachments().contains("/path/to/file1.pdf")); + assertTrue(email.getAttachments().contains("/path/to/file2.docx")); + } + + @Test + void testEmailWithMultipleAttachments() { + // Given + ListConfigurationManager는 애플리케이션 전체에서 사용되는 설정값들을 중앙에서 관리합니다. + * Singleton 패턴을 사용하여 애플리케이션 전체에서 하나의 설정 관리자만 존재하도록 보장합니다. + * + *
+ * // 설정값 저장
+ * ConfigurationManager.getInstance().setProperty("db.url", "jdbc:mysql://localhost:3306/mydb");
+ * ConfigurationManager.getInstance().setProperty("api.timeout", "30000");
+ *
+ * // 설정값 조회
+ * String dbUrl = ConfigurationManager.getInstance().getProperty("db.url");
+ * String timeout = ConfigurationManager.getInstance().getProperty("api.timeout");
+ *
+ *
+ * DatabaseConnectionPool은 데이터베이스 연결을 효율적으로 관리하는 연결 풀을 구현합니다. + * Singleton 패턴을 사용하여 애플리케이션 전체에서 하나의 연결 풀만 존재하도록 보장합니다. + * + *
+ * DatabaseConnectionPool pool = DatabaseConnectionPool.getInstance();
+ *
+ * // 연결 획득
+ * Connection conn = pool.acquireConnection();
+ * try {
+ * // 데이터베이스 작업 수행
+ * conn.executeQuery("SELECT * FROM users");
+ * } finally {
+ * // 연결 반환 (필수!)
+ * pool.releaseConnection(conn);
+ * }
+ *
+ *
+ * LoggerService는 애플리케이션 전체에서 통일된 로깅 기능을 제공합니다. + * Singleton 패턴을 사용하여 로그 메시지를 중앙에서 관리하고 일관된 형식으로 출력합니다. + * + *
+ * LoggerService logger = LoggerService.getInstance();
+ * logger.info("Application started");
+ * logger.error("Database connection failed");
+ * logger.debug("User input: " + userInput);
+ * logger.warning("Memory usage above 80%");
+ *
+ *
+ * 은행 계좌를 통한 직접 이체 결제를 처리합니다. + * 실무에서는 오픈뱅킹 API를 사용하여 실시간 계좌이체를 진행합니다. + */ +public class BankTransferPayment implements PaymentStrategy { + + private final String bankName; + private final String accountNumber; + private final String accountHolderName; + + /** + * 생성자. + * + * @param bankName 은행명 + * @param accountNumber 계좌번호 + * @param accountHolderName 예금주명 + */ + public BankTransferPayment(String bankName, String accountNumber, String accountHolderName) { + this.bankName = bankName; + this.accountNumber = accountNumber; + this.accountHolderName = accountHolderName; + } + + @Override + public PaymentResult pay(double amount) { + System.out.println("Processing bank transfer payment..."); + System.out.println("Bank: " + bankName); + System.out.println("Account: " + maskAccountNumber(accountNumber)); + System.out.println("Holder: " + accountHolderName); + System.out.println("Amount: $" + amount); + + // 계좌 유효성 검증 + if (!validateAccount()) { + return PaymentResult.failure("Invalid account information"); + } + + // 잔액 확인 시뮬레이션 + if (!checkAccountBalance(amount)) { + return PaymentResult.failure("Insufficient account balance"); + } + + // 이체 처리 시뮬레이션 + String transactionId = "BT-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + System.out.println("Transfer successful! Transaction ID: " + transactionId); + + return PaymentResult.success("Bank transfer completed", transactionId); + } + + @Override + public String getPaymentMethodName() { + return "Bank Transfer (" + bankName + ")"; + } + + @Override + public boolean canProcess(double amount) { + return amount > 0 && amount <= 100000.0; // 최대 한도 100,000 + } + + /** + * 계좌 정보 유효성 검증. + * + * @return 유효하면 true + */ + private boolean validateAccount() { + return bankName != null && !bankName.isEmpty() + && accountNumber != null && accountNumber.length() >= 10 + && accountHolderName != null && !accountHolderName.isEmpty(); + } + + /** + * 계좌 잔액 확인 (시뮬레이션). + * + * @param amount 이체 금액 + * @return 잔액이 충분하면 true + */ + private boolean checkAccountBalance(double amount) { + // 실제로는 은행 API를 통해 확인 + return amount <= 50000.0; // 시뮬레이션: 50,000까지 이체 가능 + } + + /** + * 계좌번호 마스킹. + * + * @param accountNumber 계좌번호 + * @return 마스킹된 계좌번호 + */ + private String maskAccountNumber(String accountNumber) { + if (accountNumber == null || accountNumber.length() < 4) { + return "****"; + } + return "****-****-" + accountNumber.substring(accountNumber.length() - 4); + } + + public String getBankName() { + return bankName; + } + + public String getAccountNumber() { + return accountNumber; + } + + public String getAccountHolderName() { + return accountHolderName; + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/payment/CreditCardPayment.java b/strategy/src/main/java/com/iluwatar/strategy/payment/CreditCardPayment.java new file mode 100644 index 000000000000..b275d6462c51 --- /dev/null +++ b/strategy/src/main/java/com/iluwatar/strategy/payment/CreditCardPayment.java @@ -0,0 +1,120 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.strategy.payment; + +import java.util.UUID; + +/** + * 신용카드 결제 전략 구현. + * + *
신용카드를 통한 결제를 처리합니다. + * 실무에서는 PG사 API를 호출하여 실제 결제를 진행합니다. + */ +public class CreditCardPayment implements PaymentStrategy { + + private final String cardNumber; + private final String cardHolderName; + private final String cvv; + private final String expiryDate; + + /** + * 생성자. + * + * @param cardNumber 카드 번호 + * @param cardHolderName 카드 소유자명 + * @param cvv CVV 번호 + * @param expiryDate 만료일 (MM/YY) + */ + public CreditCardPayment(String cardNumber, String cardHolderName, + String cvv, String expiryDate) { + this.cardNumber = cardNumber; + this.cardHolderName = cardHolderName; + this.cvv = cvv; + this.expiryDate = expiryDate; + } + + @Override + public PaymentResult pay(double amount) { + System.out.println("Processing credit card payment..."); + System.out.println("Card: **** **** **** " + cardNumber.substring(cardNumber.length() - 4)); + System.out.println("Amount: $" + amount); + + // 카드 유효성 검증 + if (!validateCard()) { + return PaymentResult.failure("Invalid card information"); + } + + // 잔액 확인 시뮬레이션 + if (!checkBalance(amount)) { + return PaymentResult.failure("Insufficient credit limit"); + } + + // 결제 처리 시뮬레이션 + String transactionId = "CC-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + System.out.println("Payment successful! Transaction ID: " + transactionId); + + return PaymentResult.success("Credit card payment completed", transactionId); + } + + @Override + public String getPaymentMethodName() { + return "Credit Card"; + } + + @Override + public boolean canProcess(double amount) { + return amount > 0 && amount <= 10000.0; // 최대 한도 10,000 + } + + /** + * 카드 정보 유효성 검증. + * + * @return 유효하면 true + */ + private boolean validateCard() { + // 간단한 검증 로직 + return cardNumber != null && cardNumber.length() >= 13 + && cvv != null && cvv.length() == 3 + && expiryDate != null && expiryDate.matches("\\d{2}/\\d{2}"); + } + + /** + * 잔액 확인 (시뮬레이션). + * + * @param amount 결제 금액 + * @return 잔액이 충분하면 true + */ + private boolean checkBalance(double amount) { + // 실제로는 카드사 API를 통해 확인 + return amount <= 5000.0; // 시뮬레이션: 5000까지 사용 가능 + } + + public String getCardNumber() { + return cardNumber; + } + + public String getCardHolderName() { + return cardHolderName; + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/payment/PayPalPayment.java b/strategy/src/main/java/com/iluwatar/strategy/payment/PayPalPayment.java new file mode 100644 index 000000000000..6a611b4d130f --- /dev/null +++ b/strategy/src/main/java/com/iluwatar/strategy/payment/PayPalPayment.java @@ -0,0 +1,124 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.strategy.payment; + +import java.util.UUID; + +/** + * PayPal 결제 전략 구현. + * + *
PayPal 계정을 통한 결제를 처리합니다. + * 실무에서는 PayPal API를 사용하여 OAuth 인증 후 결제를 진행합니다. + */ +public class PayPalPayment implements PaymentStrategy { + + private final String email; + private final String password; + + /** + * 생성자. + * + * @param email PayPal 이메일 + * @param password PayPal 비밀번호 + */ + public PayPalPayment(String email, String password) { + this.email = email; + this.password = password; + } + + @Override + public PaymentResult pay(double amount) { + System.out.println("Processing PayPal payment..."); + System.out.println("Email: " + maskEmail(email)); + System.out.println("Amount: $" + amount); + + // 로그인 검증 + if (!authenticate()) { + return PaymentResult.failure("PayPal authentication failed"); + } + + // 잔액 확인 시뮬레이션 + if (!checkPayPalBalance(amount)) { + return PaymentResult.failure("Insufficient PayPal balance"); + } + + // 결제 처리 시뮬레이션 + String transactionId = "PP-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + System.out.println("PayPal payment successful! Transaction ID: " + transactionId); + + return PaymentResult.success("PayPal payment completed", transactionId); + } + + @Override + public String getPaymentMethodName() { + return "PayPal"; + } + + @Override + public boolean canProcess(double amount) { + return amount > 0 && amount <= 25000.0; // 최대 한도 25,000 + } + + /** + * PayPal 인증 (시뮬레이션). + * + * @return 인증 성공하면 true + */ + private boolean authenticate() { + // 실제로는 PayPal OAuth API를 사용 + return email != null && email.contains("@") + && password != null && password.length() >= 6; + } + + /** + * PayPal 잔액 확인 (시뮬레이션). + * + * @param amount 결제 금액 + * @return 잔액이 충분하면 true + */ + private boolean checkPayPalBalance(double amount) { + // 실제로는 PayPal API를 통해 확인 + return amount <= 10000.0; // 시뮬레이션: 10,000까지 사용 가능 + } + + /** + * 이메일 마스킹. + * + * @param email 이메일 + * @return 마스킹된 이메일 + */ + private String maskEmail(String email) { + if (email == null || !email.contains("@")) { + return "***@***.com"; + } + String[] parts = email.split("@"); + String username = parts[0]; + String masked = username.substring(0, Math.min(3, username.length())) + "***"; + return masked + "@" + parts[1]; + } + + public String getEmail() { + return email; + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/payment/PaymentProcessor.java b/strategy/src/main/java/com/iluwatar/strategy/payment/PaymentProcessor.java new file mode 100644 index 000000000000..c1bee493e2d7 --- /dev/null +++ b/strategy/src/main/java/com/iluwatar/strategy/payment/PaymentProcessor.java @@ -0,0 +1,150 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.strategy.payment; + +/** + * 실무 예제: 결제 처리 시스템 (Strategy Pattern Context). + * + *
PaymentProcessor는 다양한 결제 전략을 사용하여 결제를 처리합니다. + * 런타임에 결제 방법을 변경할 수 있어 유연한 결제 시스템을 구현할 수 있습니다. + * + *
+ * // 신용카드 결제
+ * PaymentStrategy creditCard = new CreditCardPayment(
+ * "1234-5678-9012-3456", "John Doe", "123", "12/25"
+ * );
+ * PaymentProcessor processor = new PaymentProcessor(creditCard);
+ * PaymentResult result = processor.processPayment(100.0);
+ *
+ * // 런타임에 결제 방법 변경
+ * processor.setPaymentStrategy(new PayPalPayment("user@example.com", "password"));
+ * result = processor.processPayment(200.0);
+ *
+ *
+ * Strategy 패턴의 핵심 인터페이스로, 다양한 결제 방법을 추상화합니다. + * 각 결제 수단은 이 인터페이스를 구현하여 동일한 방식으로 처리될 수 있습니다. + */ +public interface PaymentStrategy { + + /** + * 결제 처리. + * + * @param amount 결제 금액 + * @return 결제 성공 여부 + */ + PaymentResult pay(double amount); + + /** + * 결제 수단 이름 조회. + * + * @return 결제 수단 이름 + */ + String getPaymentMethodName(); + + /** + * 결제 가능 여부 확인. + * + * @param amount 결제 금액 + * @return 결제 가능하면 true + */ + boolean canProcess(double amount); +} diff --git a/strategy/src/test/java/com/iluwatar/strategy/payment/PaymentProcessorTest.java b/strategy/src/test/java/com/iluwatar/strategy/payment/PaymentProcessorTest.java new file mode 100644 index 000000000000..f1ebe32f19d3 --- /dev/null +++ b/strategy/src/test/java/com/iluwatar/strategy/payment/PaymentProcessorTest.java @@ -0,0 +1,208 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.strategy.payment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * PaymentProcessor 테스트. + */ +class PaymentProcessorTest { + + @Test + void testCreditCardPaymentSuccess() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When + PaymentResult result = processor.processPayment(1000.0); + + // Then + assertTrue(result.isSuccess()); + assertNotNull(result.getTransactionId()); + assertTrue(result.getTransactionId().startsWith("CC-")); + } + + @Test + void testCreditCardPaymentInsufficientLimit() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When + PaymentResult result = processor.processPayment(6000.0); + + // Then + assertFalse(result.isSuccess()); + assertEquals("Insufficient credit limit", result.getMessage()); + } + + @Test + void testBankTransferPaymentSuccess() { + // Given + PaymentStrategy bankTransfer = new BankTransferPayment( + "KB Bank", "1234567890", "John Doe" + ); + PaymentProcessor processor = new PaymentProcessor(bankTransfer); + + // When + PaymentResult result = processor.processPayment(10000.0); + + // Then + assertTrue(result.isSuccess()); + assertNotNull(result.getTransactionId()); + assertTrue(result.getTransactionId().startsWith("BT-")); + } + + @Test + void testPayPalPaymentSuccess() { + // Given + PaymentStrategy paypal = new PayPalPayment("user@example.com", "password123"); + PaymentProcessor processor = new PaymentProcessor(paypal); + + // When + PaymentResult result = processor.processPayment(500.0); + + // Then + assertTrue(result.isSuccess()); + assertNotNull(result.getTransactionId()); + assertTrue(result.getTransactionId().startsWith("PP-")); + } + + @Test + void testSwitchPaymentStrategy() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When - 신용카드로 결제 + PaymentResult result1 = processor.processPayment(1000.0); + + // Then + assertTrue(result1.isSuccess()); + assertEquals("Credit Card", processor.getCurrentPaymentMethod()); + + // When - PayPal로 변경 + PaymentStrategy paypal = new PayPalPayment("user@example.com", "password123"); + processor.setPaymentStrategy(paypal); + PaymentResult result2 = processor.processPayment(500.0); + + // Then + assertTrue(result2.isSuccess()); + assertEquals("PayPal", processor.getCurrentPaymentMethod()); + } + + @Test + void testInvalidPaymentAmount() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When + PaymentResult result = processor.processPayment(-100.0); + + // Then + assertFalse(result.isSuccess()); + assertEquals("Invalid payment amount", result.getMessage()); + } + + @Test + void testPaymentExceedsLimit() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When + PaymentResult result = processor.processPayment(15000.0); + + // Then + assertFalse(result.isSuccess()); + assertTrue(result.getMessage().contains("exceeds limit")); + } + + @Test + void testCanProcessPayment() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When & Then + assertTrue(processor.canProcessPayment(1000.0)); + assertTrue(processor.canProcessPayment(5000.0)); + assertFalse(processor.canProcessPayment(15000.0)); + } + + @Test + void testDifferentPaymentMethods() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentStrategy bankTransfer = new BankTransferPayment( + "KB Bank", "1234567890", "John Doe" + ); + PaymentStrategy paypal = new PayPalPayment("user@example.com", "password123"); + + // When & Then + assertEquals("Credit Card", creditCard.getPaymentMethodName()); + assertEquals("Bank Transfer (KB Bank)", bankTransfer.getPaymentMethodName()); + assertEquals("PayPal", paypal.getPaymentMethodName()); + } + + @Test + void testPaymentResultFields() { + // Given + PaymentStrategy creditCard = new CreditCardPayment( + "1234567890123456", "John Doe", "123", "12/25" + ); + PaymentProcessor processor = new PaymentProcessor(creditCard); + + // When + PaymentResult result = processor.processPayment(1000.0); + + // Then + assertTrue(result.isSuccess()); + assertNotNull(result.getMessage()); + assertNotNull(result.getTransactionId()); + assertNotNull(result.getTimestamp()); + } +}