// 이 파일이 하는 일: "디자인 패턴 실전" 코스 — GoF 23개를 외우는 대신 '문제→해법'으로
// 읽는 법에서 출발해, 생성·구조·행동 패턴의 핵심 6개, 스프링이 숨겨 둔 패턴들, SOLID라는
// 나침반, 그리고 안티패턴을 되돌리는 용기까지 7개 섹션으로 안내하는 정적 학습 페이지.
// 프레이밍은 "우리 코드가 실물 교재" — market 서비스의 시세 프로바이더 분기와 MarketBus를
// 예제 삼아, 패턴이 교과서가 아니라 지금 내 코드의 문제를 푸는 도구임을 체감시킨다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_PATTERN_MAP = `패턴을 읽는 올바른 순서: 이름 → X, 문제 → 해법 → O

"자주 만나는 문제"                    "검증된 해법(패턴)"
──────────────────────────────────────────────────────
생성 로직이 여기저기 흩어져 있다   →   팩토리 (생성을 한곳에 모은다)
생성자 인자가 7개를 넘어간다       →   빌더 (조립 과정을 읽기 좋게)
외부 API 모양이 우리와 안 맞는다   →   어댑터 (모양을 번역하는 겉옷)
분기(if/switch)가 계속 자란다      →   전략 (분기를 다형성으로)
한 사건을 여럿이 알아야 한다       →   옵저버 (구독-발행으로 느슨하게)
흐름은 같은데 일부만 다르다        →   템플릿 메서드 (뼈대 고정, 차이만 위임)

GoF 책의 23개 중 실무에서 매일 만나는 건 이 6개 안팎이에요.
나머지는 "그런 게 있다"만 알아 두고, 문제를 만났을 때 찾아보면 됩니다.`;

const CODE_BUILDER = `// 생성자 인자가 늘어나면 호출부가 암호문이 된다
Article a = new Article("삼성전자 2분기 실적", body, "KR", true, false, null, 3);
// true가 뭐고 false가 뭔지, null은 왜 있는지 — 아무도 모른다

// 빌더: '무엇을 넣는지'가 이름으로 드러난다
Article a = Article.builder()
    .title("삼성전자 2분기 실적")
    .body(body)
    .marketRegion("KR")
    .premium(true)        // 아, 프리미엄 글이구나
    .pinned(false)        // 상단 고정은 아니고
    .priority(3)
    .build();

// 포인트: 빌더의 가치는 '멋'이 아니라 가독성과 실수 방지.
// 인자 2~3개짜리 객체에 빌더를 씌우는 건 과용입니다.`;

const CODE_SINGLETON = `// 교과서 싱글턴 — 인스턴스를 딱 하나만 만들고 전역에서 공유
public class JwtService {
    private static final JwtService INSTANCE = new JwtService();
    private JwtService() {}                    // 밖에서 new 금지
    public static JwtService getInstance() { return INSTANCE; }
}

// 그런데 스프링에선 이렇게 쓸 일이 거의 없어요.
@Service
public class JwtService { ... }
// @Service·@Component 빈의 기본 스코프가 이미 '싱글턴'이기 때문.
// 컨테이너가 인스턴스 하나를 만들어 필요한 곳에 주입해 줍니다.
// 직접 만든 싱글턴은 테스트에서 갈아 끼우기 어렵지만(정적 결합),
// 스프링 빈은 테스트에서 가짜(Mock)로 교체할 수 있죠.
// 교훈: 프레임워크가 이미 준 패턴을 손으로 다시 만들지 말 것.`;

const CODE_ADAPTER = `// 외부 시세 API 3곳 — 응답 모양이 제각각이다
// A사: { last: 71000, chg: -1.2 }
// B사: { price: "71000", changeRate: "-1.2%" }
// 키움 브리지: { cur_prc: "-71000", flu_rt: "-1.20" }  (부호가 접두어!)

// 어댑터: 바깥의 낯선 모양을 '우리 표준 모양'으로 번역하는 겉옷
public interface QuoteSource {
    Quote fetch(String symbol);     // 우리 서비스가 아는 단 하나의 모양
}

public class KiwoomAdapter implements QuoteSource {
    private final KiwoomBridgeClient bridge;   // 낯선 외부 세계

    public Quote fetch(String symbol) {
        var raw = bridge.currentPrice(symbol);
        // 번역은 전부 여기서: 부호 접두어 제거, 문자열→숫자, 필드명 통일
        long price = Math.abs(Long.parseLong(raw.curPrc()));
        return new Quote(symbol, price, parseRate(raw.fluRt()));
    }
}

// 효과: 외부 API가 아무리 괴상해도, 그 괴상함이 어댑터 안에 갇힌다.
// 나머지 코드는 Quote 하나만 알면 된다.`;

