μΌ | μ | ν | μ | λͺ© | κΈ | ν |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- νν΄νλ¬μ€ νκ³
- νΌλ ꡬν
- νν λ°±μλ
- νν΄νλ¬μ€λ°±μλ
- fanout on write
- κΈ°λ₯ ν μ€νΈ
- νν νκΈ°
- TDD
- pull model
- νν΄νλ¬μ€νκΈ°
- νν΄
- νλ‘ν νμ λΉ
- API Aggregation
- λμμΈ ν¨ν΄
- μμ°¨μ§ν₯
- push model
- νν΄νλ¬μ€
- OOP
- μμ± ν¨ν΄
- νν΄νλ¬μ€ λ°±μλ
- νν΄+
- νν
- λ°μ΄ν° 쿼리
- νν΄ νλ¬μ€ νκΈ°
- fanout on read
- νν΄ λ°±μλ
- νν΄ νκΈ°
- μ±κΈν€ λΉ
- νν΄νλ¬μ€ νκΈ°
- λΉ μ€μ½ν
- Today
- Total
deVlog
[λμμΈ ν¨ν΄] νμ ν¨ν΄ - Status Pattern (μν ν¨ν΄) λ³Έλ¬Έ
[λμμΈ ν¨ν΄] νμ ν¨ν΄ - Status Pattern (μν ν¨ν΄)
μλ£¨λ° 2025. 4. 2. 23:21λͺ©μ°¨
π κ°μ
μν ν¨ν΄(State Pattern)μ κ°μ²΄μ λ΄λΆ μνκ° λ³κ²½λ λ κ°μ²΄μ νλμ΄ λ³κ²½λλλ‘ νλ νλ λμμΈ ν¨ν΄μ΄λ€. μ¦, κ°μ²΄μ μνμ λ°λΌ κ°μ²΄μ νλμ΄ λ¬λΌμ§κ² νλ ν¨ν΄μΌλ‘, λ§μΉ κ°μ²΄μ ν΄λμ€κ° λ³κ²½λλ κ²μ²λΌ 보μ΄κ² νλ€.
νΉμ§
- μν μ ν λ‘μ§μ μΊ‘μννμ¬ μ½λμ κ°λ μ±κ³Ό μ μ§λ³΄μμ±μ λμΈλ€.
- μνλ³ λμμ λ³λμ ν΄λμ€λ‘ λΆλ¦¬νμ¬ λ¨μΌ μ± μ μμΉ(SRP)μ μ€μνλ€.
- 쑰건문(if-else, switch)μ μ κ±°νκ³ κ°μ²΄μ§ν₯μ μΈ λ°©μμΌλ‘ μν μ νμ μ²λ¦¬νλ€.
π©π»π» μ½λλ‘ μμ보μ
μνκΈ°μ μν λ³νλ₯Ό ꡬννλ μμ λ₯Ό ν΅ν΄ μν ν¨ν΄μ μ΄ν΄λ³΄μ.
// 1. μν μΈν°νμ΄μ€
interface State {
void insertCoin(VendingMachine vendingMachine);
void selectProduct(VendingMachine vendingMachine);
void dispense(VendingMachine vendingMachine);
void refund(VendingMachine vendingMachine);
}
// 2. ꡬ체μ μΈ μν ν΄λμ€λ€
class NoCoinState implements State {
@Override
public void insertCoin(VendingMachine vendingMachine) {
System.out.println("λμ μ΄ ν¬μ
λμμ΅λλ€.");
vendingMachine.setState(vendingMachine.getHasCoinState());
}
@Override
public void selectProduct(VendingMachine vendingMachine) {
System.out.println("λμ μ λ¨Όμ λ£μ΄μ£ΌμΈμ.");
}
@Override
public void dispense(VendingMachine vendingMachine) {
System.out.println("λμ μ λ¨Όμ λ£μ΄μ£ΌμΈμ.");
}
@Override
public void refund(VendingMachine vendingMachine) {
System.out.println("λ°νν λμ μ΄ μμ΅λλ€.");
}
}
class HasCoinState implements State {
@Override
public void insertCoin(VendingMachine vendingMachine) {
System.out.println("μ΄λ―Έ λμ μ΄ λ€μ΄μμ΅λλ€. μνμ μ νν΄μ£ΌμΈμ.");
}
@Override
public void selectProduct(VendingMachine vendingMachine) {
System.out.println("μνμ΄ μ νλμμ΅λλ€.");
vendingMachine.setState(vendingMachine.getProductSelectedState());
}
@Override
public void dispense(VendingMachine vendingMachine) {
System.out.println("μνμ λ¨Όμ μ νν΄μ£ΌμΈμ.");
}
@Override
public void refund(VendingMachine vendingMachine) {
System.out.println("λμ μ΄ λ°νλμμ΅λλ€.");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
}
class ProductSelectedState implements State {
@Override
public void insertCoin(VendingMachine vendingMachine) {
System.out.println("μ΄λ―Έ μνμ΄ μ νλμμ΅λλ€. μΆκ° λμ μ νμ μμ΅λλ€.");
}
@Override
public void selectProduct(VendingMachine vendingMachine) {
System.out.println("μ΄λ―Έ μνμ΄ μ νλμμ΅λλ€.");
}
@Override
public void dispense(VendingMachine vendingMachine) {
System.out.println("μνμ΄ λμμ΅λλ€. κ°μ¬ν©λλ€!");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
@Override
public void refund(VendingMachine vendingMachine) {
System.out.println("μν μ νμ΄ μ·¨μλμμ΅λλ€. λμ μ΄ λ°νλ©λλ€.");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
}
class SoldOutState implements State {
@Override
public void insertCoin(VendingMachine vendingMachine) {
System.out.println("μ£μ‘ν©λλ€. μ¬κ³ κ° μμ΅λλ€. λμ μ΄ λ°νλ©λλ€.");
}
@Override
public void selectProduct(VendingMachine vendingMachine) {
System.out.println("μ£μ‘ν©λλ€. μ¬κ³ κ° μμ΅λλ€.");
}
@Override
public void dispense(VendingMachine vendingMachine) {
System.out.println("μ€ν¨: μ¬κ³ κ° μμ΅λλ€.");
}
@Override
public void refund(VendingMachine vendingMachine) {
System.out.println("λ°νν λμ μ΄ μμ΅λλ€.");
}
}
// 3. 컨ν
μ€νΈ ν΄λμ€
class VendingMachine {
private State noCoinState;
private State hasCoinState;
private State productSelectedState;
private State soldOutState;
private State currentState;
private int count; // μν μ¬κ³
public VendingMachine(int count) {
// μν κ°μ²΄λ€ μ΄κΈ°ν
noCoinState = new NoCoinState();
hasCoinState = new HasCoinState();
productSelectedState = new ProductSelectedState();
soldOutState = new SoldOutState();
this.count = count;
if (count > 0) {
currentState = noCoinState;
} else {
currentState = soldOutState;
}
}
// μ¬μ©μ νλ λ©μλλ€
public void insertCoin() {
currentState.insertCoin(this);
}
public void selectProduct() {
currentState.selectProduct(this);
}
public void dispense() {
currentState.dispense(this);
if (currentState != soldOutState) {
count--;
if (count == 0) {
setState(soldOutState);
}
}
}
public void refund() {
currentState.refund(this);
}
// μν λ³κ²½ λ©μλ
public void setState(State state) {
this.currentState = state;
}
// μν νλ λ©μλλ€
public State getNoCoinState() {
return noCoinState;
}
public State getHasCoinState() {
return hasCoinState;
}
public State getProductSelectedState() {
return productSelectedState;
}
public State getSoldOutState() {
return soldOutState;
}
// νμ¬ μν μΆλ ₯
public void printStatus() {
System.out.println("νμ¬ μν: " + currentState.getClass().getSimpleName());
System.out.println("λ¨μ μ¬κ³ : " + count);
}
}
// 4. ν΄λΌμ΄μΈνΈ μ½λ
public class StatePatternExample {
public static void main(String[] args) {
// μνκΈ° μμ± (μ¬κ³ 3κ°)
VendingMachine vendingMachine = new VendingMachine(3);
// μ μ μλ리μ€: λμ ν¬μ
-> μν μ ν -> μν λ°°μΆ
System.out.println("----- μλλ¦¬μ€ 1: μ μ ꡬ맀 κ³Όμ -----");
vendingMachine.printStatus();
vendingMachine.insertCoin();
vendingMachine.selectProduct();
vendingMachine.dispense();
vendingMachine.printStatus();
// λμ λ°ν μλ리μ€: λμ ν¬μ
-> λ°ν
System.out.println("\n----- μλλ¦¬μ€ 2: λμ λ°ν -----");
vendingMachine.insertCoin();
vendingMachine.refund();
vendingMachine.printStatus();
// μν μ ν μ·¨μ μλ리μ€: λμ ν¬μ
-> μν μ ν -> μ·¨μ
System.out.println("\n----- μλλ¦¬μ€ 3: μν μ ν μ·¨μ -----");
vendingMachine.insertCoin();
vendingMachine.selectProduct();
vendingMachine.refund();
vendingMachine.printStatus();
// μ¬κ³ μμ§ μλ리μ€
System.out.println("\n----- μλλ¦¬μ€ 4: μ¬κ³ μμ§ -----");
vendingMachine.insertCoin();
vendingMachine.selectProduct();
vendingMachine.dispense();
vendingMachine.insertCoin();
vendingMachine.selectProduct();
vendingMachine.dispense();
vendingMachine.printStatus();
// μ¬κ³ μμ μλ리μ€
System.out.println("\n----- μλλ¦¬μ€ 5: μ¬κ³ μμ -----");
vendingMachine.insertCoin();
vendingMachine.printStatus();
}
}
- State μΈν°νμ΄μ€λ μνκΈ°μ λͺ¨λ νλμ λν λ©μλλ₯Ό μ μνλ€.
- ꡬ체μ μΈ μν ν΄λμ€λ€(NoCoinState, HasCoinState, ProductSelectedState, SoldOutState)μ κ° μνμμμ νλμ ꡬννλ€.
- VendingMachine ν΄λμ€λ 컨ν μ€νΈ μν μ νλ©°, νμ¬ μνλ₯Ό μ μ§νκ³ μ¬μ©μ νλμ νμ¬ μνμ μμνλ€.
- μνκ° μ νλ λλ§λ€ 컨ν μ€νΈμ setState() λ©μλλ₯Ό ν΅ν΄ μν κ°μ²΄κ° λ³κ²½λλ€.
βοΈ μ₯/λ¨μ
β μ₯μ
- 쑰건문 μ κ±°
- 볡μ‘ν 쑰건문(if-else, switch)μ μ κ±°νκ³ κ°μ²΄μ§ν₯μ μΈ λ°©μμΌλ‘ μνλ³ νλμ λΆλ¦¬νλ€.
- μ΄λ μ½λμ κ°λ μ±κ³Ό μ μ§λ³΄μμ±μ ν¬κ² ν₯μμν¨λ€.
- λ¨μΌ μ±
μ μμΉ(SRP) μ€μ
- κ° μνμ νλμ λ³λμ ν΄λμ€λ‘ μΊ‘μννμ¬ μ± μμ λΆμ°μν¨λ€.
- νΉμ μνμ κ΄λ ¨λ νλλ§ λ³κ²½νλ©΄ λλ―λ‘ μ½λ λ³κ²½μ΄ μ½λ€.
- κ°λ°©/νμ μμΉ(OCP) μ€μ
- κΈ°μ‘΄ μ½λλ₯Ό μμ νμ§ μκ³ λ μλ‘μ΄ μνμ νλμ μΆκ°ν μ μλ€.
- μ: μνκΈ°μ 'μ κ² μ€' μνλ₯Ό μΆκ°νλλΌλ κΈ°μ‘΄ μν ν΄λμ€λ λ³κ²½ν νμκ° μλ€.
- μν μ ν λ‘μ§μ λͺ
νν
- μν μ ν λ‘μ§μ΄ κ° μν ν΄λμ€ λ΄λΆμ λΆμ°λμ΄ μμ΄ μν κ΄λ¦¬κ° λͺ νν΄μ§λ€.
- λλ²κΉ κ³Ό μ½λ μ΄ν΄κ° μ¬μμ§λ€.
β λ¨μ
- ν΄λμ€ μ μ¦κ°
- κ° μνλ§λ€ λ³λμ ν΄λμ€κ° νμνλ―λ‘ μ 체 ν΄λμ€ μκ° λ§μμ§ μ μλ€.
- κ°λ¨ν μν κ΄λ¦¬μλ κ³Όλν λμμΈμ΄ λ μ μλ€.
- μν κ° μμ‘΄μ± κ΄λ¦¬
- μν μ ν λ‘μ§μ΄ μν ν΄λμ€μ λΆμ°λμ΄ μκΈ° λλ¬Έμ μν κ° μμ‘΄μ±μ΄ μκΈΈ μ μλ€.
- μ΄λ‘ μΈν΄ ν μνλ₯Ό λ³κ²½ν λ λ€λ₯Έ μνλ ν¨κ» λ³κ²½ν΄μΌ ν μ μλ€.
- 컨ν
μ€νΈ μ’
μμ±
- μν ν΄λμ€κ° 컨ν μ€νΈ(VendingMachine)μ κ°νκ² κ²°ν©λμ΄ μμ΄ μ¬μ¬μ©μ±μ΄ μ νλ μ μλ€.
- μν ν΄λμ€λ 컨ν μ€νΈμ λ©μλμ μμ±μ μ κ·Όν΄μΌ ν μ μλ€.
- μν κ°μ²΄ κ΄λ¦¬ μ€λ²ν€λ
- λͺ¨λ μν κ°μ²΄λ₯Ό 미리 μμ±νκ³ μ μ§ν΄μΌ νλ―λ‘ λ©λͺ¨λ¦¬ μ¬μ©λμ΄ μ¦κ°ν μ μλ€.
- νΉν μνκ° λ§κ³ 볡μ‘ν μμ€ν μμλ μ΄ λ¬Έμ κ° λλλ¬μ§ μ μλ€.
π μ 리
μν ν¨ν΄μ κ°μ²΄μ λ΄λΆ μνκ° λ³κ²½λ λ κ°μ²΄μ νλμ΄ λ³κ²½λλλ‘ νλ λμμΈ ν¨ν΄μ΄λ€.
μ΄ ν¨ν΄μ μνλ³ νλμ λ³λμ ν΄λμ€λ‘ μΊ‘μννμ¬ μ‘°κ±΄λ¬Έμ μ κ±°νκ³ , κ°μ²΄μ§ν₯μ μΈ λ°©μμΌλ‘ μν μ νμ κ΄λ¦¬νλ€. μν μ ν λ‘μ§μ λΆμ°μν€κ³ κ° μνκ° μμ λ§μ νλμ μ± μμ§κ² ν¨μΌλ‘μ¨ μ½λμ κ°λ μ±κ³Ό μ μ§λ³΄μμ±μ ν₯μμν¨λ€.
β μΈμ μ¬μ©νλ©΄ μ’μκΉ?
- κ°μ²΄μ νλμ΄ μνμ λ°λΌ ν¬κ² λ¬λΌμ§ λ
- κ°μ²΄ μν λ³νμ λ°λ₯Έ 쑰건문(if-else, switch)μ΄ λ³΅μ‘ν΄μ§ λ
- μν μ ν λ‘μ§μ λ λͺ ννκ² νννκ³ μΆμ λ
- κ°μ²΄ μ§ν₯μ μΈ λ°©μμΌλ‘ μν κ΄λ¦¬λ₯Ό ꡬννκ³ μΆμ λ
β μΈμ νΌν΄μΌ ν κΉ?
- κ°λ¨ν μν κ΄λ¦¬λ§ νμν κ²½μ°(μ€λ²μμ§λμ΄λ§ κ°λ₯μ±)
- μνμ μ νμ΄ μ κ³ λ³κ²½μ΄ κ±°μ μλ κ²½μ°
- μ±λ₯μ΄ μ€μν μν©μμ μν κ°μ²΄ μμ± μ€λ²ν€λκ° λ¬Έμ κ° λ λ
- λ©λͺ¨λ¦¬ μ¬μ©λμ΄ μ€μν μ νλ νκ²½μμ
π‘ μλ°/μ€νλ§ νλ μμν¬μμμ μν ν¨ν΄
1. μ€νλ§ μ€ν μ΄νΈ λ¨Έμ (Spring Statemachine)
μ€νλ§μμλ μν ν¨ν΄μ ꡬννκΈ° μν μ μ© νλ μμν¬μΈ Spring Statemachineμ μ 곡νλ€.
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
public class OrderStateMachineExample {
// μ£Όλ¬Έ μν μ΄κ±°ν
public enum OrderStates {
CREATED, PAID, PROCESSING, SHIPPED, DELIVERED, CANCELLED
}
// μ£Όλ¬Έ μ΄λ²€νΈ μ΄κ±°ν
public enum OrderEvents {
PAY, PROCESS, SHIP, DELIVER, CANCEL
}
public static void main(String[] args) throws Exception {
// μν λ¨Έμ λΉλ μμ±
Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder();
// μν λ¨Έμ ꡬμ±
builder.configureStates()
.withStates()
.initial(OrderStates.CREATED)
.states(EnumSet.allOf(OrderStates.class));
// μν μ ν μ μ
builder.configureTransitions()
.withExternal()
.source(OrderStates.CREATED).target(OrderStates.PAID)
.event(OrderEvents.PAY)
.and()
.withExternal()
.source(OrderStates.PAID).target(OrderStates.PROCESSING)
.event(OrderEvents.PROCESS)
.and()
.withExternal()
.source(OrderStates.PROCESSING).target(OrderStates.SHIPPED)
.event(OrderEvents.SHIP)
.and()
.withExternal()
.source(OrderStates.SHIPPED).target(OrderStates.DELIVERED)
.event(OrderEvents.DELIVER)
.and()
.withExternal()
.source(OrderStates.CREATED).target(OrderStates.CANCELLED)
.event(OrderEvents.CANCEL)
.and()
.withExternal()
.source(OrderStates.PAID).target(OrderStates.CANCELLED)
.event(OrderEvents.CANCEL);
// μν λ¨Έμ μμ±
StateMachine<OrderStates, OrderEvents> stateMachine = builder.build();
stateMachine.start();
// μν λ¨Έμ μ¬μ©
System.out.println("Current state: " + stateMachine.getState().getId());
stateMachine.sendEvent(OrderEvents.PAY);
System.out.println("Current state: " + stateMachine.getState().getId());
stateMachine.sendEvent(OrderEvents.PROCESS);
System.out.println("Current state: " + stateMachine.getState().getId());
stateMachine.sendEvent(OrderEvents.SHIP);
System.out.println("Current state: " + stateMachine.getState().getId());
stateMachine.sendEvent(OrderEvents.DELIVER);
System.out.println("Current state: " + stateMachine.getState().getId());
}
}
2. JPA Entity μν κ΄λ¦¬
μλμ κ°μ΄ JPA μν°ν°μ μνλ₯Ό κ΄λ¦¬ν λλ μν ν¨ν΄μ μμ©ν μ μλ€.
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
private LocalDateTime orderDate;
private LocalDateTime paidDate;
private LocalDateTime shippedDate;
private LocalDateTime deliveredDate;
private LocalDateTime cancelledDate;
// μνμ λ°λ₯Έ νλμ μ²λ¦¬νλ μν κ°μ²΄
@Transient
private OrderState state;
public Order() {
this.orderDate = LocalDateTime.now();
this.status = OrderStatus.CREATED;
this.state = new CreatedOrderState();
}
// μν λ³κ²½ λ©μλ
public void setState(OrderState state) {
this.state = state;
this.status = state.getStatus();
}
// μ£Όλ¬Έ μ²λ¦¬ νλ
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void deliver() {
state.deliver(this);
}
public void cancel() {
state.cancel(this);
}
// getter/setter
// ...
}
// μν μΈν°νμ΄μ€
interface OrderState {
OrderStatus getStatus();
void pay(Order order);
void ship(Order order);
void deliver(Order order);
void cancel(Order order);
}
// μ£Όλ¬Έ μμ± μν
class CreatedOrderState implements OrderState {
@Override
public OrderStatus getStatus() {
return OrderStatus.CREATED;
}
@Override
public void pay(Order order) {
order.setPaidDate(LocalDateTime.now());
order.setState(new PaidOrderState());
}
@Override
public void ship(Order order) {
throw new IllegalStateException("Created order cannot be shipped directly");
}
@Override
public void deliver(Order order) {
throw new IllegalStateException("Created order cannot be delivered directly");
}
@Override
public void cancel(Order order) {
order.setCancelledDate(LocalDateTime.now());
order.setState(new CancelledOrderState());
}
}
// μ§λΆ μλ£ μν
class PaidOrderState implements OrderState {
// ꡬν...
}
// μ£Όλ¬Έ μν μ΄κ±°ν
enum OrderStatus {
CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}
π μ€μ μ¬μ© μ¬λ‘
- μν¬νλ‘μ° κ΄λ¦¬ μμ€ν
- λΉμ¦λμ€ νλ‘μΈμ€μ λ€μν λ¨κ³μ μν μ νμ κ΄λ¦¬
- μ: JIRAμ κ°μ μ΄μ νΈλνΉ μμ€ν μ ν°μΌ μν κ΄λ¦¬
- μ£Όλ¬Έ μ²λ¦¬ μμ€ν
- μ£Όλ¬Έ μμ±, κ²°μ , μ²λ¦¬, λ°°μ‘, μλ£ λ±μ μν κ΄λ¦¬
- κ° μνμμ νμ©λλ μμ κ³Ό λ€μ μνλ‘μ μ ν μ μ΄
- κ²μ μΊλ¦ν° AI
- μΊλ¦ν°μ μν(λκΈ°, μμ°°, μΆκ²©, 곡격 λ±)μ λ°λ₯Έ νλ μ μ΄
- μν©μ λ°λΌ μνλ₯Ό λμ μΌλ‘ μ ν
- λ€νΈμν¬ μ°κ²° κ΄λ¦¬
- TCP μ°κ²° μν(μ°κ²° μ€, μ°κ²°λ¨, μ’ λ£ μ€ λ±) κ΄λ¦¬
- μνμ λ°λ₯Έ ν¨ν· μ²λ¦¬ λ‘μ§ κ΅¬ν
- λ¬Έμ κ΄λ¦¬ μμ€ν
- λ¬Έμμ μν(μ΄μ, κ²ν μ€, μΉμΈλ¨, κ²μλ¨ λ±) κ΄λ¦¬
- μνμ λ°λ₯Έ μ κ·Ό κΆν λ° μμ μ μ΄
- UI μν κ΄λ¦¬
- μ¬μ©μ μΈν°νμ΄μ€ κ΅¬μ± μμμ μν(νμ±ν, λΉνμ±ν, μ νλ¨ λ±) κ΄λ¦¬
- React, Vue.jsμ κ°μ νλ‘ νΈμλ νλ μμν¬μμμ μν κ΄λ¦¬
- μ μ μκ±°λ κ²°μ μ²λ¦¬
- κ²°μ νλ‘μΈμ€μ λ€μν μν(κ²°μ μμ², μΉμΈ λκΈ°, μΉμΈλ¨, κ±°λΆλ¨ λ±) κ΄λ¦¬
- μνμ λ°λ₯Έ κ²°μ μμ€ν λμ μ μ΄