์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ๋Š” ๋””์ž์ธ ํŒจํ„ด 6๊ฐ€์ง€

๋””์ž์ธ ํŒจํ„ด. GoF 23๊ฐœ๋ฅผ ๋‹ค ์™ธ์šธ ํ•„์š”๋Š” ์—†๋‹ค. ์‹ฑ๊ธ€ํ†ค·์ „๋žต·ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ·ํŒฉํ† ๋ฆฌ·์˜ต์ €๋ฒ„·๋ฐ์ฝ”๋ ˆ์ดํ„ฐ — ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ๋Š” ์—ฌ์„ฏ ๊ฐ€์ง€ ํŒจํ„ด์„, ์Šคํ”„๋ง ์ฝ”๋“œ์— ์ด๋ฏธ ์–ด๋–ป๊ฒŒ ๋…น์•„ ์žˆ๋Š”์ง€์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•œ๋‹ค.


๋“ค์–ด๊ฐ€๋ฉฐ

๋””์ž์ธ ํŒจํ„ด์„ ์ฒ˜์Œ ๊ณต๋ถ€ํ•˜๋ฉด GoF 23๊ฐœ๋ฅผ ์™ธ์šฐ๋ ค๋‹ค ์ง€์นœ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ •์ž‘ ์‹ค๋ฌด์—์„œ๋Š” ๊ทธ์ค‘ ์†์— ๊ผฝ๋Š” ๋ช‡ ๊ฐœ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์“ด๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์šฐ๋ฆฌ๊ฐ€ ๋งค์ผ ์“ฐ๋Š” ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ ์ž์ฒด๊ฐ€ ๋””์ž์ธ ํŒจํ„ด ๋ฉ์–ด๋ฆฌ๋‹ค. ๋นˆ(Bean), JdbcTemplate, @EventListener ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ „๋ถ€ ํŒจํ„ด์˜ ๊ตฌํ˜„์ฒด๋‹ค.

๋””์ž์ธ ํŒจํ„ด์€ ์™ธ์šฐ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ด๋ฏธ ์“ฐ๊ณ  ์žˆ๋˜ ๊ฒƒ์— ์ด๋ฆ„์„ ๋ถ™์ด๋Š” ์ผ์— ๊ฐ€๊น๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋งŒ๋‚˜๋Š” ์—ฌ์„ฏ ๊ฐ€์ง€ ํŒจํ„ด์„ ๊ณจ๋ผ, ์–ด๋–ค ๋ฌธ์ œ๋ฅผ ํ‘ธ๋Š”์ง€์™€ ์Šคํ”„๋ง์ด ์ด๊ฑธ ์–ด๋””์„œ ์“ฐ๋Š”์ง€๋ฅผ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•œ๋‹ค. ์˜ˆ์‹œ๋Š” Java/Spring ๊ธฐ์ค€์ด๋‹ค.


1. ์‹ฑ๊ธ€ํ†ค (Singleton) — ์ธ์Šคํ„ด์Šค๋Š” ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค

ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋”ฑ ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค์–ด ๊ณต์œ ํ•˜๋Š” ํŒจํ„ด์ด๋‹ค. ์„ค์ • ๊ฐ์ฒด, ์ปค๋„ฅ์…˜ ํ’€, ์บ์‹œ์ฒ˜๋Ÿผ "์—ฌ๋Ÿฌ ๊ฐœ ์žˆ์„ ์ด์œ ๊ฐ€ ์—†๋Š”" ๊ฒƒ์— ์“ด๋‹ค.

์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ฉด ๋ณดํ†ต ์ด๋ ‡๊ฒŒ ํ•œ๋‹ค.

public class AppConfig {

    private static final AppConfig INSTANCE = new AppConfig();

    private AppConfig() {}   // ์ƒ์„ฑ์ž๋ฅผ ๋ง‰์•„ ์™ธ๋ถ€ ์ƒ์„ฑ ์ฐจ๋‹จ