const CODE_STRATEGY_BEFORE = `// [Before] market 서비스의 시세 조회 — 프로바이더 분기가 메서드마다 반복
public Quote getQuote(String market, String symbol) {
    if (market.equals("KR")) {
        var raw = kiwoomBridge.currentPrice(symbol);
        return toQuote(raw);
    } else if (market.equals("US")) {
        var raw = usVendorClient.quote(symbol);
        return convertUs(raw);
    } else if (market.equals("CRYPTO")) {
        var raw = cryptoClient.ticker(symbol);
        return convertCrypto(raw);
    }
    throw new IllegalArgumentException("unknown market: " + market);
}
// 같은 if 사다리가 getQuote·getOrderbook·getCandles… 곳곳에 복붙된다.
// 프로바이더 하나 추가 = 흩어진 분기를 전부 찾아 고치기 (누락하면 버그)`;

const CODE_STRATEGY_AFTER = `// [After] 전략 패턴 — '시장별로 다른 부분'을 각자의 클래스로 분리
public interface QuoteProvider {
    String market();                  // 자기가 담당하는 시장
    Quote getQuote(String symbol);
    Orderbook getOrderbook(String symbol);
}

@Component class KiwoomProvider implements QuoteProvider {
    public String market() { return "KR"; }
    public Quote getQuote(String symbol) { /* 키움 브리지 호출 */ }
    ...
}
@Component class UsVendorProvider implements QuoteProvider { /* "US" */ }
@Component class CryptoProvider   implements QuoteProvider { /* "CRYPTO" */ }

// 선택 로직은 단 한 곳 — 스프링이 구현체들을 List로 다 모아 준다
@Service
public class QuoteService {
    private final Map<String, QuoteProvider> providers;

    QuoteService(List<QuoteProvider> all) {    // 구현체 전부 자동 주입!
        this.providers = new HashMap<>();
        for (var p : all) providers.put(p.market(), p);
    }

    public Quote getQuote(String market, String symbol) {
        var p = providers.get(market);
        if (p == null) throw new IllegalArgumentException(market);
        return p.getQuote(symbol);             // if 사다리가 사라졌다
    }
}`;

const CODE_STRATEGY_COST = `새 프로바이더(예: 일본 주식 "JP") 추가 비용 비교

[Before — if 분기]                  [After — 전략 패턴]
① getQuote에 else-if 추가           ① JpProvider 클래스 하나 신규 작성
② getOrderbook에 else-if 추가       ② 끝. (@Component만 붙이면
③ getCandles에 else-if 추가             스프링이 List에 자동 포함)
④ …분기가 있는 곳을 전부 수색
⑤ 하나라도 빠뜨리면 런타임 버그      기존 파일 수정: 0개
                                    → 기존 코드를 안 건드리니
기존 파일 수정: 3~N개                  기존 기능이 깨질 수가 없다
검증 범위: 수정한 파일 전부          검증 범위: 새 클래스 하나`;

const CODE_OBSERVER = `// 옵저버/Pub-Sub — 우리 MarketBus가 실물 교재
// "시세 틱 하나가 도착했다"를 알아야 하는 곳: SSE 스트림, 캐시, 알림 판정…
// 발행자가 구독자를 일일이 알면 → 새 구독자가 생길 때마다 발행자를 수정해야 한다.

// 그래서 market 서비스는 Valkey Pub/Sub 채널(market.ticks)에 '발행'만 하고,
publisher.publish("market.ticks", tickJson);   // 누가 듣는지 모른 채 발행

// 듣고 싶은 쪽이 각자 '구독'한다.
subscriber.subscribe("market.ticks", (msg) -> {
    var tick = parse(msg);
    sseEmitters.forEach(e -> e.send(tick));    // SSE로 브라우저에 전달
});

// 발행자와 구독자는 서로의 존재를 모른다 — 채널 이름만 공유한다.
// 옵저버(같은 프로세스 안, 리스트로 직접 호출)와
// Pub/Sub(중간 브로커를 경유, 프로세스 경계를 넘음)은 형제 관계:
// '느슨한 결합으로 사건을 퍼뜨린다'는 핵심이 같다.`;

