Home > Troubleshooting > πŸ”[Troubleshooting] OOP와 SOLID 관계.

πŸ”[Troubleshooting] OOP와 SOLID 관계.
Troubleshooting Backend Development Spring Boot OOP SOLID

πŸš€ OOP와 SOLID의 관계 Troubleshooting !

핡심 질문: OOPλ©΄ μΆ©λΆ„ν•œλ°, μ™œ SOLIDκ°€ ν•„μš”ν• κΉŒ?

πŸ€” 자주 ν•˜λŠ” 착각

// ❌ "객체지ν–₯으둜 μ„€κ³„ν–ˆμœΌλ‹ˆ 끝 μ•„λ‹Œκ°€?"
@Service
public class OrderService {
    private ProductRepository productRepo;
    private UserRepository userRepo;
    private PaymentService paymentService;
    private EmailService emailService;
    private SmsService smsService;
    
    public void processOrder(OrderRequest request) {
        // μƒν’ˆ 쑰회, 결제, 이메일, SMS λ“± λͺ¨λ“  κ±Έ ν•œ λ²ˆμ—...
        // 200μ€„μ˜ λ³΅μž‘ν•œ 둜직
    }
}
// βœ… "OOP + SOLID 원칙을 μ μš©ν•˜λ©΄?"
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderValidator orderValidator;
    private final PaymentProcessor paymentProcessor;
    private final NotificationSender notificationSender;
    
    public OrderResult processOrder(OrderRequest request) {
        orderValidator.validate(request);
        PaymentResult payment = paymentProcessor.process(request);
        notificationSender.sendConfirmation(request, payment);
        return OrderResult.from(request, payment);
    }
}

β€œOOP둜 클래슀λ₯Ό λ‚˜λˆ„κ³  상속을 μ“°λ©΄ λ˜λŠ” κ±° μ•„λ‹Œκ°€μš”? μ™œ SOLIDκ°€ 또 ν•„μš”ν•˜μ£ ?”

μ΄λŠ” λ§Žμ€ κ°œλ°œμžλ“€μ΄ κ°€μ§€λŠ” μžμ—°μŠ€λŸ¬μš΄ μ˜λ¬Έμž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ OOPλ§ŒμœΌλ‘œλŠ” κ±΄κ°•ν•œ 섀계λ₯Ό 보μž₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€.


πŸ—οΈ μ •λ‹΅: OOPλŠ” ν† λŒ€, SOLIDλŠ” κ±΄κ°•ν•œ 섀계 κ°€μ΄λ“œ

🏒 건물 건좕 λΉ„μœ λ‘œ μ΄ν•΄ν•˜κΈ°

κ°œλ… 건좕 λΉ„μœ  개발 λΉ„μœ  μ—­ν• 
OOP πŸ—οΈ 건좕 ꡬ쑰 (κΈ°λ‘₯, 보, λ²½) 클래슀, 객체, 상속, μΊ‘μŠν™” κΈ°λ³Έ ν† λŒ€
SOLID πŸ”§ 건좕 κ·œλ²” (내진섀계, μ•ˆμ „κ·œμ •) λ‹¨μΌμ±…μž„μ›μΉ™(SRP), 개방-폐쇄원칙(OCP), λ¦¬μŠ€μ½”ν”„, μΈν„°νŽ˜μ΄μŠ€λΆ„λ¦¬, μ˜μ‘΄μ—­μ „ ν’ˆμ§ˆ κ°€μ΄λ“œλΌμΈ

μ™œ 이 λΉ„μœ κ°€ μ€‘μš”ν•œκ°€?

  • κΈ°λ‘₯κ³Ό λ²½λ§ŒμœΌλ‘œλŠ” 건물이 λ¬΄λ„ˆμ§ˆ 수 있음 (OOPλ§ŒμœΌλ‘œλŠ” λΆ€μ‘±)
  • 건좕 κ·œλ²”μ΄ μžˆμ–΄μ•Ό μ•ˆμ „ν•˜κ³  μ˜€λž˜κ°€λŠ” 건물이 됨 (SOLID ν•„μš”)
  • λ‘˜ λ‹€ ν•„μš”ν•˜μ§€λ§Œ 역할이 닀름

πŸ’‘ OOP vs OOP+SOLID μ‹€μ œ 비ꡐ

μ‹œλ‚˜λ¦¬μ˜€: μ£Όλ¬Έ 처리 μ‹œμŠ€ν…œ

🚨 OOP만 μ μš©ν•œ 경우

@Service
public class OrderService {
    
    // λͺ¨λ“  μ˜μ‘΄μ„±μ„ 직접 μ°Έμ‘°
    private ProductRepository productRepository;
    private UserRepository userRepository;
    private PaymentGateway creditCardGateway;  // μ‹ μš©μΉ΄λ“œλ§Œ 지원
    private EmailSender emailSender;          // μ΄λ©”μΌλ§Œ 지원
    
    public void processOrder(Long userId, Long productId) {
        // ❌ ν•œ λ©”μ„œλ“œμ—μ„œ λͺ¨λ“  것을 처리
        User user = userRepository.findById(userId);
        Product product = productRepository.findById(productId);
        
        // 재고 확인
        if (product.getStock() < 1) {
            throw new OutOfStockException();
        }
        
        // μ‹ μš©μΉ΄λ“œ 결제만 κ°€λŠ₯
        creditCardGateway.charge(user.getCreditCard(), product.getPrice());
        
        // 재고 κ°μ†Œ
        product.decreaseStock(1);
        productRepository.save(product);
        
        // 이메일 λ°œμ†‘λ§Œ κ°€λŠ₯
        emailSender.send(user.getEmail(), "μ£Όλ¬Έ μ™„λ£Œ", "주문이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
        
        // μ£Όλ¬Έ μ €μž₯
        Order order = new Order(userId, productId, LocalDateTime.now());
        // ... μ €μž₯ 둜직
    }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • 결제 방식 μΆ”κ°€ μ‹œ β†’ 전체 μ½”λ“œ μˆ˜μ • ν•„μš”
  • SMS μ•Œλ¦Ό μΆ”κ°€ μ‹œ β†’ 또 λ‹€μ‹œ 전체 μˆ˜μ •
  • ν…ŒμŠ€νŠΈν•˜κΈ° 어렀움 β†’ μ‹€μ œ κ²°μ œκΉŒμ§€ ν…ŒμŠ€νŠΈ

βœ… OOP + SOLID μ μš©ν•œ 경우

// 1️⃣ SRP: 각각의 μ±…μž„μ„ 뢄리
@Component
public class OrderValidator {
    public void validate(OrderRequest request) {
        // μ£Όλ¬Έ μœ νš¨μ„± κ²€μ¦λ§Œ λ‹΄λ‹Ή
    }
}

@Component  
public class StockManager {
    public void reserveStock(String productId, int quantity) {
        // 재고 κ΄€λ¦¬λ§Œ λ‹΄λ‹Ή
    }
}

// 2️⃣ DIP: μΈν„°νŽ˜μ΄μŠ€μ— 의쑴
public interface PaymentProcessor {
    PaymentResult process(PaymentRequest request);
}

public interface NotificationSender {
    void sendOrderConfirmation(OrderConfirmation confirmation);
}

// 3️⃣ OCP: ν™•μž₯에 μ—΄λ €μžˆκ³  μˆ˜μ •μ— λ‹«ν˜€μžˆμŒ
@Service
@RequiredArgsConstructor
public class OrderService {
    
    private final OrderValidator orderValidator;
    private final StockManager stockManager;
    private final PaymentProcessor paymentProcessor;  // κ΅¬ν˜„μ²΄μ— μ˜μ‘΄ν•˜μ§€ μ•ŠμŒ
    private final NotificationSender notificationSender;  // ν™•μž₯ κ°€λŠ₯
    
    public OrderResult processOrder(OrderRequest request) {
        // 각 λ‹¨κ³„λ³„λ‘œ λͺ…ν™•ν•˜κ²Œ 뢄리
        orderValidator.validate(request);
        stockManager.reserveStock(request.getProductId(), request.getQuantity());
        PaymentResult payment = paymentProcessor.process(request.toPaymentRequest());
        notificationSender.sendOrderConfirmation(OrderConfirmation.from(request, payment));
        
        return OrderResult.success(request, payment);
    }
}

πŸŽ‰ μž₯점듀:

  • 카카였페이 μΆ”κ°€ β†’ κ΅¬ν˜„μ²΄λ§Œ μΆ”κ°€, κΈ°μ‘΄ μ½”λ“œ 무변경
  • SMS μ•Œλ¦Ό μΆ”κ°€ β†’ NotificationSender κ΅¬ν˜„μ²΄λ§Œ μΆ”κ°€
  • ν…ŒμŠ€νŠΈ 용이 β†’ Mock 객체둜 독립적 ν…ŒμŠ€νŠΈ

🎯 SOLID 각 μ›μΉ™μ˜ μ‹€μ œ 효과

1. 🎯 SRP (Single Responsibility Principle) - 단일 μ±…μž„ 원칙

β€œν•˜λ‚˜μ˜ ν΄λž˜μŠ€λŠ” ν•˜λ‚˜μ˜ μ±…μž„λ§Œ κ°€μ Έμ•Ό ν•œλ‹€β€

❌ SRP μœ„λ°˜ 사둀

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired 
    private EmailService emailService;
    @Autowired
    private AuditLogger auditLogger;
    
    // μ‚¬μš©μž 생성 (CRUD μ±…μž„)
    public User createUser(UserCreateRequest request) {
        // λΉ„λ°€λ²ˆν˜Έ 검증 둜직 (검증 μ±…μž„)
        if (request.getPassword().length() < 8) {
            throw new WeakPasswordException();
        }
        if (!request.getPassword().matches(".*[A-Z].*")) {
            throw new WeakPasswordException();
        }
        
        User user = User.builder()
                .email(request.getEmail())
                .password(encryptPassword(request.getPassword()))
                .build();
                
        User savedUser = userRepository.save(user);
        
        // ν™˜μ˜ 이메일 λ°œμ†‘ (이메일 μ±…μž„)
        emailService.sendWelcomeEmail(savedUser.getEmail(), savedUser.getName());
        
        // 감사 둜그 기둝 (λ‘œκΉ… μ±…μž„)
        auditLogger.logUserCreation(savedUser.getId(), LocalDateTime.now());
        
        return savedUser;
    }
    
    // λΉ„λ°€λ²ˆν˜Έ μ•”ν˜Έν™” (μ•”ν˜Έν™” μ±…μž„)
    private String encryptPassword(String password) {
        // λ³΅μž‘ν•œ μ•”ν˜Έν™” 둜직...
        return BCrypt.hashpw(password, BCrypt.gensalt());
    }
    
    // μ‚¬μš©μž 쑰회 (CRUD μ±…μž„)
    public User findUser(Long id) { ... }
    
    // λΉ„λ°€λ²ˆν˜Έ λ³€κ²½ μ‹œ 이메일 μ•Œλ¦Ό (이메일 + CRUD μ±…μž„)
    public void changePassword(Long userId, String newPassword) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException());
        