    public static AppConfig getInstance() {
        return INSTANCE;
    }
}

์ƒ์„ฑ์ž๋ฅผ private์œผ๋กœ ๋ง‰๊ณ , ์ •์  ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜๋งŒ ๋…ธ์ถœํ•œ๋‹ค. ์–ด๋””์„œ getInstance()๋ฅผ ๋ถ€๋ฅด๋“  ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋Œ์•„์˜จ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค๋ฌด์—์„œ ์ด๋ ‡๊ฒŒ ์ง์ ‘ ์งœ๋Š” ๊ฒฝ์šฐ๋Š” ๋“œ๋ฌผ๋‹ค. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ ์ดˆ๊ธฐํ™” ๋ฌธ์ œ, ํ…Œ์ŠคํŠธ์˜ ์–ด๋ ค์›€ ๋•Œ๋ฌธ์ด๋‹ค. ์ง์ ‘ ๋งŒ๋“  ์‹ฑ๊ธ€ํ†ค์€ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ๊ฐ„ ๊ฒฉ๋ฆฌ๊ฐ€ ๊นจ์ง€๊ธฐ ์‰ฝ๊ณ , ๋ชฉ(mock)์œผ๋กœ ๋ฐ”๊ฟ”์น˜๊ธฐ๋„ ์–ด๋ ต๋‹ค.

์Šคํ”„๋ง ์–ด๋””์— ์žˆ๋‚˜: ์‚ฌ์‹ค ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์‹ฑ๊ธ€ํ†ค์„ ๋งค์ผ ์“ฐ๊ณ  ์žˆ๋‹ค. ์Šคํ”„๋ง ๋นˆ์˜ ๊ธฐ๋ณธ ์Šค์ฝ”ํ”„๊ฐ€ ์‹ฑ๊ธ€ํ†ค์ด๋‹ค. @Service, @Repository, @Component๋กœ ๋“ฑ๋ก๋œ ๋นˆ์€ ์ปจํ…Œ์ด๋„ˆ๋‹น ํ•˜๋‚˜๋งŒ ์ƒ์„ฑ๋ผ ๊ณต์œ ๋œ๋‹ค. ์ง์ ‘ getInstance()๋ฅผ ์งœ์ง€ ์•Š์•„๋„, ์Šคํ”„๋ง์ด ์‹ฑ๊ธ€ํ†ค ๊ด€๋ฆฌ๋ฅผ ๋Œ€์‹  ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์Šคํ”„๋ง์„ ์“ด๋‹ค๋ฉด ์‹ฑ๊ธ€ํ†ค์„ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ผ์€ ๊ฑฐ์˜ ์—†๋‹ค. ๋Œ€์‹  ๋นˆ์€ ์ƒํƒœ๋ฅผ ๊ฐ–์ง€ ์•Š๊ฒŒ(stateless) ์„ค๊ณ„ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•˜๋‹ค. ์‹ฑ๊ธ€ํ†ค ๋นˆ์— ๊ฐ€๋ณ€ ํ•„๋“œ๋ฅผ ๋‘๋ฉด ๋ชจ๋“  ์š”์ฒญ์ด ๊ทธ ํ•„๋“œ๋ฅผ ๊ณต์œ ํ•ด์„œ ๋™์‹œ์„ฑ ๋ฒ„๊ทธ๊ฐ€ ์ƒ๊ธด๋‹ค.

// ์œ„ํ—˜: ์‹ฑ๊ธ€ํ†ค ๋นˆ์— ๊ฐ€๋ณ€ ์ƒํƒœ
@Service
public class CounterService {
    private int count = 0;   // ๋ชจ๋“  ์š”์ฒญ์ด ๊ณต์œ  → ๋™์‹œ์„ฑ ๋ฒ„๊ทธ
    public void increment() { count++; }
}

์‹ฑ๊ธ€ํ†ค ๋นˆ์€ ๋ฌด์ƒํƒœ๋กœ ๋‘๊ณ , ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋‚˜ ์ง€์—ญ ๋ณ€์ˆ˜๋กœ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์›์น™์ด๋‹ค.