const CODE_TEMPLATE = `// 템플릿 메서드 — 공통 흐름은 부모가 고정, 다른 부분만 자식에게 위임
public abstract class MarketCollector {
    // 뼈대(템플릿): 수집의 순서는 누구도 못 바꾼다
    public final void collect() {
        var raw = fetchRaw();          // ← 여기만 시장마다 다름
        var ticks = normalize(raw);    // ← 여기도 시장마다 다름
        validate(ticks);               // 공통: 이상값 걸러내기
        publish(ticks);                // 공통: MarketBus로 발행
    }
    protected abstract RawData fetchRaw();
    protected abstract List<Tick> normalize(RawData raw);
    private void validate(List<Tick> t) { /* 공통 검증 */ }
    private void publish(List<Tick> t)  { /* 공통 발행 */ }
}

public class KrCollector extends MarketCollector {
    protected RawData fetchRaw() { /* 키움 브리지에서 */ }
    protected List<Tick> normalize(RawData raw) { /* 부호 접두어 처리 */ }
}
// 전략과의 차이: 전략은 '알고리즘 전체'를 갈아 끼우고(합성),
// 템플릿 메서드는 '흐름 속 빈칸'만 갈아 끼운다(상속).
// 요즘은 상속 대신 람다를 넘기는 '템플릿 콜백' 변형을 더 많이 씁니다(섹션 5).`;

const CODE_SPRING_DI = `// 스프링을 쓰는 순간, 우리는 이미 패턴 위에 서 있다

// ① DI(의존성 주입) = 의존성 역전 원칙(DIP)의 구현
@Service
public class AuthService {
    private final TokenStore tokenStore;        // 인터페이스에 의존
    AuthService(TokenStore tokenStore) {        // 구현체는 밖에서 주입
        this.tokenStore = tokenStore;           // (내가 new 하지 않는다)
    }
}
// AuthService는 TokenStore가 Valkey인지 메모리인지 모른다.
// '무엇을 쓸지'의 결정권이 사용처에서 조립자(컨테이너)로 역전(IoC)됐다.

// ② @Transactional = 프록시 패턴
// 스프링은 이 클래스를 감싼 '대리인(proxy)' 객체를 만들어 빈으로 등록한다.
// 호출 → [프록시: 트랜잭션 시작] → 진짜 메서드 → [프록시: commit/rollback]
// 우리가 쓴 적 없는 트랜잭션 코드가 실행되는 비밀이 이것.
// 같은 클래스 안에서 this.method()로 부르면 프록시를 안 거쳐
// @Transactional이 무시되는 함정도, 프록시 구조를 알면 당연해진다.

// ③ JdbcTemplate·RestClient의 Template = 템플릿 콜백
// 연결 열기 → [내 코드] → 예외 처리 → 연결 닫기
// 지루한 앞뒤는 템플릿이, 가운데 빈칸만 내가 람다로 채운다.`;

const CODE_SOLID = `SOLID — 패턴을 '언제' 쓸지 알려주는 다섯 개의 나침반

S 단일 책임   한 클래스가 바뀌는 이유는 하나여야 한다
              → 시세 조회와 프로바이더 선택이 한 메서드에? 분리 신호.
O 개방 폐쇄   확장에는 열리고, 수정에는 닫히게
              → "새 프로바이더 추가에 기존 코드 수정 0개" = 전략 패턴의 효과.
L 리스코프    자식은 부모 자리에 그대로 끼워도 동작해야 한다
              → QuoteProvider 구현체는 어느 것을 꽂아도 계약이 같아야.
I 인터페이스  뚱뚱한 인터페이스 하나보다 날씬한 여러 개
   분리       → 호가를 지원 안 하는 프로바이더에게 getOrderbook을 강요하지 말 것.
D 의존성 역전 구체가 아니라 추상에 의존하라
              → QuoteService는 KiwoomProvider가 아닌 QuoteProvider를 본다.

순서를 기억하세요: SOLID가 원칙(왜), 패턴은 그 원칙의 구현 기법(어떻게).
패턴 이름을 하나도 몰라도 SOLID를 지키면 절반은 성공입니다.`;