        // λΉ„λ°€λ²ˆν˜Έ 검증 (검증 μ±…μž„)
        validatePassword(newPassword);
        
        user.setPassword(encryptPassword(newPassword));
        userRepository.save(user);
        
        // 이메일 μ•Œλ¦Ό (이메일 μ±…μž„)
        emailService.sendPasswordChangeNotification(user.getEmail());
        
        // 감사 둜그 (λ‘œκΉ… μ±…μž„)
        auditLogger.logPasswordChange(userId, LocalDateTime.now());
    }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • μ—¬λŸ¬ 이유둜 변경됨: λΉ„λ°€λ²ˆν˜Έ μ •μ±… λ³€κ²½, 이메일 ν…œν”Œλ¦Ώ λ³€κ²½, λ‘œκΉ… 방식 λ³€κ²½
  • ν…ŒμŠ€νŠΈ 어렀움: ν•˜λ‚˜μ˜ λ©”μ„œλ“œ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ λͺ¨λ“  μ˜μ‘΄μ„± ν•„μš”
  • μž¬μ‚¬μš© λΆˆκ°€: λ‹€λ₯Έ κ³³μ—μ„œ λΉ„λ°€λ²ˆν˜Έ 검증 λ‘œμ§μ„ μ“Έ 수 μ—†μŒ

βœ… SRP 적용 ν›„

// 1. λΉ„λ°€λ²ˆν˜Έ 검증 μ±…μž„ 뢄리
@Component
public class PasswordValidator {
    private static final int MIN_LENGTH = 8;
    private static final String UPPERCASE_PATTERN = ".*[A-Z].*";
    private static final String LOWERCASE_PATTERN = ".*[a-z].*";
    private static final String DIGIT_PATTERN = ".*[0-9].*";
    private static final String SPECIAL_CHAR_PATTERN = ".*[!@#$%^&*].*";
    
    public void validate(String password) {
        if (password == null || password.length() < MIN_LENGTH) {
            throw new WeakPasswordException("λΉ„λ°€λ²ˆν˜ΈλŠ” μ΅œμ†Œ " + MIN_LENGTH + "자 이상이어야 ν•©λ‹ˆλ‹€");
        }
        
        if (!password.matches(UPPERCASE_PATTERN)) {
            throw new WeakPasswordException("λŒ€λ¬Έμžλ₯Ό 포함해야 ν•©λ‹ˆλ‹€");
        }
        
        if (!password.matches(LOWERCASE_PATTERN)) {
            throw new WeakPasswordException("μ†Œλ¬Έμžλ₯Ό 포함해야 ν•©λ‹ˆλ‹€");
        }
        
        if (!password.matches(DIGIT_PATTERN)) {
            throw new WeakPasswordException("숫자λ₯Ό 포함해야 ν•©λ‹ˆλ‹€");
        }
        
        if (!password.matches(SPECIAL_CHAR_PATTERN)) {
            throw new WeakPasswordException("특수문자λ₯Ό 포함해야 ν•©λ‹ˆλ‹€");
        }
    }
}

// 2. λΉ„λ°€λ²ˆν˜Έ μ•”ν˜Έν™” μ±…μž„ 뢄리
@Component
public class PasswordEncoder {
    public String encode(String rawPassword) {
        return BCrypt.hashpw(rawPassword, BCrypt.gensalt(12));
    }
    
    public boolean matches(String rawPassword, String encodedPassword) {
        return BCrypt.checkpw(rawPassword, encodedPassword);
    }
}

// 3. μ‚¬μš©μž 이벀트 λ°œν–‰ μ±…μž„ 뢄리
@Component
@RequiredArgsConstructor
public class UserEventPublisher {
    private final ApplicationEventPublisher eventPublisher;
    
    public void publishUserCreated(User user) {
        eventPublisher.publishEvent(new UserCreatedEvent(user));
    }
    
    public void publishPasswordChanged(Long userId) {
        eventPublisher.publishEvent(new PasswordChangedEvent(userId));
    }
}

// 4. μ‚¬μš©μž 도메인 μ„œλΉ„μŠ€ - 였직 μ‚¬μš©μž CRUD만 λ‹΄λ‹Ή
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final PasswordValidator passwordValidator;
    private final PasswordEncoder passwordEncoder;
    private final UserEventPublisher eventPublisher;
    
    @Transactional
    public User createUser(UserCreateRequest request) {
        passwordValidator.validate(request.getPassword());
        
        User user = User.builder()
                .email(request.getEmail())
                .password(passwordEncoder.encode(request.getPassword()))
                .name(request.getName())
                .build();
                
        User savedUser = userRepository.save(user);
        eventPublisher.publishUserCreated(savedUser);
        
        return savedUser;
    }
    
    @Transactional
    public void changePassword(Long userId, String newPassword) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException());
                
        passwordValidator.validate(newPassword);
        user.changePassword(passwordEncoder.encode(newPassword));
        
        userRepository.save(user);
        eventPublisher.publishPasswordChanged(userId);
    }
    
    @Transactional(readOnly = true)
    public User findUser(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException());
    }
}

// 5. 이벀트 ν•Έλ“€λŸ¬λ‘œ λΆ€μˆ˜ 효과 처리
@Component
@RequiredArgsConstructor
@EventListener
public class UserEventHandler {
    private final EmailService emailService;
    private final AuditLogger auditLogger;
    
    @EventListener
    @Async
    public void handleUserCreated(UserCreatedEvent event) {
        User user = event.getUser();
        
        // ν™˜μ˜ 이메일 λ°œμ†‘
        emailService.sendWelcomeEmail(user.getEmail(), user.getName());
        
        // 감사 둜그 기둝
        auditLogger.logUserCreation(user.getId(), LocalDateTime.now());
    }
    
    @EventListener
    @Async
    public void handlePasswordChanged(PasswordChangedEvent event) {
        User user = userRepository.findById(event.getUserId())
                .orElseThrow(() -> new UserNotFoundException());
                
        // λΉ„λ°€λ²ˆν˜Έ λ³€κ²½ μ•Œλ¦Ό
        emailService.sendPasswordChangeNotification(user.getEmail());
        
        // 감사 둜그 기둝
        auditLogger.logPasswordChange(event.getUserId(), LocalDateTime.now());
    }
}

πŸŽ‰ SRP 적용 ν›„ μž₯점듀:

  • λͺ…ν™•ν•œ μ±…μž„: 각 클래슀의 역할이 λͺ…확함
  • μž¬μ‚¬μš© κ°€λŠ₯: PasswordValidatorλ₯Ό λ‹€λ₯Έ κ³³μ—μ„œλ„ μ‚¬μš© κ°€λŠ₯
  • ν…ŒμŠ€νŠΈ 용이: 각 μ»΄ν¬λ„ŒνŠΈλ₯Ό λ…λ¦½μ μœΌλ‘œ ν…ŒμŠ€νŠΈ
  • λ³€κ²½ 영ν–₯ μ΅œμ†Œν™”: λΉ„λ°€λ²ˆν˜Έ μ •μ±… λ³€κ²½ μ‹œ PasswordValidator만 μˆ˜μ •

2. πŸšͺ OCP (Open-Closed Principle) - 개방-폐쇄 원칙

β€œν™•μž₯μ—λŠ” μ—΄λ €μžˆκ³ , μˆ˜μ •μ—λŠ” λ‹«ν˜€μžˆμ–΄μ•Ό ν•œλ‹€β€

❌ OCP μœ„λ°˜ 사둀

@Service
@RequiredArgsConstructor
public class PaymentService {
    