2. ์ „๋žต ํŒจํ„ด (Strategy) — if-else ์ง€์˜ฅ ํƒˆ์ถœ

๊ฐ€์žฅ ์ž์ฃผ ์“ฐ๊ณ , ํšจ๊ณผ๋„ ํ™•์‹คํ•œ ํŒจํ„ด์ด๋‹ค. ์•Œ๊ณ ๋ฆฌ์ฆ˜(์ „๋žต)์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•˜๊ณ , ์‹คํ–‰ ์‹œ์ ์— ๊ณจ๋ผ ์“ฐ๋Š” ๊ตฌ์กฐ๋‹ค.

๊ฒฐ์ œ ์ˆ˜๋‹จ์ด ๋Š˜์–ด๋‚˜๋Š” ์ƒํ™ฉ์„ ๋– ์˜ฌ๋ ค๋ณด์ž. ์ฒ˜์Œ์—” if-else๋กœ ์‹œ์ž‘ํ•œ๋‹ค.

// ์•ˆํ‹ฐํŒจํ„ด: ๊ฒฐ์ œ ์ˆ˜๋‹จ์ด ๋Š˜ ๋•Œ๋งˆ๋‹ค ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค
public PaymentResult pay(PaymentRequest request) {
    if (request.type() == CARD) {
        // ์นด๋“œ ๊ฒฐ์ œ ๋กœ์ง
    } else if (request.type() == BANK) {
        // ๊ณ„์ขŒ์ด์ฒด ๋กœ์ง
    } else if (request.type() == POINT) {
        // ํฌ์ธํŠธ ๊ฒฐ์ œ ๋กœ์ง
    }
    // ๊ฒฐ์ œ ์ˆ˜๋‹จ ์ถ”๊ฐ€ = ์ด ์ฝ”๋“œ ์ˆ˜์ • (OCP ์œ„๋ฐ˜)
}

๊ฒฐ์ œ ์ˆ˜๋‹จ์ด ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ์ด ๋ฉ”์„œ๋“œ๋ฅผ ๊ฑด๋“œ๋ ค์•ผ ํ•œ๋‹ค. ๊ฐœ๋ฐฉ-ํ์‡„ ์›์น™(OCP) ์œ„๋ฐ˜์ด๋‹ค. ์ „๋žต ํŒจํ„ด์œผ๋กœ ํ’€๋ฉด ์ด๋ ‡๊ฒŒ ๋œ๋‹ค.

// ์ „๋žต ์ธํ„ฐํŽ˜์ด์Šค
public interface PaymentProcessor {
    boolean supports(PaymentType type);
    PaymentResult process(PaymentRequest request);
}

@Component
public class CardPaymentProcessor implements PaymentProcessor {
    public boolean supports(PaymentType type) { return type == PaymentType.CARD; }
    public PaymentResult process(PaymentRequest request) {
        // ์นด๋“œ ๊ฒฐ์ œ ๋กœ์ง
        return PaymentResult.success();
    }
}

@Component
public class PointPaymentProcessor implements PaymentProcessor {
    public boolean supports(PaymentType type) { return type == PaymentType.POINT; }
    public PaymentResult process(PaymentRequest request) {
        // ํฌ์ธํŠธ ๊ฒฐ์ œ ๋กœ์ง
        return PaymentResult.success();
    }
}

์—ฌ๊ธฐ์„œ ์Šคํ”„๋ง์˜ ๊ฐ•๋ ฅํ•จ์ด ๋“œ๋Ÿฌ๋‚œ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์˜ List๋กœ ์ฃผ์ž…๋ฐ›์œผ๋ฉด, ์Šคํ”„๋ง์ด ๋ชจ๋“  ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋ชจ์•„์„œ ๋„ฃ์–ด์ค€๋‹ค.

@Service
public class PaymentService {