const CODE_SMELLS = `전략 패턴 도입을 알리는 코드 냄새 체크리스트

[ ] 같은 모양의 if/switch 사다리가 두 곳 이상에 복붙되어 있다
[ ] 새 케이스를 추가할 때마다 '분기가 있는 곳 수색'부터 해야 한다
[ ] 분기 하나의 몸통이 화면 반 이상을 차지한다
[ ] "여기 고치면 다른 시장 것도 깨지지 않을까" 겁이 난다

반대로, 도입을 미뤄야 할 신호

[ ] 분기가 단 한 곳에만 있고, 각 가지가 서너 줄이다
[ ] 케이스가 2개뿐이고 늘어날 계획이 없다
[ ] "언젠가 필요할지도 몰라서" 미리 만들고 있다  ← 가장 위험

리팩터링의 철칙: 테스트 먼저.
① 현재 동작을 고정하는 테스트 작성 → ② 초록 확인 →
③ 패턴으로 구조 변경(동작은 그대로) → ④ 테스트가 여전히 초록인지 확인.
안전망 없는 리팩터링은 리팩터링이 아니라 도박입니다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '패턴 읽는 법' },
  { n: 2, label: '생성 패턴' },
  { n: 3, label: '구조 패턴' },
  { n: 4, label: '행동 패턴' },
  { n: 5, label: '스프링 속 패턴' },
  { n: 6, label: 'SOLID 나침반' },
  { n: 7, label: '안티패턴' },
];

export default function Lv3DesignPatternsPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 고급 과정</div>
        <h1>🧩 디자인 패턴 실전</h1>
        <p>
          외우는 패턴 말고 <strong>쓰는 패턴</strong> — 서비스를 만들어 본 사람이라면 이미
          패턴이 풀어 줄 문제를 코드 안에 갖고 있어요. GoF 23개를 암기하는 대신,
          <strong> '이 문제엔 이 해법'</strong>이라는 대응표를 우리 코드(시세 프로바이더 분기,
          MarketBus)로 직접 확인합니다. 마지막엔 패턴을 <strong>되돌리는 용기</strong>까지요.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 60분</span>
          <span className="chip">실습: 시세 프로바이더 Strategy 재설계</span>
          <span className="chip">선수 지식: 서비스 개발 경험·Java·스프링</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="패턴을 배우는 법" sub="외우지 말고, '문제→해법'으로 읽는다">
        <p>
          디자인 패턴 공부가 실패하는 전형적인 경로가 있어요. GoF(Gang of Four) 책의{' '}
          <strong>23개 패턴 이름을 앞에서부터 외우기</strong> 시작하는 거죠. 싱글턴, 팩토리
          메서드, 추상 팩토리, 프로토타입… 열 개쯤에서 지치고, 정작 내 코드의 if 지옥 앞에선
          아무것도 떠오르지 않습니다. 순서가 거꾸로였기 때문이에요.
        </p>
        <p>
          패턴의 정체는 <strong>"자주 만나는 문제에 대한, 선배들이 검증한 해법의 이름"</strong>
          입니다. 그러니 읽는 순서도 <strong>문제가 먼저</strong>여야 해요. "생성 로직이
          흩어졌네 → 팩토리", "분기가 자꾸 자라네 → 전략". 문제를 겪어 본 사람만 해법의 가치를
          알아봅니다 — 여러분이 이 과정의 대상인 이유예요.
        </p>
        <Code>{CODE_PATTERN_MAP}</Code>
        <p>
          그리고 처음부터 분명히 해 둘 것: <strong>패턴은 목적이 아니라 도구</strong>입니다.
          패턴을 많이 쓴 코드가 좋은 코드가 아니라, <strong>문제에 맞는 패턴을 문제가 생겼을
          때</strong> 쓴 코드가 좋은 코드예요. 도구 자랑을 위해 문제를 발명하는 순간,
          패턴은 오버엔지니어링이라는 이름의 빚이 됩니다(섹션 7에서 다시).
        </p>
        <div className="tip">
          <b>패턴의 진짜 보너스, 어휘</b> 패턴 이름은 팀의 <strong>공용어</strong>이기도 해요.
          "여기 프로바이더 선택을 전략으로 빼죠"라는 한 문장이, 설계 회의 30분짜리 설명을
          대신합니다. 코드 리뷰에서 "이거 퍼사드 하나 두는 게 낫지 않아요?"가 통하는 팀과
          아닌 팀은 소통 속도가 다릅니다.
        </div>
      </Section>

      <Section n={2} title="생성 패턴 — 만드는 방법을 감춘다" sub="팩토리·빌더, 그리고 스프링이 이미 준 싱글턴">
        <p>
          생성 패턴의 공통 질문은 하나예요. <strong>"객체를 만드는 방법이 사용하는 쪽에
          노출되어야 하나?"</strong> <span className="icode">new KiwoomBridgeClient(host, port,
          retry, ...)</span> 같은 조립 지식이 사용처마다 복사되면, 조립법이 바뀔 때 전부
          고쳐야 하죠. <strong>팩토리</strong>는 그 조립 지식을 한곳에 모아 감춥니다 —
          "나는 <span className="icode">create("KR")</span>만 부를게, 뭘 어떻게 만드는지는
          네가 알아서".
        </p>
        <p>
          <strong>빌더</strong>는 생성 과정 자체가 복잡할 때의 해법이에요. 인자가 예닐곱 개를
          넘어가는 생성자는 호출부가 암호문이 됩니다. 무엇이 제목이고 무엇이 플래그인지,
          자리만 보고는 알 수 없으니까요.
        </p>
        <Code>{CODE_BUILDER}</Code>
        <p>
          그리고 가장 유명한 생성 패턴, <strong>싱글턴</strong>. "인스턴스를 하나만 만들어
          전역에서 공유한다"는 아이디어인데 — 스프링을 쓰는 우리에겐 반전이 있습니다.
        </p>
        <Code>{CODE_SINGLETON}</Code>
        <div className="tip">
          <b>우리 코드에서 찾아보기</b> service-auth의 <span className="icode">JwtService</span>,
          market의 <span className="icode">MarketBus</span> — 전부 <span className="icode">@Service</span>/
          <span className="icode">@Component</span> 빈, 즉 <strong>컨테이너가 관리하는 싱글턴</strong>
          이에요. 우리가 싱글턴 코드를 한 줄도 안 썼는데 싱글턴의 이득(인스턴스 공유)을 누리고
          있죠. 이것이 이 코스의 반복 주제입니다: <strong>프레임워크가 이미 준 패턴을 알아보는
          눈</strong>이 패턴을 새로 짜는 손보다 먼저입니다.
        </div>
      </Section>

      <Section n={3} title="구조 패턴 — 모양이 안 맞는 것들을 잇는다" sub="어댑터·데코레이터·퍼사드">
        <p>
          구조 패턴은 <strong>이미 있는 것들을 어떻게 이어 붙이느냐</strong>의 문제를 다룹니다.
          가장 자주 만나는 상황은 이거예요. 외부 API의 응답 모양이 우리 도메인 모델과 다르다 —
          그것도 프로바이더마다 <strong>제각각</strong> 다르다.
        </p>
        <Code>{CODE_ADAPTER}</Code>
        <p>
          <strong>어댑터</strong>의 효과는 격리예요. 키움 브리지의 "부호가 접두어로 붙는
          가격" 같은 괴상함이 <span className="icode">KiwoomAdapter</span> 밖으로 한 발짝도
          못 나옵니다. 외부 API가 필드명을 바꿔도 고칠 곳은 어댑터 하나뿐이고요. 여행용
          돼지코(플러그 어댑터)와 원리가 같아서 이름도 어댑터입니다.
        </p>
        <p>
          <strong>데코레이터</strong>는 "기능을 겹겹이 입히기"예요. 기존 객체를 감싸서(같은
          인터페이스로) 호출 앞뒤에 일을 더합니다. 캐시를 입힌 <span className="icode">
          CachingQuoteSource(realSource)</span>, 로깅을 입힌 <span className="icode">
          LoggingQuoteSource(cachingSource)</span>처럼 <strong>런타임에 조합</strong>이 되죠.
          상속으로 같은 걸 하려면 '캐시+로깅', '캐시만', '로깅만'… 조합 수만큼 자식 클래스가
          폭발합니다. <strong>겹겹이 입힐 기능이라면 상속보다 데코레이터</strong> — 이 구분이
          섹션 5의 프록시·AOP를 이해하는 열쇠가 돼요.
        </p>
        <p>
          <strong>퍼사드(facade, 건물의 정면)</strong>는 복잡한 하위 시스템에{' '}
          <strong>창구 하나만 여는</strong> 패턴입니다. "주문 체결 알림을 보내려면 구독 조회,
          템플릿 렌더링, 푸시 발송, 감사 로그를 순서대로…" — 이 지식을 호출하는 쪽마다 갖게
          하지 말고, <span className="icode">NotificationFacade.notifyFill(order)</span> 하나
          뒤로 숨기는 거예요. 호출부는 단순해지고, 내부 절차가 바뀌어도 창구 뒤에서만 바뀝니다.
        </p>
        <div className="tip">
          <b>셋의 구분법</b> 셋 다 '감싸기'라 헷갈리면 목적을 보세요. <strong>어댑터는 모양을
          바꾸려고</strong>(인터페이스 번역), <strong>데코레이터는 기능을 더하려고</strong>(같은
          인터페이스 유지), <strong>퍼사드는 단순하게 보이려고</strong>(여러 개를 하나로) 감쌉니다.
        </div>
      </Section>

      <Section n={4} title="행동 패턴 — 누가 무엇을 결정하는가" sub="전략·옵저버·템플릿 메서드, 이 코스의 심장">
        <p>
          드디어 이 코스의 실습 무대입니다. market 서비스에는 한국(키움 브리지)·미국·코인,
          시장마다 다른 시세 프로바이더가 있고, 초기 코드는 이걸 <strong>if 분기</strong>로
          갈랐어요. 처음엔 나쁘지 않았습니다. 문제는 <strong>같은 분기가 여러 메서드에
          복붙되면서</strong> 시작됐죠.
        </p>
        <Code>{CODE_STRATEGY_BEFORE}</Code>
        <p>
          <strong>전략(Strategy) 패턴</strong>의 처방: "시장마다 다른 부분"을 각자의 클래스로
          분리하고, 공통 인터페이스로 묶은 뒤, <strong>선택을 한 곳으로</strong> 모읍니다.
          if 사다리가 다형성으로 바뀌는 순간이에요.
        </p>
        <Code>{CODE_STRATEGY_AFTER}</Code>
        <p>
          눈여겨볼 대목은 생성자의 <span className="icode">List&lt;QuoteProvider&gt;</span>{' '}
          주입입니다. 스프링은 <strong>같은 인터페이스의 빈을 전부 모아 List로</strong> 넣어
          줘요. 새 프로바이더는 <span className="icode">@Component</span> 클래스 하나만 추가하면
          자동으로 합류합니다. 재설계의 성과를 숫자로 비교해 보죠 — 이게 실습의 핵심 산출물이에요.
        </p>
        <Code>{CODE_STRATEGY_COST}</Code>
        <p>
          두 번째 행동 패턴, <strong>옵저버와 Pub/Sub</strong>. 멀리 갈 것 없이 우리{' '}
          <span className="icode">MarketBus</span>가 실물 교재입니다. 시세 틱 하나를 SSE 스트림·
          캐시·알림 판정이 모두 알아야 하는데, 발행자가 구독자 목록을 직접 들고 있으면 구독자가
          늘 때마다 발행자를 수정해야 하죠. 그래서 중간에 <strong>채널</strong>을 둡니다.
        </p>
        <Code>{CODE_OBSERVER}</Code>
        <p>
          세 번째, <strong>템플릿 메서드</strong>. 시장별 시세 수집기의 <strong>흐름은 전부
          같은데</strong>(가져오기→정규화→검증→발행) 앞의 두 단계만 시장마다 달라요. 흐름을
          부모가 <span className="icode">final</span>로 고정하고, 다른 부분만 추상 메서드로
          자식에게 위임합니다.
        </p>
        <Code>{CODE_TEMPLATE}</Code>
        <div className="tip">
          <b>전략 vs 템플릿 메서드, 뭘 쓰지?</b> 갈아 끼우는 단위로 정하세요. <strong>알고리즘
          전체가 다르면 전략</strong>(합성 — 런타임 교체 가능, 테스트 쉬움), <strong>흐름은
          같고 일부 단계만 다르면 템플릿 메서드</strong>(상속 — 흐름 자체를 강제하고 싶을 때).
          현대 자바에선 상속의 경직성 때문에, 템플릿 메서드도 람다를 받는 '템플릿 콜백'으로
          많이 대체합니다.
        </div>
      </Section>

      <Section n={5} title="스프링이 숨겨둔 패턴들" sub="매일 쓰면서 몰랐던 것들의 정체">
        <p>
          여기까지 오면 이상한 기시감이 들 거예요. "어… 이거 스프링이 맨날 하던 거잖아?"
          맞습니다. <strong>스프링 프레임워크 자체가 디자인 패턴의 박물관</strong>이에요.
          우리는 패턴을 배우기 전부터 이미 패턴 위에서 개발해 왔습니다. 이름을 몰랐을 뿐이죠.
        </p>
        <Code>{CODE_SPRING_DI}</Code>
        <p>
          <strong>DI/IoC</strong>부터 풀어 보죠. 생성자 주입은 "구체 클래스를 내가{' '}
          <span className="icode">new</span> 하지 않고, 인터페이스로 받는다"는 것 —{' '}
          <strong>의존성 역전 원칙(DIP)</strong>의 구현 그 자체입니다. '무엇을 쓸지'의 결정권이
          사용하는 클래스에서 조립하는 컨테이너로 <strong>역전(Inversion)</strong>되었기에
          IoC(제어의 역전)라고 부르고요. 섹션 4에서 <span className="icode">List&lt;QuoteProvider&gt;</span>
          가 자동 주입된 것도 이 메커니즘의 선물이었습니다.
        </p>
        <p>
          <strong>@Transactional의 비밀</strong>은 프록시 패턴이에요. 스프링은 여러분의 클래스를
          감싼 <strong>대리인 객체</strong>를 만들어 빈으로 등록합니다. 호출이 대리인을 거치며
          트랜잭션 시작·커밋·롤백이 앞뒤에 끼워지는 거죠. 섹션 3의 데코레이터와 형제인데,
          <strong> 데코레이터는 기능 추가가 목적</strong>이고 <strong>프록시는 접근 제어·대리가
          목적</strong>이라는 뉘앙스 차이가 있습니다. AOP(관점 지향)도 같은 원리 — 로깅·보안
          같은 공통 관심사를 프록시가 끼워 넣는 것입니다.
        </p>
        <p>
          이 구조를 알면 유명한 함정도 당연해져요. <strong>같은 클래스 안에서{' '}
          <span className="icode">this.method()</span>로 부르면 <span className="icode">@Transactional</span>
          이 안 먹는다</strong> — 내부 호출은 대리인(프록시)을 거치지 않고 진짜 객체로 직행하기
          때문입니다. "스프링이 마법을 부린다"가 아니라 "프록시가 감싸고 있다"로 이해하는 순간,
          마법이 공학이 됩니다.
        </p>
        <div className="tip">
          <b>템플릿 콜백 찾아보기</b> <span className="icode">JdbcTemplate</span>·
          <span className="icode">RestClient</span>처럼 이름에 Template이 들어가거나 람다를 받는
          스프링 API는 대부분 <strong>템플릿 콜백</strong>이에요. 연결 열기·예외 변환·자원 정리
          같은 지루한 앞뒤를 템플릿이 맡고, 우리는 가운데 빈칸만 채웁니다. 섹션 4의 템플릿
          메서드에서 상속을 람다로 바꾼 변형이죠.
        </div>
      </Section>

      <Section n={6} title="SOLID로 패턴 고르기" sub="패턴 선택의 나침반은 원칙이다">
        <p>
          패턴 목록을 알아도 남는 질문이 있어요. <strong>"그래서 지금 이 코드엔 뭘 써야
          하지?"</strong> 그 판단의 나침반이 <strong>SOLID 원칙</strong>입니다. 패턴이
          '어떻게(How)'라면, SOLID는 '왜(Why)' — 원칙이 위반된 지점이 곧 패턴이 들어갈
          자리예요.
        </p>
        <Code>{CODE_SOLID}</Code>
        <p>
          섹션 4의 실습을 SOLID로 다시 읽어 봅시다. Before 코드의 문제는{' '}
          <strong>개방 폐쇄 원칙(OCP) 위반</strong>이었어요 — 새 시장을 '확장'하려면 기존
          메서드를 '수정'해야 했으니까요. 전략 패턴 도입 후엔 새 클래스 추가만으로 확장되고
          기존 코드는 닫혀 있죠. 또 <span className="icode">QuoteService</span>가 구체
          프로바이더가 아닌 인터페이스만 보는 건 <strong>의존성 역전(DIP)</strong>, 프로바이더
          선택과 시세 조회를 분리한 건 <strong>단일 책임(SRP)</strong>의 적용입니다. 패턴
          하나가 원칙 셋을 동시에 회복시킨 거예요.
        </p>
        <p>
          <strong>인터페이스 분리(ISP)</strong>는 실전에서 이렇게 씁니다. 어떤 프로바이더는
          호가(orderbook)를 지원하지 않는데 <span className="icode">QuoteProvider</span>에{' '}
          <span className="icode">getOrderbook</span>이 있다면? 지원 안 하는 구현체가 예외를
          던지는 어색한 코드가 생겨요(리스코프 위반이기도!). 답은 인터페이스를{' '}
          <span className="icode">QuoteProvider</span>와 <span className="icode">OrderbookProvider</span>
          로 <strong>쪼개서</strong>, 되는 것만 구현하게 하는 것입니다.
        </p>
        <div className="tip">
          <b>절반의 성공</b> 순서를 헷갈리지 마세요. <strong>패턴 없이도 SOLID면 절반은
          성공</strong>입니다. 작은 클래스, 인터페이스 의존, 한 가지 책임 — 이것만 지켜도
          코드는 이미 유연해요. 패턴은 SOLID를 지키기 위한 <strong>정형화된 수단</strong>이지,
          SOLID를 대신하는 부적이 아닙니다. 원칙이 먼저, 패턴은 그다음.
        </div>
      </Section>

      <Section n={7} title="안티패턴과 리팩터링 신호" sub="도입할 때를 아는 것, 되돌릴 줄 아는 것">
        <p>
          마지막 관문은 <strong>타이밍</strong>입니다. 같은 전략 패턴도 너무 늦으면 if 지옥에서
          고생하고, 너무 이르면 오버엔지니어링이 돼요. 도입 시점을 알려주는 건 이론이 아니라{' '}
          <strong>코드 냄새(code smell)</strong> — 코드가 보내는 구조적 신호입니다.
        </p>
        <Code>{CODE_SMELLS}</Code>
        <p>
          체크리스트의 아래쪽, <strong>"언젠가 필요할지도 몰라서"</strong>가 가장 위험한
          이유를 짚고 갑시다. 아직 오지 않은 요구사항을 위해 추상화를 쌓으면, 그 추상화 자체가
          비용이 됩니다 — 읽을 때마다 인터페이스를 한 겹 더 통과해야 하고, 구현체가 하나뿐인
          인터페이스는 독자에게 "다른 구현이 있나?" 하는 헛된 탐색을 시켜요. 업계에선 이를{' '}
          <strong>YAGNI</strong>(You Aren't Gonna Need It — 어차피 안 쓸걸)라고 부릅니다.
          섹션 4의 실습이 정당했던 건 분기가 <strong>이미</strong> 세 곳에 복붙되어 있었기
          때문이지, 패턴이 멋있어서가 아니에요.
        </p>
        <p>
          그리고 리팩터링의 안전망, <strong>테스트</strong>. 패턴 도입은 "동작은 그대로, 구조만
          바꾸기"입니다. 그런데 동작이 그대로인 걸 무엇으로 증명하죠? 도입 <strong>전에</strong>{' '}
          현재 동작을 고정하는 테스트를 만들고, 구조 변경 <strong>후에</strong> 그 테스트가
          여전히 통과함을 확인하는 것 — 이 전/후 초록불이 유일한 증명입니다. 실습에서도
          프로바이더 분기를 옮기기 전에 <span className="icode">getQuote</span>의 시장별 결과를
          고정하는 테스트부터 작성하세요.
        </p>
        <p>
          마지막으로, <strong>되돌리는 용기</strong>. 이미 들어간 과잉 추상화를 발견했다면 —
          구현체 하나뿐인 인터페이스, 케이스 둘뿐인 전략, 아무도 안 쓰는 확장 포인트 —{' '}
          <strong>걷어내는 것도 리팩터링</strong>입니다. "선배가 만든 구조라서", "지우면 없어
          보여서" 남겨 두는 코드가 다음 사람의 학습 비용이 돼요. 패턴을 넣을 줄 아는 개발자는
          많지만, 뺄 줄 아는 개발자는 드뭅니다. 후자가 시니어예요.
        </p>
        <div className="tip">
          <b>실습 최종 점검</b> 재설계를 마쳤다면 스스로에게 물어보세요. ① 새 프로바이더 추가
          시 <strong>기존 파일 수정이 0개</strong>인가? ② 전/후로 <strong>같은 테스트</strong>가
          통과하는가? ③ 6개월 뒤의 내가 이 구조를 보고 <strong>왜 이렇게 했는지</strong> 알 수
          있는가? 셋 다 예라면, 여러분은 패턴을 외운 게 아니라 쓴 것입니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🧩 여기까지 왔다면</h3>
          <p className="muted">
            이제 패턴은 암기 목록이 아니라 <strong>문제→해법 대응표</strong>가 됐어요. if 지옥엔
            전략을, 낯선 외부 API엔 어댑터를, 사건 전파엔 옵저버를 — 그리고 스프링이 이미 깔아 둔
            패턴들(DI·프록시·템플릿 콜백)을 알아보는 눈과, 과한 추상화를 걷어내는 용기까지
            손에 쥐었습니다. 그런데 좋은 구조가 코드에만 있는 건 아니에요. 쿼리 한 줄이 수백만
            행을 훑느냐 수십 행만 짚느냐도 <strong>자료구조의 설계</strong>가 정합니다. 다음{' '}
            <Link to="/learn/lv3-db-indexing"><strong>DB 인덱스와 실행계획 읽기</strong></Link>{' '}
            코스에서, 데이터베이스가 숨겨 둔 설계의 세계로 이어 갑시다.
          </p>
        </div>
      </div>
    </div>
  );
}