    // 결제 방식이 좔가될 λ•Œλ§ˆλ‹€ 이 λ©”μ„œλ“œλ₯Ό μˆ˜μ •ν•΄μ•Ό 함
    public PaymentResult processPayment(PaymentRequest request) {
        switch (request.getPaymentType()) {
            case CREDIT_CARD:
                return processCreditCard(request);
            case BANK_TRANSFER:
                return processBankTransfer(request);
            case PAYPAL:
                return processPaypal(request);
            // 카카였페이 μΆ”κ°€ μ‹œ -> 이 λ©”μ„œλ“œλ₯Ό μˆ˜μ •ν•΄μ•Ό 함!
            case KAKAO_PAY:
                return processKakaoPay(request);
            // λ„€μ΄λ²„νŽ˜μ΄ μΆ”κ°€ μ‹œ -> 또 이 λ©”μ„œλ“œλ₯Ό μˆ˜μ •ν•΄μ•Ό 함!
            case NAVER_PAY:
                return processNaverPay(request);
            default:
                throw new UnsupportedPaymentTypeException();
        }
    }
    
    private PaymentResult processCreditCard(PaymentRequest request) {
        // μ‹ μš©μΉ΄λ“œ 결제 둜직
        CreditCardGateway gateway = new CreditCardGateway();
        return gateway.charge(request.getAmount(), request.getCreditCardInfo());
    }
    
    private PaymentResult processBankTransfer(PaymentRequest request) {
        // κ³„μ’Œμ΄μ²΄ 둜직
        BankTransferGateway gateway = new BankTransferGateway();
        return gateway.transfer(request.getAmount(), request.getBankInfo());
    }
    
    // μƒˆ 결제 λ°©μ‹λ§ˆλ‹€ λ©”μ„œλ“œκ°€ 계속 좔가됨...
    private PaymentResult processPaypal(PaymentRequest request) { ... }
    private PaymentResult processKakaoPay(PaymentRequest request) { ... }
    private PaymentResult processNaverPay(PaymentRequest request) { ... }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • ν•„μš”: μƒˆ 결제 λ°©μ‹λ§ˆλ‹€ processPayment λ©”μ„œλ“œ μˆ˜μ •
  • 클래슀 크기 증가: 결제 방식이 λŠ˜μ–΄λ‚ μˆ˜λ‘ ν΄λž˜μŠ€κ°€ κ±°λŒ€ν•΄μ§
  • ν…ŒμŠ€νŠΈ 영ν–₯: μƒˆ 결제 방식 μΆ”κ°€ μ‹œ κΈ°μ‘΄ ν…ŒμŠ€νŠΈλ„ μˆ˜μ • ν•„μš”

βœ… OCP 적용 ν›„

// 1. 결제 처리기 μΈν„°νŽ˜μ΄μŠ€ μ •μ˜
public interface PaymentProcessor {
    PaymentResult process(PaymentRequest request);
    boolean supports(PaymentType paymentType);
}

// 2. 각 결제 방식별 κ΅¬ν˜„μ²΄
@Component
public class CreditCardProcessor implements PaymentProcessor {
    
    @Override
    public PaymentResult process(PaymentRequest request) {
        CreditCardGateway gateway = new CreditCardGateway();
        
        try {
            CreditCardPaymentResult result = gateway.charge(
                request.getAmount(), 
                request.getCreditCardInfo()
            );
            
            return PaymentResult.builder()
                    .transactionId(result.getTransactionId())
                    .status(PaymentStatus.SUCCESS)
                    .amount(request.getAmount())
                    .paymentType(PaymentType.CREDIT_CARD)
                    .processedAt(LocalDateTime.now())
                    .build();
                    
        } catch (CreditCardException e) {
            return PaymentResult.failure(e.getMessage());
        }
    }
    
    @Override
    public boolean supports(PaymentType paymentType) {
        return PaymentType.CREDIT_CARD.equals(paymentType);
    }
}

@Component  
public class BankTransferProcessor implements PaymentProcessor {
    
    @Override
    public PaymentResult process(PaymentRequest request) {
        BankTransferGateway gateway = new BankTransferGateway();
        
        try {
            BankTransferResult result = gateway.transfer(
                request.getAmount(),
                request.getBankInfo()
            );
            
            return PaymentResult.builder()
                    .transactionId(result.getTransactionId())
                    .status(PaymentStatus.SUCCESS)
                    .amount(request.getAmount())
                    .paymentType(PaymentType.BANK_TRANSFER)
                    .processedAt(LocalDateTime.now())
                    .build();
                    
        } catch (BankTransferException e) {
            return PaymentResult.failure(e.getMessage());
        }
    }
    
    @Override
    public boolean supports(PaymentType paymentType) {
        return PaymentType.BANK_TRANSFER.equals(paymentType);
    }
}

// 3. μƒˆλ‘œμš΄ 결제 방식 μΆ”κ°€ - κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • μ—†μŒ!
@Component
public class KakaoPayProcessor implements PaymentProcessor {
    
    @Override
    public PaymentResult process(PaymentRequest request) {
        KakaoPayGateway gateway = new KakaoPayGateway();
        
        try {
            KakaoPayResult result = gateway.pay(
                request.getAmount(),
                request.getKakaoPayInfo()
            );
            
            return PaymentResult.builder()
                    .transactionId(result.getTid())
                    .status(PaymentStatus.SUCCESS)
                    .amount(request.getAmount())
                    .paymentType(PaymentType.KAKAO_PAY)
                    .processedAt(LocalDateTime.now())
                    .build();
                    
        } catch (KakaoPayException e) {
            return PaymentResult.failure(e.getMessage());
        }
    }
    
    @Override
    public boolean supports(PaymentType paymentType) {
        return PaymentType.KAKAO_PAY.equals(paymentType);
    }
}

// 4. 결제 μ„œλΉ„μŠ€ - μˆ˜μ •μ— λ‹«ν˜€μžˆκ³  ν™•μž₯에 μ—΄λ €μžˆμŒ
@Service
@RequiredArgsConstructor
public class PaymentService {
    private final List<PaymentProcessor> paymentProcessors;
    
    public PaymentResult processPayment(PaymentRequest request) {
        PaymentProcessor processor = paymentProcessors.stream()
                .filter(p -> p.supports(request.getPaymentType()))
                .findFirst()
                .orElseThrow(() -> new UnsupportedPaymentTypeException(
                    "μ§€μ›ν•˜μ§€ μ•ŠλŠ” 결제 방식: " + request.getPaymentType()
                ));
                
        return processor.process(request);
    }
}

πŸŽ‰ OCP 적용 ν›„ μž₯점듀:

  • κΈ°μ‘΄ μ½”λ“œ 보호: μƒˆ 결제 방식 μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • λΆˆν•„μš”
  • ν™•μž₯ 용이: PaymentProcessor κ΅¬ν˜„μ²΄λ§Œ μΆ”κ°€ν•˜λ©΄ 됨
  • 독립적 개발: 각 결제 방식을 λ…λ¦½μ μœΌλ‘œ 개발/ν…ŒμŠ€νŠΈ κ°€λŠ₯

3. πŸ”„ LSP (Liskov Substitution Principle) - λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙

β€œλΆ€λͺ¨ 클래슀λ₯Ό μžμ‹ 클래슀둜 μΉ˜ν™˜ν•΄λ„ ν”„λ‘œκ·Έλž¨μ΄ μ˜¬λ°”λ₯΄κ²Œ λ™μž‘ν•΄μ•Ό ν•œλ‹€β€

❌ LSP μœ„λ°˜ 사둀

// κΈ°λ³Έ 할인 μ •μ±…
public class DiscountPolicy {
    public int calculateDiscount(int originalPrice) {
        return (int) (originalPrice * 0.1); // 10% 할인
    }
}

// λ¬Έμ œμžˆλŠ” 상속 - LSP μœ„λ°˜
public class VipDiscountPolicy extends DiscountPolicy {
    @Override
    public int calculateDiscount(int originalPrice) {
        if (originalPrice < 50000) {
            // λΆ€λͺ¨μ™€ λ‹€λ₯Έ μ „μ œμ‘°κ±΄ - LSP μœ„λ°˜!
            throw new IllegalArgumentException("VIP 할인은 5λ§Œμ› 이상뢀터 κ°€λŠ₯ν•©λ‹ˆλ‹€");
        }
        return (int) (originalPrice * 0.2); // 20% 할인
    }
}

// 또 λ‹€λ₯Έ LSP μœ„λ°˜
public class NoDiscountPolicy extends DiscountPolicy {
    @Override
    public int calculateDiscount(int originalPrice) {
        // λΆ€λͺ¨μ™€ λ‹€λ₯Έ 후행쑰건 - 항상 0 λ°˜ν™˜
        return 0; // 할인 μ—†μŒ
    }
}

// ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œ
@Service
public class OrderService {
    
    public OrderResult calculateOrder(List<OrderItem> items, User user) {
        int totalPrice = items.stream()
                .mapToInt(item -> item.getPrice() * item.getQuantity())
                .sum();
                
        DiscountPolicy discountPolicy = getDiscountPolicy(user);
        
        // LSP μœ„λ°˜μœΌλ‘œ μΈν•œ 문제 λ°œμƒ!
        int discount = discountPolicy.calculateDiscount(totalPrice); // μ˜ˆμ™Έ λ°œμƒ κ°€λŠ₯
        
        return new OrderResult(totalPrice, discount, totalPrice - discount);
    }
    