    private final List<PaymentProcessor> processors;  // ๋ชจ๋“  ๊ตฌํ˜„์ฒด ์ž๋™ ์ฃผ์ž…

    public PaymentService(List<PaymentProcessor> processors) {
        this.processors = processors;
    }

    public PaymentResult pay(PaymentRequest request) {
        return processors.stream()
                .filter(p -> p.supports(request.type()))
                .findFirst()
                .orElseThrow(() -> new UnsupportedPaymentException(request.type()))
                .process(request);
    }
}

์ด์ œ ์ƒˆ ๊ฒฐ์ œ ์ˆ˜๋‹จ์€ PaymentProcessor๋ฅผ ๊ตฌํ˜„ํ•œ @Component๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค. PaymentService๋Š” ํ•œ ์ค„๋„ ๊ณ ์น˜์ง€ ์•Š๋Š”๋‹ค. ์ด๊ฒŒ ์ „๋žต ํŒจํ„ด + ์Šคํ”„๋ง DI์˜ ์ •์„ ์กฐํ•ฉ์ด๋‹ค.


3. ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ (Template Method) — ํ๋ฆ„์€ ๊ณ ์ •, ์ผ๋ถ€๋งŒ ๋ณ€๊ฒฝ

์ „์ฒด ์ฒ˜๋ฆฌ ํ๋ฆ„์€ ๊ณ ์ •ํ•˜๋˜, ์ผ๋ถ€ ๋‹จ๊ณ„๋งŒ ํ•˜์œ„ ํด๋ž˜์Šค๊ฐ€ ๋ฐ”๊พธ๋„๋ก ํ•˜๋Š” ํŒจํ„ด์ด๋‹ค. "๋ผˆ๋Œ€๋Š” ๊ฐ™๊ณ  ์†๋งŒ ๋‹ค๋ฅธ" ์ž‘์—…์— ์“ด๋‹ค.

๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ → ๊ฒ€์ฆํ•˜๊ณ  → ์ €์žฅํ•˜๋Š” ํ๋ฆ„์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. ํ๋ฆ„์€ ํ•ญ์ƒ ๊ฐ™์ง€๋งŒ ๋ฐ์ดํ„ฐ ์†Œ์Šค(CSV, API, DB)๋งˆ๋‹ค ์„ธ๋ถ€ ๊ตฌํ˜„์ด ๋‹ค๋ฅด๋‹ค.

public abstract class DataImporter {

    // ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ: ํ๋ฆ„์„ ๊ณ ์ •ํ•œ๋‹ค (final๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œ ๋ฐฉ์ง€)
    public final void importData(String source) {
        List<Row> raw = read(source);          // ๋‹จ๊ณ„ 1
        List<Row> valid = validate(raw);       // ๋‹จ๊ณ„ 2
        save(valid);                            // ๋‹จ๊ณ„ 3
        log.info("{}๊ฑด ์ž„ํฌํŠธ ์™„๋ฃŒ", valid.size());
    }

    // ํ•˜์œ„ ํด๋ž˜์Šค๊ฐ€ ์ฑ„์šฐ๋Š” ๋ถ€๋ถ„
    protected abstract List<Row> read(String source);
    protected abstract List<Row> validate(List<Row> rows);
    protected abstract void save(List<Row> rows);
}

public class CsvDataImporter extends DataImporter {
    protected List<Row> read(String source) { /* CSV ํŒŒ์‹ฑ */ }
    protected List<Row> validate(List<Row> rows) { /* CSV ๊ฒ€์ฆ */ }
    protected void save(List<Row> rows) { /* DB ์ €์žฅ */ }
}

importData()์˜ ์ˆœ์„œ๋Š” ๋ˆ„๊ตฌ๋„ ๋ชป ๋ฐ”๊พผ๋‹ค. ๊ฐ ๋‹จ๊ณ„์˜ ๊ตฌํ˜„๋งŒ ๋‹ฌ๋ผ์ง„๋‹ค.