    private DiscountPolicy getDiscountPolicy(User user) {
        if (user.isVip()) {
            return new VipDiscountPolicy(); // 5λ§Œμ› 미만 μ‹œ μ˜ˆμ™Έ λ°œμƒ!
        }
        return new DiscountPolicy();
    }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • μ˜ˆμƒμΉ˜ λͺ»ν•œ μ˜ˆμ™Έ: VIP 할인 μ •μ±…μ—μ„œ κ°‘μžκΈ° μ˜ˆμ™Έ λ°œμƒ
  • 일관성 μ—†μŒ: 같은 μΈν„°νŽ˜μ΄μŠ€μΈλ° λ‹€λ₯Έ λ™μž‘ 방식
  • μΉ˜ν™˜ λΆˆκ°€λŠ₯: λΆ€λͺ¨λ₯Ό μžμ‹μœΌλ‘œ λ°”κΎΈλ©΄ ν”„λ‘œκ·Έλž¨μ΄ 깨짐

βœ… LSP 적용 ν›„

// 할인 μ •μ±… μΈν„°νŽ˜μ΄μŠ€ - λͺ…ν™•ν•œ 계약 μ •μ˜
public interface DiscountPolicy {
    /**
     * μ£Όμ–΄μ§„ 가격에 λŒ€ν•œ 할인 κΈˆμ•‘μ„ κ³„μ‚°ν•©λ‹ˆλ‹€.
     * @param originalPrice μ›λž˜ 가격 (0 이상)
     * @param user μ‚¬μš©μž 정보
     * @return 할인 κΈˆμ•‘ (0 이상)
     * @throws IllegalArgumentException 가격이 0 미만인 경우
     */
    int calculateDiscount(int originalPrice, User user);
    
    /**
     * 이 할인 정책이 적용 κ°€λŠ₯ν•œμ§€ ν™•μΈν•©λ‹ˆλ‹€.
     * @param user μ‚¬μš©μž 정보
     * @param totalPrice 총 μ£Όλ¬Έ κΈˆμ•‘
     * @return 적용 κ°€λŠ₯ μ—¬λΆ€
     */
    boolean isApplicable(User user, int totalPrice);
}

// κΈ°λ³Έ 할인 μ •μ±…
@Component
public class RegularDiscountPolicy implements DiscountPolicy {
    private static final double DISCOUNT_RATE = 0.1;
    
    @Override
    public int calculateDiscount(int originalPrice, User user) {
        if (originalPrice < 0) {
            throw new IllegalArgumentException("가격은 0 이상이어야 ν•©λ‹ˆλ‹€");
        }
        
        if (!isApplicable(user, originalPrice)) {
            return 0;
        }
        
        return (int) (originalPrice * DISCOUNT_RATE);
    }
    
    @Override
    public boolean isApplicable(User user, int totalPrice) {
        return !user.isVip() && totalPrice >= 10000; // 1λ§Œμ› 이상 일반 νšŒμ›
    }
}

// VIP 할인 μ •μ±… - LSP μ€€μˆ˜
@Component  
public class VipDiscountPolicy implements DiscountPolicy {
    private static final double DISCOUNT_RATE = 0.2;
    private static final int MIN_PRICE_FOR_VIP_DISCOUNT = 50000;
    
    @Override
    public int calculateDiscount(int originalPrice, User user) {
        if (originalPrice < 0) {
            throw new IllegalArgumentException("가격은 0 이상이어야 ν•©λ‹ˆλ‹€");
        }
        
        // 적용 λΆˆκ°€λŠ₯ν•œ 경우 μ˜ˆμ™Έκ°€ μ•„λ‹Œ 0 λ°˜ν™˜ - LSP μ€€μˆ˜
        if (!isApplicable(user, originalPrice)) {
            return 0;
        }
        
        return (int) (originalPrice * DISCOUNT_RATE);
    }
    
    @Override
    public boolean isApplicable(User user, int totalPrice) {
        return user.isVip() && totalPrice >= MIN_PRICE_FOR_VIP_DISCOUNT;
    }
}

// 할인 μ—†μŒ μ •μ±…
@Component
public class NoDiscountPolicy implements DiscountPolicy {
    
    @Override
    public int calculateDiscount(int originalPrice, User user) {
        if (originalPrice < 0) {
            throw new IllegalArgumentException("가격은 0 이상이어야 ν•©λ‹ˆλ‹€");
        }
        return 0; // 항상 0 λ°˜ν™˜ - 일관성 μœ μ§€
    }
    
    @Override
    public boolean isApplicable(User user, int totalPrice) {
        return true; // 항상 적용 κ°€λŠ₯ (할인 μ—†μŒμ΄λ―€λ‘œ)
    }
}

// 할인 μ •μ±… κ²°μ •μž
@Service
@RequiredArgsConstructor
public class DiscountPolicyDecider {
    private final List<DiscountPolicy> discountPolicies;
    
    public DiscountPolicy decide(User user, int totalPrice) {
        return discountPolicies.stream()
                .filter(policy -> policy.isApplicable(user, totalPrice))
                .findFirst()
                .orElse(new NoDiscountPolicy());
    }
}

// ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œ - LSP μ€€μˆ˜λ‘œ μ•ˆμ „ν•¨
@Service
@RequiredArgsConstructor
public class OrderService {
    private final DiscountPolicyDecider discountPolicyDecider;
    
    public OrderResult calculateOrder(List<OrderItem> items, User user) {
        int totalPrice = items.stream()
                .mapToInt(item -> item.getPrice() * item.getQuantity())
                .sum();
                
        DiscountPolicy discountPolicy = discountPolicyDecider.decide(user, totalPrice);
        
        // μ–΄λ–€ κ΅¬ν˜„μ²΄λ“  μ•ˆμ „ν•˜κ²Œ μΉ˜ν™˜ κ°€λŠ₯
        int discount = discountPolicy.calculateDiscount(totalPrice, user);
        
        return OrderResult.builder()
                .originalPrice(totalPrice)
                .discountAmount(discount)
                .finalPrice(totalPrice - discount)
                .appliedPolicy(discountPolicy.getClass().getSimpleName())
                .build();
    }
}

πŸŽ‰ LSP 적용 ν›„ μž₯점듀:

  • μ•ˆμ „ν•œ μΉ˜ν™˜: μ–΄λ–€ κ΅¬ν˜„μ²΄λ“  μ˜ˆμ™Έ 없이 λ™μž‘
  • μΌκ΄€λœ 계약: λͺ¨λ“  κ΅¬ν˜„μ²΄κ°€ λ™μΌν•œ μ „μ œ/ν›„ν–‰ 쑰건 μ€€μˆ˜
  • 예츑 κ°€λŠ₯ν•œ λ™μž‘: ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œκ°€ μ•ˆμ „ν•¨

4. 🧩 ISP (Interface Segregation Principle) - μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙

β€œν΄λΌμ΄μ–ΈνŠΈλŠ” μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μΈν„°νŽ˜μ΄μŠ€μ— μ˜μ‘΄ν•˜λ©΄ μ•ˆ λœλ‹€β€

❌ ISP μœ„λ°˜ 사둀

// κ±°λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€ - ISP μœ„λ°˜
public interface UserManager {
    // μ‚¬μš©μž CRUD
    User createUser(UserCreateRequest request);
    User updateUser(Long id, UserUpdateRequest request);
    void deleteUser(Long id);
    User findUser(Long id);
    List<User> findAllUsers();
    
    // 인증 κ΄€λ ¨
    boolean authenticate(String email, String password);
    String generateToken(User user);
    void logout(String token);
    void resetPassword(String email);
    
    // 이메일 κ΄€λ ¨  
    void sendWelcomeEmail(User user);
    void sendPasswordResetEmail(String email);
    void sendPromotionEmail(List<User> users, String content);
    
    // 톡계 κ΄€λ ¨
    int getTotalUserCount();
    List<User> getActiveUsers();
    Map<String, Integer> getUserStatsByRegion();
    
    // κ΄€λ¦¬μž μ „μš©
    void banUser(Long userId, String reason);
    void unbanUser(Long userId);
    List<User> getBannedUsers();
}

// 문제: 이메일 λ°œμ†‘λ§Œ ν•„μš”ν•œ ν΄λΌμ΄μ–ΈνŠΈκ°€ λͺ¨λ“  λ©”μ„œλ“œμ— 의쑴
@Component
public class EmailNotificationService {
    private final UserManager userManager; // κ±°λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€μ— 의쑴
    
    public void sendMonthlyNewsletter() {
        List<User> users = userManager.findAllUsers();
        // μ‹€μ œλ‘œλŠ” sendPromotionEmail만 ν•„μš”ν•˜μ§€λ§Œ 
        // κ±°λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€ 전체에 의쑴
        userManager.sendPromotionEmail(users, "μ›”κ°„ λ‰΄μŠ€λ ˆν„°");
    }
}

// 문제: μ‚¬μš©μž 쑰회만 ν•„μš”ν•œ ν΄λΌμ΄μ–ΈνŠΈκ°€ λΆˆν•„μš”ν•œ μ˜μ‘΄μ„±μ„ 가짐
@Controller
public class UserController {
    private final UserManager userManager; // κ±°λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€μ— 의쑴
    
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // findUser만 ν•„μš”ν•˜μ§€λ§Œ 전체 μΈν„°νŽ˜μ΄μŠ€μ— 의쑴
        User user = userManager.findUser(id);
        return ResponseEntity.ok(user);
    }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • λΆˆν•„μš”ν•œ μ˜μ‘΄μ„±: μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”μ„œλ“œκΉŒμ§€ 의쑴
  • λ³€κ²½ 영ν–₯ ν™•μ‚°: μΈν„°νŽ˜μ΄μŠ€ λ³€κ²½ μ‹œ λͺ¨λ“  ν΄λΌμ΄μ–ΈνŠΈ 영ν–₯
  • κ΅¬ν˜„ λΆ€λ‹΄: κ΅¬ν˜„μ²΄μ—μ„œ λͺ¨λ“  λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό 함

βœ… ISP 적용 ν›„

// 1. μ‚¬μš©μž CRUD μ „μš© μΈν„°νŽ˜μ΄μŠ€
public interface UserRepository {
    User save(User user);
    User findById(Long id);
    List<User> findAll();
    void deleteById(Long id);
    boolean existsById(Long id);
}

// 2. 인증 μ „μš© μΈν„°νŽ˜μ΄μŠ€  
public interface AuthenticationService {
    boolean authenticate(String email, String password);
    String generateToken(User user);
    void logout(String token);
    void resetPassword(String email);
}

// 3. 이메일 μ „μš© μΈν„°νŽ˜μ΄μŠ€
public interface UserEmailService {
    void sendWelcomeEmail(User user);
    void sendPasswordResetEmail(String email);
    void sendPromotionEmail(List<User> users, String content);
}

// 4. μ‚¬μš©μž 톡계 μ „μš© μΈν„°νŽ˜μ΄μŠ€
public interface UserStatisticsService {
    int getTotalUserCount();
    List<User> getActiveUsers();
    Map<String, Integer> getUserStatsByRegion();
    List<User> getUsersRegisteredBetween(LocalDate start, LocalDate end);
}

// 5. κ΄€λ¦¬μž μ „μš© μΈν„°νŽ˜μ΄μŠ€
public interface UserAdminService {
    void banUser(Long userId, String reason);
    void unbanUser(Long userId);
    List<User> getBannedUsers();
    void sendWarningToUser(Long userId, String message);
}

// 6. μ‚¬μš©μž 도메인 μ„œλΉ„μŠ€
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final UserEventPublisher eventPublisher;
    
    @Transactional
    public User createUser(UserCreateRequest request) {
        User user = User.builder()
                .email(request.getEmail())
                .password(passwordEncoder.encode(request.getPassword()))
                .name(request.getName())
                .build();
                
        User savedUser = userRepository.save(user);
        eventPublisher.publishUserCreated(savedUser);
        
        return savedUser;
    }
    
    @Transactional(readOnly = true)
    public User findUser(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException());
    }
}

// 각 ν΄λΌμ΄μ–ΈνŠΈλŠ” ν•„μš”ν•œ μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
@Component
@RequiredArgsConstructor
public class EmailNotificationService {
    private final UserEmailService userEmailService; // 이메일 μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
    private final UserRepository userRepository; // μ‚¬μš©μž 쑰회 μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
    
    public void sendMonthlyNewsletter() {
        List<User> users = userRepository.findAll();
        userEmailService.sendPromotionEmail(users, "μ›”κ°„ λ‰΄μŠ€λ ˆν„°");
    }
}

@Controller
@RequiredArgsConstructor
public class UserController {
    private final UserService userService; // ν•„μš”ν•œ μ„œλΉ„μŠ€λ§Œ 의쑴
    
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findUser(id);
        return ResponseEntity.ok(user);
    }
}

@Controller
@RequiredArgsConstructor
public class AdminController {
    private final UserAdminService adminService; // κ΄€λ¦¬μž μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
    private final UserStatisticsService statsService; // 톡계 μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
    
    @PostMapping("/admin/users/{id}/ban")
    public ResponseEntity<Void> banUser(
            @PathVariable Long id, 
            @RequestBody BanRequest request) {
        adminService.banUser(id, request.getReason());
        return ResponseEntity.ok().build();
    }
    
    @GetMapping("/admin/users/stats")
    public ResponseEntity<UserStats> getUserStats() {
        UserStats stats = UserStats.builder()
                .totalCount(statsService.getTotalUserCount())
                .activeUsers(statsService.getActiveUsers().size())
                .regionStats(statsService.getUserStatsByRegion())
                .build();
        return ResponseEntity.ok(stats);
    }
}

πŸŽ‰ ISP 적용 ν›„ μž₯점듀:

  • ν•„μš”ν•œ μ˜μ‘΄μ„±λ§Œ: 각 ν΄λΌμ΄μ–ΈνŠΈκ°€ μ‹€μ œ μ‚¬μš©ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ§Œ 의쑴
  • λ³€κ²½ 영ν–₯ μ΅œμ†Œν™”: μΈν„°νŽ˜μ΄μŠ€ λ³€κ²½ μ‹œ ν•΄λ‹Ή ν΄λΌμ΄μ–ΈνŠΈλ§Œ 영ν–₯
  • κ΅¬ν˜„ λ‹¨μˆœν™”: 각 μΈν„°νŽ˜μ΄μŠ€λ³„λ‘œ 독립적인 κ΅¬ν˜„μ²΄ μž‘μ„± κ°€λŠ₯

5. ⬇️ DIP (Dependency Inversion Principle) - μ˜μ‘΄κ΄€κ³„ μ—­μ „ 원칙

β€œκ³ μˆ˜μ€€ λͺ¨λ“ˆμ€ μ €μˆ˜μ€€ λͺ¨λ“ˆμ— μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆ λœλ‹€. λ‘˜ λ‹€ 좔상화에 μ˜μ‘΄ν•΄μ•Ό ν•œλ‹€β€

❌ DIP μœ„λ°˜ 사둀

// μ €μˆ˜μ€€ λͺ¨λ“ˆλ“€ (ꡬ체적인 κ΅¬ν˜„)
public class MySqlUserRepository {
    public User save(User user) {
        // MySQL νŠΉν™” μ €μž₯ 둜직
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        // JDBC μ½”λ“œ...
        return user;
    }
    
    public User findById(Long id) {
        // MySQL νŠΉν™” 쑰회 둜직
        String sql = "SELECT * FROM users WHERE id = ?";
        // JDBC μ½”λ“œ...
        return user;
    }
}

public class SmtpEmailService {
    public void sendEmail(String to, String subject, String content) {
        // SMTP νŠΉν™” 이메일 λ°œμ†‘ 둜직
        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        // JavaMail μ½”λ“œ...
    }
}

// κ³ μˆ˜μ€€ λͺ¨λ“ˆμ΄ μ €μˆ˜μ€€ λͺ¨λ“ˆμ— 직접 의쑴 - DIP μœ„λ°˜
@Service
public class UserService {
    // ꡬ체적인 κ΅¬ν˜„μ²΄μ— 직접 의쑴!
    private final MySqlUserRepository userRepository;
    private final SmtpEmailService emailService;
    
    public UserService() {
        // μƒμ„±μžμ—μ„œ 직접 μΈμŠ€ν„΄μŠ€ 생성 - κ°•ν•œ κ²°ν•©!
        this.userRepository = new MySqlUserRepository();
        this.emailService = new SmtpEmailService();
    }
    
    public User createUser(String name, String email) {
        User user = new User(name, email);
        
        // MySQL에 κ°•ν•˜κ²Œ κ²°ν•©
        User savedUser = userRepository.save(user);
        
        // SMTP에 κ°•ν•˜κ²Œ κ²°ν•©  
        emailService.sendEmail(
            savedUser.getEmail(), 
            "ν™˜μ˜ν•©λ‹ˆλ‹€", 
            "κ°€μž…μ„ ν™˜μ˜ν•©λ‹ˆλ‹€"
        );
        
        return savedUser;
    }
}

// 또 λ‹€λ₯Έ DIP μœ„λ°˜ - 결제 μ„œλΉ„μŠ€
@Service  
public class PaymentService {
    // ꡬ체적인 PG사에 직접 의쑴
    private final IamportPaymentGateway iamportGateway;
    
    public PaymentService() {
        this.iamportGateway = new IamportPaymentGateway();
    }
    
    public PaymentResult processPayment(PaymentRequest request) {
        // μ•„μž„ν¬νŠΈμ—λ§Œ 의쑴 - λ‹€λ₯Έ PG둜 λ³€κ²½ μ‹œ 전체 μˆ˜μ • ν•„μš”
        return iamportGateway.charge(request.getAmount(), request.getCardInfo());
    }
}

πŸ”₯ λ¬Έμ œμ λ“€:

  • κ°•ν•œ κ²°ν•©: ꡬ체 κ΅¬ν˜„μ— 직접 μ˜μ‘΄ν•˜μ—¬ 변경이 어렀움
  • ν…ŒμŠ€νŠΈ 어렀움: μ‹€μ œ MySQL, SMTPλ₯Ό μ‚¬μš©ν•΄μ•Ό ν…ŒμŠ€νŠΈ κ°€λŠ₯
  • ν™•μž₯μ„± λΆ€μ‘±: λ‹€λ₯Έ κ΅¬ν˜„μ²΄λ‘œ λ³€κ²½ μ‹œ μ½”λ“œ 전체 μˆ˜μ • ν•„μš”

βœ… DIP 적용 ν›„

// 1. κ³ μˆ˜μ€€μ—μ„œ μ •μ˜ν•œ 좔상화 (μΈν„°νŽ˜μ΄μŠ€)
public interface UserRepository {
    User save(User user);
    User findById(Long id);
    List<User> findAll();
    boolean existsByEmail(String email);
}