์Šคํ”„๋ง ์–ด๋””์— ์žˆ๋‚˜: JdbcTemplate์ด ๋Œ€ํ‘œ์ ์ด๋‹ค. ์ปค๋„ฅ์…˜ ์–ป๊ธฐ → ์ฟผ๋ฆฌ ์‹คํ–‰ → ๊ฒฐ๊ณผ ๋งคํ•‘ → ์ปค๋„ฅ์…˜ ๋ฐ˜ํ™˜์ด๋ผ๋Š” ํ๋ฆ„์€ ๊ณ ์ •๋ผ ์žˆ๊ณ , ๊ฐœ๋ฐœ์ž๋Š” RowMapper๋กœ "๊ฒฐ๊ณผ ๋งคํ•‘" ๋ถ€๋ถ„๋งŒ ์ฑ„์šด๋‹ค. RestTemplate๋„ ๊ฐ™์€ ๊ตฌ์กฐ๋‹ค.


4. ํŒฉํ† ๋ฆฌ ํŒจํ„ด (Factory) — ๊ฐ์ฒด ์ƒ์„ฑ ์ฑ…์ž„ ๋ถ„๋ฆฌ

๊ฐ์ฒด ์ƒ์„ฑ ๋กœ์ง์„ ํ•œ๊ณณ์œผ๋กœ ๋ชจ์œผ๋Š” ํŒจํ„ด์ด๋‹ค. new๋ฅผ ์ง์ ‘ ์“ฐ๋ฉด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๊ฐ€ ๊ตฌ์ฒด ํด๋ž˜์Šค์— ๋ฌถ์ด๋Š”๋ฐ, ํŒฉํ† ๋ฆฌ๊ฐ€ ๊ทธ ๊ฒฐ์ •์„ ๋Œ€์‹ ํ•œ๋‹ค.

public class NotificationFactory {

    public static Notification create(Channel channel) {
        return switch (channel) {
            case EMAIL -> new EmailNotification();
            case SMS   -> new SmsNotification();
            case PUSH  -> new PushNotification();
        };
    }
}

// ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์€ ๊ตฌ์ฒด ํด๋ž˜์Šค๋ฅผ ๋ชฐ๋ผ๋„ ๋œ๋‹ค
Notification noti = NotificationFactory.create(Channel.EMAIL);
noti.send("์ฃผ๋ฌธ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค");

ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋Š” EmailNotification์ด๋ผ๋Š” ๊ตฌ์ฒด ํƒ€์ž…์„ ๋ชฐ๋ผ๋„ ๋œ๋‹ค. ์ƒˆ ์ฑ„๋„์ด ์ƒ๊ธฐ๋ฉด ํŒฉํ† ๋ฆฌ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋œ๋‹ค.

์Šคํ”„๋ง ์–ด๋””์— ์žˆ๋‚˜: ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ ์ž์ฒด๊ฐ€ ๊ฑฐ๋Œ€ํ•œ ํŒฉํ† ๋ฆฌ๋‹ค. BeanFactory๋ผ๋Š” ์ด๋ฆ„๋ถ€ํ„ฐ๊ฐ€ ๊ทธ๋ ‡๋‹ค. @Bean ๋ฉ”์„œ๋“œ๋„ ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ํŒจํ„ด์˜ ๊ตฌํ˜„์ด๋‹ค. ์šฐ๋ฆฌ๊ฐ€ new๋กœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊บผ๋‚ด ์“ฐ๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ํŒฉํ† ๋ฆฌ ํŒจํ„ด ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.


5. ์˜ต์ €๋ฒ„ ํŒจํ„ด (Observer) — ๋А์Šจํ•˜๊ฒŒ ์—ฐ๊ฒฐํ•˜๊ธฐ