public interface EmailService {
    void sendEmail(EmailMessage message);
}

public interface PaymentGateway {
    PaymentResult charge(PaymentRequest request);
    PaymentResult refund(String transactionId, int amount);
}

// 2. κ³ μˆ˜μ€€ λͺ¨λ“ˆ - μΆ”μƒν™”μ—λ§Œ 의쑴
@Service
@RequiredArgsConstructor  // μƒμ„±μž μ£Όμž…
public class UserService {
    // 좔상화(μΈν„°νŽ˜μ΄μŠ€)에 의쑴
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final PasswordEncoder passwordEncoder;
    
    @Transactional
    public User createUser(UserCreateRequest request) {
        // 이메일 쀑볡 확인
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new DuplicateEmailException();
        }
        
        User user = User.builder()
                .name(request.getName())
                .email(request.getEmail())
                .password(passwordEncoder.encode(request.getPassword()))
                .build();
        
        // 좔상화λ₯Ό ν†΅ν•œ μ €μž₯
        User savedUser = userRepository.save(user);
        
        // 좔상화λ₯Ό ν†΅ν•œ 이메일 λ°œμ†‘
        EmailMessage welcomeMessage = EmailMessage.builder()
                .to(savedUser.getEmail())
                .subject("ν™˜μ˜ν•©λ‹ˆλ‹€!")
                .content("κ°€μž…μ„ ν™˜μ˜ν•©λ‹ˆλ‹€, " + savedUser.getName() + "λ‹˜!")
                .build();
                
        emailService.sendEmail(welcomeMessage);
        
        return savedUser;
    }
}

@Service
@RequiredArgsConstructor
public class PaymentService {
    // 좔상화에 의쑴
    private final PaymentGateway paymentGateway;
    
    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        try {
            // μ–΄λ–€ PG사든 λ™μΌν•œ μΈν„°νŽ˜μ΄μŠ€λ‘œ 처리
            return paymentGateway.charge(request);
        } catch (PaymentException e) {
            throw new PaymentProcessingException("결제 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€", e);
        }
    }
    
    @Transactional
    public PaymentResult processRefund(String transactionId, int amount) {
        return paymentGateway.refund(transactionId, amount);
    }
}

// 3. μ €μˆ˜μ€€ λͺ¨λ“ˆλ“€ - 좔상화λ₯Ό κ΅¬ν˜„
@Repository
public class MySqlUserRepository implements UserRepository {
    
    @Override
    public User save(User user) {
        // MySQL JPA κ΅¬ν˜„
        return userJpaRepository.save(user);
    }
    
    @Override
    public User findById(Long id) {
        return userJpaRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException());
    }
    
    @Override
    public boolean existsByEmail(String email) {
        return userJpaRepository.existsByEmail(email);
    }
}

// λ‹€λ₯Έ DB κ΅¬ν˜„μ²΄λ„ μ‰½κ²Œ μΆ”κ°€ κ°€λŠ₯
@Repository
@Profile("mongodb")
public class MongoUserRepository implements UserRepository {
    
    @Override  
    public User save(User user) {
        // MongoDB κ΅¬ν˜„
        return mongoTemplate.save(user);
    }
    
    @Override
    public User findById(Long id) {
        return mongoTemplate.findById(id, User.class);
    }
}

@Service
public class SmtpEmailService implements EmailService {
    
    @Override
    public void sendEmail(EmailMessage message) {
        // SMTP κ΅¬ν˜„
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo(message.getTo());
        mailMessage.setSubject(message.getSubject());
        mailMessage.setText(message.getContent());
        
        mailSender.send(mailMessage);
    }
}

// λ‹€λ₯Έ 이메일 μ„œλΉ„μŠ€λ„ μ‰½κ²Œ μΆ”κ°€
@Service
@Profile("ses")
public class AwsSesEmailService implements EmailService {
    
    @Override
    public void sendEmail(EmailMessage message) {
        // AWS SES κ΅¬ν˜„
        SendEmailRequest request = SendEmailRequest.builder()
                .destination(Destination.builder().toAddresses(message.getTo()).build())
                .message(Message.builder()
                        .subject(Content.builder().data(message.getSubject()).build())
                        .body(Body.builder().text(Content.builder().data(message.getContent()).build()).build())
                        .build())
                .source(fromEmail)
                .build();
                
        sesClient.sendEmail(request);
    }
}

@Service
public class IamportPaymentGateway implements PaymentGateway {
    
    @Override
    public PaymentResult charge(PaymentRequest request) {
        // μ•„μž„ν¬νŠΈ API 호좜
        IamportResponse<Payment> response = iamportClient.paymentByImpUid(request.getImpUid());
        
        return PaymentResult.builder()
                .transactionId(response.getResponse().getImpUid())
                .status(convertStatus(response.getResponse().getStatus()))
                .amount(response.getResponse().getAmount().intValue())
                .build();
    }
}

// λ‹€λ₯Έ PG사도 μ‰½κ²Œ μΆ”κ°€
@Service
@Profile("toss")
public class TossPaymentGateway implements PaymentGateway {
    
    @Override
    public PaymentResult charge(PaymentRequest request) {
        // ν† μŠ€νŽ˜μ΄λ¨ΌμΈ  API 호좜
        // ...
    }
}

πŸŽ‰ DIP 적용 ν›„ μž₯점듀:

  • λŠμŠ¨ν•œ κ²°ν•©: κ΅¬ν˜„μ²΄ λ³€κ²½ μ‹œ κ³ μˆ˜μ€€ λͺ¨λ“ˆ μˆ˜μ • λΆˆν•„μš”
  • ν…ŒμŠ€νŠΈ μš©μ΄μ„±: Mock 객체둜 μ‰½κ²Œ λ‹¨μœ„ ν…ŒμŠ€νŠΈ κ°€λŠ₯
  • ν™•μž₯μ„±: μƒˆλ‘œμš΄ κ΅¬ν˜„μ²΄ μΆ”κ°€κ°€ 맀우 쉬움
  • μ„€μ • 기반 μ „ν™˜: Profileμ΄λ‚˜ μ„€μ •μœΌλ‘œ κ΅¬ν˜„μ²΄ λ³€κ²½ κ°€λŠ₯
// ν…ŒμŠ€νŠΈ μ½”λ“œ μ˜ˆμ‹œ
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @Mock
    private EmailService emailService;
    
    @Mock
    private PasswordEncoder passwordEncoder;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void createUser_성곡() {
        // given
        UserCreateRequest request = new UserCreateRequest("ν…ŒμŠ€νŠΈ", "test@test.com", "password");
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        when(passwordEncoder.encode(request.getPassword())).thenReturn("encodedPassword");
        
        User savedUser = User.builder()
                .id(1L)
                .name(request.getName())
                .email(request.getEmail())
                .build();
        when(userRepository.save(any(User.class))).thenReturn(savedUser);
        
        // when
        User result = userService.createUser(request);
        
        // then
        assertThat(result.getName()).isEqualTo("ν…ŒμŠ€νŠΈ");
        verify(emailService).sendEmail(any(EmailMessage.class));
    }
}

βš–οΈ μ–Έμ œ SOLIDλ₯Ό μ μš©ν•΄μ•Ό ν• κΉŒ?

🟒 SOLID 적극 적용 상황

// λ³΅μž‘ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ κ°€μ§„ μ£Όλ¬Έ 처리 μ‹œμŠ€ν…œ
@Service
@RequiredArgsConstructor
public class OrderProcessingService {
    // μ—¬λŸ¬ μ™ΈλΆ€ μ‹œμŠ€ν…œκ³Ό 연동
    private final PaymentGateway paymentGateway;
    private final InventoryService inventoryService;
    private final ShippingService shippingService;
    private final NotificationService notificationService;
    private final LoyaltyService loyaltyService;
    
    @Transactional
    public OrderResult processOrder(OrderRequest request) {
        // λ³΅μž‘ν•œ μ£Όλ¬Έ 처리 ν”Œλ‘œμš°
        // 1. 재고 확인 및 μ˜ˆμ•½
        // 2. 결제 처리
        // 3. 배솑 μš”μ²­  
        // 4. 포인트 적립
        // 5. μ•Œλ¦Ό λ°œμ†‘
        // 각 λ‹¨κ³„λ§ˆλ‹€ λ‹€μ–‘ν•œ μ˜ˆμ™Έ 상황 처리
    }
}

적용 μ‹ ν˜Έλ“€:

  • 높은 λ³΅μž‘λ„: ν΄λž˜μŠ€κ°€ 100쀄 이상, λ©”μ„œλ“œκ°€ 20쀄 이상
  • λ‹€μ–‘ν•œ λ³€κ²½ μš”μΈ: λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™, μ™ΈλΆ€ μ‹œμŠ€ν…œ, UI μš”κ΅¬μ‚¬ν•­ λ³€κ²½
  • ν™•μž₯ κ³„νš: μƒˆλ‘œμš΄ 결제 방식, 배솑업체, μ•Œλ¦Ό 채널 μΆ”κ°€ μ˜ˆμ •
  • νŒ€ 규λͺ¨: 3λͺ… μ΄μƒμ˜ κ°œλ°œμžκ°€ λ™μ‹œμ— μž‘μ—…

🟑 SOLID 선택적 적용 상황

// 쀑간 λ³΅μž‘λ„μ˜ μ‚¬μš©μž 관리 μ‹œμŠ€ν…œ
@Service
@RequiredArgsConstructor  
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    
    public User createUser(UserCreateRequest request) {
        // μ λ‹Ήν•œ λ³΅μž‘λ„μ˜ 둜직
        validateUserRequest(request);
        User user = buildUser(request);
        return userRepository.save(user);
    }
    
    private void validateUserRequest(UserCreateRequest request) {
        // 검증 둜직 (10쀄 λ‚΄μ™Έ)
    }
    
    private User buildUser(UserCreateRequest request) {
        // 생성 둜직 (5쀄 λ‚΄μ™Έ)
    }
}

적용 κ³ λ € 사항:

  • DIP μš°μ„  적용: ν…ŒμŠ€νŠΈ μš©μ΄μ„±μ„ μœ„ν•΄
  • SRP λΆ€λΆ„ 적용: λ„ˆλ¬΄ 큰 클래슀만 뢄리
  • OCPλŠ” ν™•μž₯ κ³„νšμ΄ μžˆμ„ λ•Œλ§Œ

πŸ”΄ SOLID μ΅œμ†Œ 적용 상황

// λ‹¨μˆœν•œ CRUD μ„œλΉ„μŠ€
@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepository repository;
    
    public Product save(Product product) {
        return repository.save(product);
    }
    
    public Product findById(Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException(id));
    }
    
    public void deleteById(Long id) {
        repository.deleteById(id);
    }
}

μ΅œμ†Œ 적용 κΈ°μ€€:

  • DIP만 적용: Spring의 μ˜μ‘΄μ„± μ£Όμž… ν™œμš©
  • λ‚˜λ¨Έμ§€ 원칙은 κ³Όλ„ν•œ 섀계: λ‹¨μˆœν•¨μ΄ 더 λ‚˜μŒ

🚨 자주 ν•˜λŠ” μ‹€μˆ˜λ“€κ³Ό ν•΄κ²°μ±…

❌ μ‹€μˆ˜ 1: κ³Όλ„ν•œ 좔상화

// λΆˆν•„μš”ν•œ μΆ”μƒν™”μ˜ 예
public interface StringProcessor {
    String process(String input);
}

@Component
public class StringTrimmer implements StringProcessor {
    public String process(String input) {
        return input.trim();
    }
}

@Component  
public class StringUpperCaser implements StringProcessor {
    public String process(String input) {
        return input.toUpperCase();
    }
}

// 이런 λ‹¨μˆœν•œ κΈ°λŠ₯κΉŒμ§€ μΈν„°νŽ˜μ΄μŠ€λ‘œ λ§Œλ“€ ν•„μš” μ—†μŒ

βœ… ν•΄κ²°μ±…: μ μ ˆν•œ μˆ˜μ€€μ˜ 좔상화

// μœ ν‹Έλ¦¬ν‹°μ„± κΈ°λŠ₯은 정적 λ©”μ„œλ“œλ‚˜ κ°„λ‹¨ν•œ μ»΄ν¬λ„ŒνŠΈλ‘œ
@Component
public class StringUtils {
    public String cleanAndFormat(String input) {
        if (input == null) return "";
        return input.trim().toUpperCase();
    }
}

❌ μ‹€μˆ˜ 2: μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„μ²΄ 1:1 λ§€μΉ­

// μ˜λ―Έμ—†λŠ” 1:1 μΈν„°νŽ˜μ΄μŠ€
public interface UserService {
    User createUser(UserCreateRequest request);
}

@Service
public class UserServiceImpl implements UserService {
    // κ΅¬ν˜„μ²΄κ°€ ν•˜λ‚˜λΏμΈλ° ꡳ이 μΈν„°νŽ˜μ΄μŠ€?
}

βœ… ν•΄κ²°μ±…: ν•„μš”μ‹œμ—λ§Œ μΈν„°νŽ˜μ΄μŠ€ 생성

// κ΅¬ν˜„μ²΄κ°€ ν•˜λ‚˜λΏμ΄κ³  ν™•μž₯ κ³„νšμ΄ μ—†λ‹€λ©΄ μΈν„°νŽ˜μ΄μŠ€ λΆˆν•„μš”
@Service
public class UserService {
    public User createUser(UserCreateRequest request) {
        // κ΅¬ν˜„ 둜직
    }
}

// ν™•μž₯ κ³„νšμ΄ μžˆκ±°λ‚˜ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ Mock이 ν•„μš”ν•œ κ²½μš°μ—λ§Œ μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš©

❌ μ‹€μˆ˜ 3: God Object λ°©μ§€λ₯Ό μœ„ν•œ κ³Όλ„ν•œ 뢄리

// λ„ˆλ¬΄ μ„ΈλΆ„ν™”λœ 뢄리
@Component
public class UserNameValidator { }     // 이름 κ²€μ¦λ§Œ
@Component  
public class UserEmailValidator { }    // 이메일 κ²€μ¦λ§Œ
@Component
public class UserPhoneValidator { }    // μ „ν™”λ²ˆν˜Έ κ²€μ¦λ§Œ
@Component
public class UserAgeValidator { }      // λ‚˜μ΄ κ²€μ¦λ§Œ

// 였히렀 λ³΅μž‘λ„λ§Œ 증가

βœ… ν•΄κ²°μ±…: κ΄€λ ¨μžˆλŠ” μ±…μž„μ€ ν•¨κ»˜ λ¬ΆκΈ°

@Component
public class UserValidator {
    public void validate(User user) {
        validateName(user.getName());
        validateEmail(user.getEmail());
        validatePhone(user.getPhone());
        validateAge(user.getAge());
    }
    
    private void validateName(String name) { }
    private void validateEmail(String email) { }
    private void validatePhone(String phone) { }
    private void validateAge(int age) { }
}

πŸŽ“ 싀무 적용 λ‘œλ“œλ§΅

πŸƒβ€β™‚οΈ 1단계: 기초 λ‹€μ§€κΈ° (1-2μ£Ό)

DIPλΆ€ν„° μ‹œμž‘

// Before: ꡬ체 ν΄λž˜μŠ€μ— 의쑴
@Service
public class OrderService {
    private MySqlOrderRepository repository = new MySqlOrderRepository();
}

// After: 좔상화에 의쑴
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository repository; // μΈν„°νŽ˜μ΄μŠ€μ— 의쑴
}

πŸšΆβ€β™‚οΈ 2단계: μ±…μž„ 뢄리 (2-3μ£Ό)

SRP 적용으둜 큰 클래슀 λΆ„ν•΄

// Before: ν•˜λ‚˜μ˜ μ„œλΉ„μŠ€κ°€ λͺ¨λ“  일을 λ‹΄λ‹Ή
@Service
public class OrderService {
    // μ£Όλ¬Έ 생성 + 결제 + μž¬κ³ κ΄€λ¦¬ + 이메일 λ°œμ†‘ + λ‘œκΉ… (200쀄)
}

// After: μ±…μž„λ³„λ‘œ 뢄리
@Service
public class OrderService {
    private final OrderValidator validator;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    // 각각 λͺ…ν™•ν•œ μ±…μž„
}

πŸƒβ€β™‚οΈ 3단계: ν™•μž₯μ„± κ³ λ € (3-4μ£Ό)

OCP 적용으둜 ν™•μž₯ κ°€λŠ₯ν•œ ꡬ쑰

// μƒˆλ‘œμš΄ 결제 방식, μ•Œλ¦Ό 방식을 μ‰½κ²Œ μΆ”κ°€ν•  수 μžˆλŠ” ꡬ쑰
public interface PaymentProcessor {
    PaymentResult process(PaymentRequest request);
}

@Service
public class PaymentService {
    private final List<PaymentProcessor> processors;
    
    public PaymentResult process(PaymentRequest request) {
        // μ μ ˆν•œ processor μ„ νƒν•΄μ„œ 처리
    }
}

πŸ”„ 4단계: 지속적 κ°œμ„ 

μ½”λ“œ 리뷰 체크리슀트

  • SRP: 이 ν΄λž˜μŠ€κ°€ λ³€κ²½λ˜λŠ” μ΄μœ κ°€ 2개 이상인가?
  • OCP: μƒˆ κΈ°λŠ₯ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œλ₯Ό μˆ˜μ •ν•΄μ•Ό ν•˜λŠ”κ°€?
  • LSP: λΆ€λͺ¨λ₯Ό μžμ‹μœΌλ‘œ 바꿔도 정상 λ™μž‘ν•˜λŠ”κ°€?
  • ISP: μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”μ„œλ“œμ— μ˜μ‘΄ν•˜κ³  μžˆλŠ”κ°€?
  • DIP: ꡬ체 ν΄λž˜μŠ€μ— 직접 μ˜μ‘΄ν•˜κ³  μžˆλŠ”κ°€?

πŸ’‘ νŒ€ λ‹¨μœ„ 적용 μ „λž΅

πŸ‘₯ μž‘μ€ νŒ€ (2-3λͺ…)

// 핡심 μ„œλΉ„μŠ€μ—λ§Œ 선택적 적용
@Service
@RequiredArgsConstructor
public class OrderService {  // 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직만 SOLID 적용
    private final PaymentGateway paymentGateway;  // DIP
    private final NotificationSender notificationSender;  // ISP + OCP
}

@Service
public class ProductService {  // λ‹¨μˆœ CRUDλŠ” κΈ°λ³Έ ꡬ쑰 μœ μ§€
    private final ProductRepository repository;
}