ํ•œ ๊ฐ์ฒด์˜ ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด, ๊ทธ๊ฒƒ์„ ๊ตฌ๋…ํ•˜๋Š” ๊ฐ์ฒด๋“ค์—๊ฒŒ ์ž๋™์œผ๋กœ ํ†ต์ง€ํ•˜๋Š” ํŒจํ„ด์ด๋‹ค. ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž๋ฅผ ๋А์Šจํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•œ๋‹ค.

์ฃผ๋ฌธ์ด ์ƒ์„ฑ๋˜๋ฉด ์ด๋ฉ”์ผ ๋ฐœ์†ก, ์žฌ๊ณ  ์ฐจ๊ฐ, ํ†ต๊ณ„ ์ง‘๊ณ„๊ฐ€ ์ผ์–ด๋‚˜์•ผ ํ•œ๋‹ค๊ณ  ํ•˜์ž. ์ด๊ฑธ OrderService์— ๋‹ค ์šฑ์—ฌ๋„ฃ์œผ๋ฉด ๊ฒฐํ•ฉ๋„๊ฐ€ ํญ๋ฐœํ•œ๋‹ค.

// ์•ˆํ‹ฐํŒจํ„ด: OrderService๊ฐ€ ๋ชจ๋“  ํ›„์† ์ž‘์—…์„ ์ง์ ‘ ํ˜ธ์ถœ
public void createOrder(OrderRequest request) {
    Order order = orderRepository.save(...);
    emailService.sendConfirmation(order);    // ์ด๋ฉ”์ผ๊ณผ ๊ฒฐํ•ฉ
    inventoryService.decrease(order);         // ์žฌ๊ณ ์™€ ๊ฒฐํ•ฉ
    statisticsService.record(order);          // ํ†ต๊ณ„์™€ ๊ฒฐํ•ฉ
}

์˜ต์ €๋ฒ„ ํŒจํ„ด(์Šคํ”„๋ง ์ด๋ฒคํŠธ)์œผ๋กœ ํ’€๋ฉด ๋ฐœํ–‰๊ณผ ์ฒ˜๋ฆฌ๊ฐ€ ๋ถ„๋ฆฌ๋œ๋‹ค.

// ์ด๋ฒคํŠธ ์ •์˜
public record OrderCreatedEvent(Long orderId) {}

// ๋ฐœํ–‰์ž: ์ฃผ๋ฌธ ์ƒ์„ฑ ํ›„ ์ด๋ฒคํŠธ๋งŒ ๋˜์ง„๋‹ค
@Service
public class OrderService {

    private final ApplicationEventPublisher publisher;

    public void createOrder(OrderRequest request) {
        Order order = orderRepository.save(request.toEntity());
        publisher.publishEvent(new OrderCreatedEvent(order.getId()));
        // ์ดํ›„ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ OrderService๋Š” ๋ชจ๋ฅธ๋‹ค
    }
}

// ๊ตฌ๋…์ž(์˜ต์ €๋ฒ„): ๊ฐ์ž ์•Œ์•„์„œ ๋ฐ˜์‘ํ•œ๋‹ค
@Component
public class OrderEventHandler {

    @EventListener
    public void sendEmail(OrderCreatedEvent event) {
        // ์ฃผ๋ฌธ ํ™•์ธ ์ด๋ฉ”์ผ ๋ฐœ์†ก
    }

    @EventListener
    public void decreaseInventory(OrderCreatedEvent event) {
        // ์žฌ๊ณ  ์ฐจ๊ฐ
    }
}

OrderService๋Š” ์ด๋ฉ”์ผ·์žฌ๊ณ ·ํ†ต๊ณ„์˜ ์กด์žฌ์กฐ์ฐจ ๋ชจ๋ฅธ๋‹ค. ํ›„์† ์ž‘์—…์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์œผ๋ฉด @EventListener ๋ฉ”์„œ๋“œ๋งŒ ๋Š˜๋ฆฌ๋ฉด ๋œ๋‹ค.