πŸ‘₯ 쀑간 νŒ€ (4-6λͺ…)

// λͺ¨λ“ˆλ³„ λ‹΄λ‹Ήμž μ§€μ •, μΈν„°νŽ˜μ΄μŠ€ 쀑심 섀계
public interface PaymentModule {
    PaymentResult process(PaymentRequest request);
}

public interface NotificationModule {
    void send(NotificationRequest request);
}

// 각 λͺ¨λ“ˆμ„ λ…λ¦½μ μœΌλ‘œ 개발 κ°€λŠ₯

πŸ‘₯ 큰 νŒ€ (7λͺ… 이상)

// μ™„μ „ν•œ SOLID 적용 + 도메인별 뢄리
// μ£Όλ¬Έ 도메인
@DomainService
public class OrderDomainService {
    // λͺ¨λ“  SOLID 원칙 적용
}

// 결제 도메인  
@DomainService
public class PaymentDomainService {
    // λͺ¨λ“  SOLID 원칙 적용
}

// 도메인간 톡신은 μ΄λ²€νŠΈλ‚˜ API κ²Œμ΄νŠΈμ›¨μ΄ μ‚¬μš©

🎯 성곡 μ§€ν‘œμ™€ μΈ‘μ •

πŸ“Š μ •λŸ‰μ  μ§€ν‘œ

// 1. 클래슀 크기 μΈ‘μ •
// Before SOLID: 평균 200쀄/클래슀
// After SOLID: 평균 50-80쀄/클래슀

// 2. μ˜μ‘΄μ„± 개수
// Before: ν•˜λ‚˜μ˜ μ„œλΉ„μŠ€κ°€ 10개 μ΄μƒμ˜ ꡬ체 ν΄λž˜μŠ€μ— 의쑴
// After: μΈν„°νŽ˜μ΄μŠ€ 의쑴으둜 결합도 κ°μ†Œ

// 3. ν…ŒμŠ€νŠΈ 컀버리지
// Before: ν†΅ν•©ν…ŒμŠ€νŠΈ μœ„μ£Όλ‘œ 느린 ν…ŒμŠ€νŠΈ
// After: λ‹¨μœ„ν…ŒμŠ€νŠΈ μ¦κ°€λ‘œ λΉ λ₯Έ ν”Όλ“œλ°±

πŸ“ˆ 정성적 μ§€ν‘œ

  • μƒˆ κΈ°λŠ₯ μΆ”κ°€ μ‹œκ°„: κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • 없이 μΆ”κ°€ κ°€λŠ₯
  • 버그 수 κ°μ†Œ: μ±…μž„μ΄ λͺ…ν™•ν•΄μ Έ 버그 λ°œμƒ 지점 λͺ…ν™•
  • νŒ€ 생산성: λͺ¨λ“ˆλ³„ 독립 개발둜 좩돌 κ°μ†Œ

πŸŽͺ 핡심 원칙 정리

πŸ† μ„±κ³΅ν•˜λŠ” 개발자의 SOLID λ§ˆμΈλ“œμ…‹

β€œμ™„λ²½ν•œ μ„€κ³„λ³΄λ‹€λŠ” 점진적 κ°œμ„ μ„!”

5κ°€μ§€ μ‹€μ²œ 원칙

  1. 🎯 SRP: β€œμ΄ ν΄λž˜μŠ€κ°€ λ³€κ²½λ˜λŠ” μ΄μœ κ°€ 2개 이상이면 뢄리λ₯Ό κ³ λ €ν•œλ‹€β€
  2. πŸšͺ OCP: β€œμƒˆ κΈ°λŠ₯ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜κ³  μžˆλ‹€λ©΄ 섀계λ₯Ό μ˜μ‹¬ν•œλ‹€β€
  3. πŸ”„ LSP: β€œλΆ€λͺ¨λ₯Ό μžμ‹μœΌλ‘œ 바꿨을 λ•Œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ©΄ 섀계λ₯Ό λ‹€μ‹œ 본닀”
  4. 🧩 ISP: β€œμ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€λ©΄ μΈν„°νŽ˜μ΄μŠ€λ₯Ό λΆ„λ¦¬ν•œλ‹€β€
  5. ⬇️ DIP: β€œν…ŒμŠ€νŠΈν•˜κΈ° μ–΄λ ΅λ‹€λ©΄ ꡬ체 ν΄λž˜μŠ€μ— μ˜μ‘΄ν•˜κ³  μžˆλŠ” 건 μ•„λ‹Œμ§€ ν™•μΈν•œλ‹€β€

πŸš€ 마무리: μ‹€λ¬΄μ—μ„œ μ‚΄μ•„λ‚¨λŠ” SOLID

⚑ 싀무 적용의 ν™©κΈˆλ₯ 

β€œSOLIDλŠ” λͺ©μ μ΄ μ•„λ‹ˆλΌ μˆ˜λ‹¨μ΄λ‹€β€

SOLID 원칙을 μ μš©ν•˜λŠ” μ΄μœ λŠ” 원칙 μžμ²΄κ°€ λͺ©μ μ΄ μ•„λ‹ˆλΌ, λ‹€μŒμ„ μœ„ν•΄μ„œμž…λ‹ˆλ‹€:

  • πŸ”§ μœ μ§€λ³΄μˆ˜μ„±: 6κ°œμ›” 후에도 μ΄ν•΄ν•˜κΈ° μ‰¬μš΄ μ½”λ“œ
  • πŸš€ ν™•μž₯μ„±: μƒˆλ‘œμš΄ μš”κ΅¬μ‚¬ν•­μ— λΉ λ₯΄κ²Œ λŒ€μ‘
  • πŸ§ͺ ν…ŒμŠ€νŠΈ μš©μ΄μ„±: μ•ˆμ •μ μΈ 배포λ₯Ό μœ„ν•œ κ²¬κ³ ν•œ ν…ŒμŠ€νŠΈ
  • πŸ‘₯ ν˜‘μ—…: νŒ€μ›λ“€κ³Ό ν•¨κ»˜ μΌν•˜κΈ° 쒋은 μ½”λ“œ

🎯 싀무 적용 3단계 μš”μ•½

1️⃣ μ‹œμž‘ 단계: DIPλΆ€ν„°

// μ˜μ‘΄μ„± μ£Όμž…μœΌλ‘œ ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ μ½”λ“œ λ§Œλ“€κΈ°
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository repository; // 좔상화에 의쑴
}

2️⃣ λ°œμ „ 단계: SRP + OCP

// μ±…μž„ 뢄리 + ν™•μž₯ κ°€λŠ₯ν•œ ꡬ쑰
@Service
public class OrderService {
    private final List<OrderValidator> validators;     // SRP
    private final List<PaymentProcessor> processors;   // OCP
}

3️⃣ μ™„μ„± 단계: 전체 원칙 적용

// λͺ¨λ“  SOLID 원칙이 μžμ—°μŠ€λŸ½κ²Œ λ…Ήμ•„λ“  μ½”λ“œ
// 각 ν΄λž˜μŠ€κ°€ λͺ…ν™•ν•œ μ±…μž„μ„ κ°€μ§€κ³ 
// ν™•μž₯에 μ—΄λ €μžˆμœΌλ©°
// μ•ˆμ „ν•˜κ²Œ μΉ˜ν™˜ κ°€λŠ₯ν•˜κ³   
// ν•„μš”ν•œ μΈν„°νŽ˜μ΄μŠ€λ§Œ μ˜μ‘΄ν•˜λ©°
// 좔상화에 μ˜μ‘΄ν•˜λŠ” ꡬ쑰

🎁 λ§ˆμ§€λ§‰ μ‘°μ–Έ

SOLID 원칙을 λ§Ήλͺ©μ μœΌλ‘œ μ μš©ν•˜μ§€ λ§ˆμ„Έμš”. ν”„λ‘œμ νŠΈμ˜ 규λͺ¨, νŒ€μ˜ 크기, μš”κ΅¬μ‚¬ν•­μ˜ λ³΅μž‘λ„λ₯Ό κ³ λ €ν•΄μ„œ μ μ ˆν•œ μˆ˜μ€€μ—μ„œ μ μš©ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

  • μž‘μ€ ν”„λ‘œμ νŠΈ: DIP μ •λ„λ§ŒμœΌλ‘œλ„ μΆ©λΆ„
  • 쀑간 ν”„λ‘œμ νŠΈ: SRP + DIP + OCP 선택 적용
  • 큰 ν”„λ‘œμ νŠΈ: λͺ¨λ“  SOLID 원칙 적극 ν™œμš©

β€œμ˜€λŠ˜μ˜ 선택이 6κ°œμ›” ν›„μ˜ λ‚˜λ₯Ό λ§Œλ“ λ‹€β€

μ§€κΈˆ λ‹Ήμž₯은 λ³΅μž‘ν•΄ 보일 수 μžˆμ§€λ§Œ, SOLID 원칙을 μ²΄λ“ν•œ κ°œλ°œμžλŠ” 더 λΉ λ₯΄κ³ , 더 μ•ˆμ „ν•˜κ²Œ, 더 즐겁게 κ°œλ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ—¬λŸ¬λΆ„μ˜ 개발 여정에 SOLIDκ°€ λ“ λ“ ν•œ λ‚˜μΉ¨λ°˜μ΄ 되기λ₯Ό λ°”λžλ‹ˆλ‹€! 🧭✨