์Šคํ”„๋ง ์–ด๋””์— ์žˆ๋‚˜: ApplicationEvent์™€ @EventListener๊ฐ€ ์˜ต์ €๋ฒ„ ํŒจํ„ด ๊ทธ ์ž์ฒด๋‹ค. @TransactionalEventListener๋ฅผ ์“ฐ๋ฉด ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ดํ›„์—๋งŒ ์˜ต์ €๋ฒ„๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.


6. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด (Decorator) — ๊ธฐ๋Šฅ์„ ๊ฒน๊ฒน์ด ์ž…ํžˆ๊ธฐ

์›๋ž˜ ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋‘๊ณ , ๊ฐ™์€ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ฐ์‹ธ ๊ธฐ๋Šฅ์„ ๋ง๋ถ™์ด๋Š” ํŒจํ„ด์ด๋‹ค. ์ƒ์† ๋Œ€์‹  ์กฐํ•ฉ์œผ๋กœ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•œ๋‹ค.

public interface Notifier {
    void send(String message);
}

// ๊ธฐ๋ณธ ๊ตฌํ˜„
public class SlackNotifier implements Notifier {
    public void send(String message) {
        // ์Šฌ๋ž™์œผ๋กœ ์ „์†ก
    }
}

// ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ: ๊ฐ™์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์›๋ณธ์„ ๊ฐ์‹ผ๋‹ค
public class LoggingNotifier implements Notifier {

    private final Notifier delegate;

    public LoggingNotifier(Notifier delegate) {
        this.delegate = delegate;
    }

    public void send(String message) {
        log.info("์ „์†ก ์‹œ์ž‘: {}", message);
        delegate.send(message);            // ์›๋ณธ ๋™์ž‘
        log.info("์ „์†ก ์™„๋ฃŒ");
    }
}

// ์‚ฌ์šฉ: ๊ฒน๊ฒน์ด ๊ฐ์‹ผ๋‹ค
Notifier notifier = new LoggingNotifier(new RetryNotifier(new SlackNotifier()));
notifier.send("๋ฐฐํฌ ์™„๋ฃŒ");   // ๋กœ๊น… → ์žฌ์‹œ๋„ → ์‹ค์ œ ์ „์†ก

์›๋ณธ SlackNotifier๋Š” ์†๋Œ€์ง€ ์•Š๊ณ  ๋กœ๊น…, ์žฌ์‹œ๋„ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๋ฐ”๊นฅ์—์„œ ์ž…ํ˜”๋‹ค.

์Šคํ”„๋ง/์ž๋ฐ” ์–ด๋””์— ์žˆ๋‚˜: ์ž๋ฐ” I/O์˜ new BufferedReader(new FileReader(...))๊ฐ€ ๊ต๊ณผ์„œ์ ์ธ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋‹ค. ์Šคํ”„๋ง์—์„œ๋Š” AOP ํ”„๋ก์‹œ๊ฐ€ ๊ฐ™์€ ์›๋ฆฌ๋กœ ๋™์ž‘ํ•œ๋‹ค. @Transactional์ด ๋ถ™์€ ๋นˆ์€, ์‹ค์ œ๋กœ๋Š” ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ๋ง์ž…ํžŒ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ฃผ์ž…๋œ๋‹ค.


ํ•œ๋ˆˆ์— ์ •๋ฆฌ

ํŒจํ„ด ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ ์Šคํ”„๋ง/์ž๋ฐ”์—์„œ์˜ ์˜ˆ
์‹ฑ๊ธ€ํ†ค (Singleton) ์ธ์Šคํ„ด์Šค๋ฅผ ํ•˜๋‚˜๋งŒ ๊ณต์œ  ์Šคํ”„๋ง ๋นˆ ๊ธฐ๋ณธ ์Šค์ฝ”ํ”„
์ „๋žต (Strategy) ์กฐ๊ฑด๋งˆ๋‹ค ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ if-else ์—†์ด List<Interface> ๋นˆ ์ฃผ์ž…
ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ ํ๋ฆ„์€ ๊ฐ™๊ณ  ์ผ๋ถ€ ๋‹จ๊ณ„๋งŒ ๋‹ค๋ฅผ ๋•Œ JdbcTemplate, RestTemplate
ํŒฉํ† ๋ฆฌ (Factory) ๊ฐ์ฒด ์ƒ์„ฑ ์ฑ…์ž„์„ ๋ถ„๋ฆฌ BeanFactory, @Bean
์˜ต์ €๋ฒ„ (Observer) ๋ฐœํ–‰์ž-๊ตฌ๋…์ž๋ฅผ ๋А์Šจํ•˜๊ฒŒ ์—ฐ๊ฒฐ @EventListener
๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์›๋ณธ์„ ์•ˆ ๊ฑด๋“œ๋ฆฌ๊ณ  ๊ธฐ๋Šฅ ํ™•์žฅ AOP ํ”„๋ก์‹œ, @Transactional

๋งˆ์น˜๋ฉฐ

๋””์ž์ธ ํŒจํ„ด์„ 23๊ฐœ์”ฉ ์™ธ์šฐ๋Š” ๊ฑด ํฐ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค. ๊ทธ๋ณด๋‹ค "์ง€๊ธˆ ๋‚ด๊ฐ€ ๊ฒช๋Š” ๋ฌธ์ œ๊ฐ€ ์–ด๋–ค ํŒจํ„ด์œผ๋กœ ํ’€๋ฆฌ๋Š”๊ฐ€" ๋ฅผ ๋– ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฒŒ ์ค‘์š”ํ•˜๋‹ค. ๊ฒฐ์ œ ์ˆ˜๋‹จ์ด ์ž๊พธ ๋Š˜๋ฉด ์ „๋žต ํŒจํ„ด, ํ›„์† ์ž‘์—…์ด ์ค„์ค„์ด ๋ถ™์œผ๋ฉด ์˜ต์ €๋ฒ„ ํŒจํ„ด ํ•˜๋Š” ์‹์œผ๋กœ.

๊ทธ๋ฆฌ๊ณ  ํ•œ ๊ฐ€์ง€ ๋”. ์šฐ๋ฆฌ๊ฐ€ ๋งค์ผ ์“ฐ๋Š” ์Šคํ”„๋ง์€ ์ด ํŒจํ„ด๋“ค์˜ ๊ฑฐ๋Œ€ํ•œ ์ „์‹œ์žฅ์ด๋‹ค. @EventListener๋ฅผ ์“ธ ๋•Œ "์•„, ์ด๊ฒŒ ์˜ต์ €๋ฒ„๊ตฌ๋‚˜" ํ•˜๊ณ  ์ธ์‹ํ•˜๋Š” ์ˆœ๊ฐ„, ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์„ค๊ณ„ ์˜๋„๊ฐ€ ๋ณด์ด๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค. ํŒจํ„ด์„ ์•ˆ๋‹ค๋Š” ๊ฑด ๊ฒฐ๊ตญ ๋‚จ์ด ์งœ๋†“์€ ์ข‹์€ ์ฝ”๋“œ๋ฅผ ๋” ์ž˜ ์ฝ๊ฒŒ ๋˜๋Š” ์ผ์ด๊ธฐ๋„ ํ•˜๋‹ค.

ํŒจํ„ด์€ ๋ชฉ์ ์ด ์•„๋‹ˆ๋ผ ๋„๊ตฌ๋‹ค. ํŒจํ„ด์„ ์œ„ํ•œ ํŒจํ„ด์€ ์˜คํžˆ๋ ค ์ฝ”๋“œ๋ฅผ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค. ๋ฌธ์ œ๊ฐ€ ๋จผ์ € ๋ณด์ด๊ณ , ๊ทธ๋‹ค์Œ์— ํŒจํ„ด์ด ๋”ฐ๋ผ์˜ค๋Š” ์ˆœ์„œ๊ฐ€ ๋งž๋‹ค.


์ฐธ๊ณ  ์ถœ์ฒ˜