Java Core / Design Patterns
Part 01 · Java Core · 1.1.05 · GoF mental model

Design Pattern là vocabulary cho trade-off

Pattern là giải pháp thiết kế có thể lặp lại cho một context, không phải đoạn code để copy. Câu trả lời tốt phải nêu problem, forces, structure, consequence và dấu hiệu không nên dùng.

5 Creational7 Structural11 Behavioral6 cặp phân biệt12 tên ngoài GoF

1. Pattern không phải gì?

Cách nhận diện: bắt đầu từ điểm thay đổi hoặc coupling đang gây đau, không bắt đầu từ tên pattern muốn áp dụng.

Bổ sung · cách học và lập luận

Đọc theo điểm thay đổi: creational hỏi “ai quyết định tạo object nào?”; structural hỏi “các object được nối với nhau ra sao?”; behavioral hỏi “ai quyết định hành vi, thứ tự hoặc sự phối hợp?”. Ba nhóm là bản đồ tra cứu, không phải ba tầng bắt buộc của ứng dụng. Catalog GoF có 23 patterns. [R8]

Khung trả lời đề xuất: nêu context/problem → các forces đang xung đột → vai trò và collaborationconsequences → trường hợp không nên dùng → test hoặc evidence chứng minh lựa chọn. Ví dụ, “thêm interface để dùng Strategy” chưa trả lời được chính sách nào thực sự thay đổi và những implementation nào phải thay thế được cho nhau.

Các bảng dưới đây giữ nguyên intent, ví dụ và cảnh báo của nguồn. Những khối “Bổ sung” tách riêng fact có tài liệu, suy luận thiết kế và phép kiểm đề xuất; chúng không thay thế contract của API hay hệ thống cụ thể.

2. 5 Creational patterns

Bảng rộng: vuốt ngang để đọc đầy đủ các cột.

Catalog nguồn · 5 pattern tạo đối tượng
PatternIntentKhi dùngTrade-off
SingletonMột instance và access point trong một scope.Stateless shared service/resource registry do container quản.Global mutable state, hidden dependency, khó test; không tự thread-safe.
Factory MethodCho implementation/subtype quyết định concrete product.Creation thay đổi nhưng client chỉ cần product interface.Thêm hierarchy; một factory switch nhỏ đôi khi rõ hơn.
Abstract FactoryTạo family objects tương thích mà không lộ concrete classes.Nhiều vendor/platform cần cả client, verifier và mapper tương ứng.Thêm product mới phải sửa mọi factory family.
BuilderXây object phức tạp theo bước, tách construction khỏi representation.Immutable object, nhiều optional fields hoặc cross-field validation.Không thay invariant validation; DTO nhỏ không cần builder.
PrototypeTạo object bằng copy từ mẫu đã cấu hình.Construction đắt hoặc runtime chỉ biết prototype.Deep/shallow copy, identity và mutable graph dễ sai; ưu tiên copy factory rõ ràng.

Bổ sung · scope, invariant và copy

Singleton: “một” trong phạm vi nào?

Fact có phạm vi: Spring singleton là một instance cho một bean definition trong một IoC container; không có nghĩa một object duy nhất cho cả cluster. Tài liệu Spring phân biệt scope này với GoF Singleton theo class loader. Scope chỉ trả lời việc cấp instance, không tự làm mutable state an toàn khi nhiều thread sử dụng. [R1]

Phép kiểm đề xuất: chỉ ra nơi tạo và sở hữu instance, scope của container/class loader, state được chia sẻ và cơ chế bảo vệ từng invariant. Không lấy identity của hai lần lookup làm bằng chứng rằng business operation là thread-safe.

Factory Method và Abstract Factory: biến thiên ở product hay family?

Làm rõ từ intent nguồn: Factory Method là creation hook mà subtype/implementation cụ thể hóa; một static method tên create() chưa đủ để kết luận đó là GoF Factory Method. Abstract Factory gom những product phải tương thích, chẳng hạn client, verifier và mapper cùng vendor. Một simple factory có nhánh switch vẫn có thể là lựa chọn rõ ràng khi variation nhỏ. Đây là quyết định về thiết kế, không phải quy tắc cấm conditional.

Builder và Prototype: xây đúng khác với sao chép độc lập

Fact API — Java 21: Object.clone() sao chép các field theo giá trị hiện có; tham chiếu bên trong không được tự nhân bản. Đây là shallow copy, không phải deep copy của mutable object graph. [R2]

Suy luận thiết kế: Builder phải kiểm invariant khi hoàn tất xây dựng; không mặc định rằng builder tạo ra object immutable. Với Prototype, định nghĩa rõ field nào được chia sẻ, field nào cần bản sao và identity nào phải mới. Một copy factory rõ ràng giúp biểu lộ quyết định đó, nhưng bản thân tên factory cũng không bảo đảm deep copy.

Phép kiểm đề xuất: sửa một nested mutable field trên bản sao rồi kiểm tra bản gốc; thử cấu hình mâu thuẫn ở build(); kiểm tra object đã build không bị thay đổi ngoài ý muốn qua reference mà caller còn giữ. Chia sẻ immutable metadata có chủ ý không phải lỗi copy.

3. 7 Structural patterns

Bảng rộng: vuốt ngang để đọc đầy đủ các cột.

Catalog nguồn · 7 pattern tổ chức cấu trúc
PatternIntentVí dụFailure mode
AdapterChuyển interface có sẵn sang interface client cần.Bọc payment SDK thành PaymentGateway.Chỉ đổi tên method nhưng để vendor model/error rò vào domain.
BridgeTách abstraction khỏi implementation để hai chiều thay đổi độc lập.Notification type × delivery channel.Over-design nếu không có hai axes of variation.
CompositeXử lý leaf và group qua cùng interface dạng tree.Permission expression, product bundle.Contract quá rộng, cycle hoặc mutation khó kiểm soát.
DecoratorBọc object, giữ interface và thêm behavior.Metrics, retry, cache quanh remote client.Ordering và stack wrappers khó debug.
FacadeInterface đơn giản cho subsystem phức tạp.CheckoutFacade trước pricing/inventory/payment.Dễ thành God Object hoặc che failure semantics.
FlyweightChia sẻ intrinsic state để giảm nhiều object giống nhau.Glyph, metadata immutable.Nhầm intrinsic/extrinsic state; cache lifecycle/concurrency.
ProxyThay mặt target để kiểm soát access, location hoặc lifecycle.Spring AOP, lazy-loading, remote proxy.Hidden I/O/latency, identity và self-invocation.

Bổ sung · boundary, wrapper và proxy

Giữ boundary thật, không chỉ thêm một lớp bọc

Review đề xuất từ các failure mode nguồn: với Adapter, kiểm tra error, kiểu dữ liệu và quy ước vendor có còn rò vào domain không. Với Bridge, viết ra hai axes of variation độc lập trước khi nhân số class. Với Composite, xác định ai ngăn cycle và ai được mutation; với Flyweight, tách intrinsic state dùng chung khỏi extrinsic state riêng của từng lần sử dụng.

Trade-off cần kiểm: Facade giảm số khái niệm client phải biết, nhưng không tự biến pricing, inventory và payment thành một transaction. Với Decorator, thứ tự wrapper là một phần của behavior: counter đặt ngoài retry đếm lần gọi logic; counter đặt trong retry đếm attempt. Mục 7.2 cho một phép thử xác định, không phải benchmark production.

Spring AOP: lời gọi có thực sự đi qua proxy không?

Fact framework: self-invocation qua this bỏ qua advice của Spring AOP proxy. Giới hạn không override được final/private áp dụng cho class-based proxy; không nên biến nó thành kết luận chung cho mọi kiểu proxy. AspectJ weaving không có cùng hạn chế self-invocation vì advice được áp dụng trong bytecode. [R3]

Version/configuration caveat: xác minh proxy mechanism và cấu hình Framework/Boot thực tế; không suy ra proxy type chỉ từ tên annotation. Phép kiểm đề xuất là so lời gọi từ bean khác qua proxy với lời gọi nội bộ, đồng thời quan sát advice nào thực sự chạy. Các giới hạn trên không nói rằng mọi Proxy pattern đều có self-invocation problem. [R3]

4. 11 Behavioral patterns

Bảng rộng: vuốt ngang để đọc đầy đủ các cột.

Catalog nguồn · 11 pattern điều phối hành vi
PatternIntentVí dụFailure mode
Chain of ResponsibilityRequest qua chuỗi handlers tới khi xử lý/kết thúc.Servlet/Security filters, validation pipeline.Order ngầm, handler nuốt hoặc xử lý trùng request.
CommandĐóng gói request thành object để queue/retry/audit/undo.TransferMoneyCommand.Command không tự idempotent; retry có thể lặp side effect.
InterpreterBiểu diễn grammar và evaluate expression.Rule/filter DSL nhỏ.Grammar lớn cần parser chuyên dụng; untrusted expression có risk.
IteratorDuyệt aggregate mà không lộ representation.Java Iterable/Iterator.Concurrent modification; resource-backed iterator phải close.
MediatorTập trung coordination để colleagues không phụ thuộc chéo.Workflow coordinator.Mediator thành God Object.
MementoLưu snapshot để restore mà không phá encapsulation.Undo, workflow checkpoint.Memory/privacy/version compatibility.
ObserverSubscribers nhận notification khi subject đổi.Application events, UI listeners.Leak, ordering, reentrancy và sync/async semantics.
StateBehavior đổi theo internal state bằng state objects.Order/payment lifecycle.Không tự giải quyết durable concurrent transition.
StrategyĐóng gói family algorithms có cùng contract.Fee, pricing, fraud, routing policy.Strategies không substitutable hoặc abstraction chỉ có một case.
Template MethodSuperclass cố định skeleton, subclasses override steps.Framework lifecycle/template APIs.Inheritance coupling và fragile base class.
VisitorThêm operations trên object structure ổn định.AST, sealed domain variants.Thêm element type phải sửa mọi visitor.

Bổ sung · contract cục bộ và bảo đảm production

Command: đóng gói request chưa tạo ra idempotency

Suy luận failure window: giả sử handler đã ghi side effect, nhưng kết quả trả về bị mất. Retry cùng command có thể lặp nghiệp vụ dù object command không đổi. Khi review, yêu cầu bằng chứng deduplication gắn với side effect, xử lý unknown outcome và reconciliation; chỉ đếm số lần handler được gọi chưa chứng minh business correctness.

Liên hệ ngoài GoF: Transactional Outbox ghi message cùng business data trong transaction database rồi để relay gửi đi; relay vẫn có thể gửi lặp nên consumer cần xử lý idempotent. Đây là một protocol bổ sung cho boundary database–message, không phải khả năng sẵn có của Command và không tự bảo vệ side effect bên hệ thống thứ ba. [R7]

Iterator: ai sở hữu tài nguyên và ai phải đóng?

Làm rõ câu nguồn “resource-backed iterator phải close”: ý cần giữ là quản lý lifecycle tài nguyên đang được duyệt. java.util.Iterator không khai báo close(). Nếu iterator được lấy từ DirectoryStream, cần đóng chính DirectoryStream, thường bằng try-with-resources. Không gọi một method không tồn tại trên biến kiểu Iterator. [R4] [R5]

Phạm vi guarantee: chính sách concurrent modification phụ thuộc implementation; không suy ra rằng mọi iterator đều fail-fast hoặc có snapshot nhất quán. Phép kiểm đề xuất: đi qua đường kết thúc sớm và đường ném exception để kiểm tra resource owner vẫn được đóng. [R4]

Observer: notification không đồng nghĩa durable delivery

Fact framework: cơ chế event mặc định của Spring dùng synchronous publication trên caller thread. Có thể thay đổi qua multicaster/executor hoặc async listener; vì vậy cần đọc cấu hình trước khi kết luận về thread, blocking hay transaction context. Application events là communication trong application context, không phải bằng chứng về durable broker delivery. [R6]

Phép kiểm đề xuất: làm listener chậm hoặc ném exception, phát sinh notification lồng nhau và tháo đăng ký listener; quan sát latency của publisher, error propagation, ordering và reference còn bị giữ. Cần giao nhận bền vững qua process boundary thì mô tả persistence/retry protocol riêng, không chỉ đổi tên “listener” thành “consumer”.

State và Memento: object model chưa đủ cho concurrent workflow

Suy luận thiết kế từ cảnh báo nguồn: State giúp đặt behavior theo lifecycle, nhưng hai worker vẫn có thể cùng đọc một trạng thái cũ. Review phải chỉ ra cách chấp nhận đúng một transition khi cần, chẳng hạn version check hoặc cơ chế đồng bộ phù hợp với persistence boundary. Memento phải xác định phạm vi snapshot, dữ liệu nhạy cảm và khả năng đọc lại sau khi schema thay đổi.

Phép kiểm đề xuất: cho hai transition cạnh tranh, rồi so state lưu bền với side effect thực tế; thử restore snapshot cũ và chứng minh không làm lộ dữ liệu đã phải xóa. Đây là yêu cầu kiểm thử đề xuất, không phải khẳng định implementation hiện tại đã có lock, transaction hoặc versioning.

5. Các cặp thường bị nhầm

Bảng rộng: vuốt ngang để đọc đầy đủ các cột.

Đối chiếu nguồn · 6 cặp dễ nhầm
CặpPhân biệt
Factory Method / Abstract FactoryMột creation hook/product so với một family products tương thích.
Adapter / FacadeAdapter đổi interface; Facade đơn giản hóa subsystem.
Decorator / ProxyDecorator thêm behavior; Proxy kiểm soát access/location/lifecycle. Intent quyết định tên.
Strategy / StateStrategy thường do client/config chọn; State đổi theo lifecycle bên trong.
Strategy / Template MethodComposition so với inheritance và fixed skeleton.
Observer / message brokerObject collaboration so với distributed infrastructure có durability/delivery semantics.
Bổ sung — hỏi intent trước class diagram: cùng hình dạng “object A giữ reference đến object B” có thể phục vụ nhiều pattern. Khi phỏng vấn, nêu điều gì thay đổi, ai có quyền chọn implementation và consequence nào được chấp nhận. Nếu một implementation đồng thời mang nhiều intent, giải thích từng vai trò thay vì ép nó vào đúng một nhãn.

6. Patterns ngoài GoF

Layered, MVC, Hexagonal/Ports and Adapters, Repository, Unit of Work, Dependency Injection, CQRS, Event Sourcing, Saga, Outbox, Circuit Breaker và Bulkhead là enterprise/architecture/distributed patterns. Không gọi tất cả là “GoF patterns”; hãy nói rõ scope và problem chúng giải quyết.

Pattern fever: nhiều interface/factory/abstract class không đồng nghĩa thiết kế tốt. Abstraction chỉ có giá trị nếu bảo vệ boundary, variation hoặc policy có thật.

Bổ sung · câu hỏi nối sang architecture

Với từng tên ngoài GoF trong đoạn nguồn, hãy yêu cầu người trình bày chỉ rõ boundary, owner, invariant và failure model. Không coi các pattern đó là một bộ phải triển khai cùng nhau, cũng không mặc định một pattern chứa đầy đủ guarantee của pattern khác.

Hai hướng đọc từ tài liệu: catalog của Fowler giúp phân biệt enterprise application patterns với object collaboration; mô tả Transactional Outbox minh họa một boundary cụ thể và vẫn ghi rõ khả năng gửi lặp. Chọn tài liệu đúng pattern thay vì dùng một nguồn chung để khẳng định mọi delivery guarantee. Fowler — P of EAA · [R7]

7. Thực nghiệm Java: đọc collaboration bằng code

Bổ sung biên soạn · Hai ví dụ độc lập, Java 21, chỉ dùng JDK; không cần Spring hoặc thư viện ngoài. Chúng là phép thử cục bộ, không phải code production hoàn chỉnh và không làm tăng số lab của Part 01.

7.1 Strategy: thay policy, giữ contract

Giả định của ví dụ: mọi số tiền là số nguyên ở cùng một đơn vị nhỏ nhất; subtotal và fee không âm. Checkout giữ một policy thay vì chọn loại giao hàng bằng tên cụ thể. Policy vẫn có thể chứa conditional rõ ràng; mục tiêu là tách variation, không phải xóa mọi if.

Mã đầy đủ · StrategyDemo.java
import java.util.Objects;

public final class StrategyDemo {
    @FunctionalInterface
    interface FeePolicy {
        long fee(long subtotal);
    }

    static final class Checkout {
        private final FeePolicy policy;

        Checkout(FeePolicy policy) {
            this.policy = Objects.requireNonNull(policy, "policy");
        }

        long total(long subtotal) {
            if (subtotal < 0) {
                throw new IllegalArgumentException("negative subtotal");
            }
            long fee = policy.fee(subtotal);
            if (fee < 0) {
                throw new IllegalStateException("negative fee");
            }
            return Math.addExact(subtotal, fee);
        }
    }

    private static void check(boolean condition, String message) {
        if (!condition) {
            throw new AssertionError(message);
        }
    }

    private static void expectFailure(
            Class<? extends RuntimeException> type, Runnable action) {
        try {
            action.run();
        } catch (RuntimeException failure) {
            if (type.isInstance(failure)) {
                return;
            }
            throw new AssertionError("Unexpected failure", failure);
        }
        throw new AssertionError("Expected " + type.getSimpleName());
    }

    public static void main(String[] args) {
        // A conditional inside a policy is not an anti-pattern.
        FeePolicy delivery = subtotal -> subtotal >= 10_000 ? 0 : 500;
        FeePolicy pickup = subtotal -> 0;

        check(new Checkout(delivery).total(9_000) == 9_500, "delivery");
        check(new Checkout(delivery).total(10_000) == 10_000, "threshold");
        check(new Checkout(pickup).total(9_000) == 9_000, "pickup");
        expectFailure(NullPointerException.class, () -> new Checkout(null));
        expectFailure(IllegalArgumentException.class,
                () -> new Checkout(delivery).total(-1));
        expectFailure(IllegalStateException.class,
                () -> new Checkout(subtotal -> -1).total(100));
        expectFailure(ArithmeticException.class,
                () -> new Checkout(subtotal -> 500).total(Long.MAX_VALUE));

        System.out.println("StrategyDemo OK: 7 checks");
    }
}

Lưu đúng tên file, biên dịch và chạy:

javac --release 21 -Xlint:all StrategyDemo.java
java StrategyDemo

Kết quả của các assertion trong ví dụ:

StrategyDemo OK: 7 checks

Acceptance: hai policy hợp lệ thay thế được trong cùng context; boundary tại 10 000 được kiểm tra; null policy, subtotal âm, fee âm và overflow đều bị từ chối đúng loại lỗi. Kiểm output và exit code bằng 0, không chỉ việc chương trình biên dịch được.

Giới hạn: chưa mô hình currency conversion, tax hoặc rounding; việc policy gọi I/O, giữ mutable state hay có concurrency cần contract và test riêng. Tên Strategy không tự cho phép hai implementation có semantics khác nhau ngoài hợp đồng đã thống nhất.

7.2 Decorator: đổi thứ tự là đổi nghĩa metric

Failure-first setup: target trong bộ nhớ ném RetryableFailure ở hai lần đầu, rồi trả về OK. So sánh count(retry(target)) với retry(count(target)); cả hai giữ interface Supplier<String> nhưng counter quan sát khác nhau.

Mã đầy đủ · WrapperOrderDemo.java
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public final class WrapperOrderDemo {
    static final class RetryableFailure extends RuntimeException {
        private static final long serialVersionUID = 1L;
    }

    private static Supplier<String> failsTwice(AtomicInteger targetCalls) {
        return () -> {
            if (targetCalls.incrementAndGet() <= 2) {
                throw new RetryableFailure();
            }
            return "OK";
        };
    }

    private static Supplier<String> count(
            Supplier<String> target, AtomicInteger metricCalls) {
        Objects.requireNonNull(target, "target");
        Objects.requireNonNull(metricCalls, "metricCalls");
        return () -> {
            metricCalls.incrementAndGet();
            return target.get();
        };
    }

    private static Supplier<String> retry(
            Supplier<String> target, int maxAttempts) {
        Objects.requireNonNull(target, "target");
        if (maxAttempts < 1) {
            throw new IllegalArgumentException("maxAttempts must be positive");
        }
        return () -> {
            for (int attempt = 1; ; attempt++) {
                try {
                    return target.get();
                } catch (RetryableFailure failure) {
                    if (attempt >= maxAttempts) {
                        throw failure;
                    }
                }
            }
        };
    }

    private static void check(boolean condition, String message) {
        if (!condition) {
            throw new AssertionError(message);
        }
    }

    private static void expectFailure(
            Class<? extends RuntimeException> type, Runnable action) {
        try {
            action.run();
        } catch (RuntimeException failure) {
            if (type.isInstance(failure)) {
                return;
            }
            throw new AssertionError("Unexpected failure", failure);
        }
        throw new AssertionError("Expected " + type.getSimpleName());
    }

    public static void main(String[] args) {
        AtomicInteger targetA = new AtomicInteger();
        AtomicInteger metricA = new AtomicInteger();
        String a = count(retry(failsTwice(targetA), 3), metricA).get();
        check("OK".equals(a) && targetA.get() == 3 && metricA.get() == 1,
                "count outside retry");

        AtomicInteger targetB = new AtomicInteger();
        AtomicInteger metricB = new AtomicInteger();
        String b = retry(count(failsTwice(targetB), metricB), 3).get();
        check("OK".equals(b) && targetB.get() == 3 && metricB.get() == 3,
                "count inside retry");

        expectFailure(IllegalArgumentException.class, () -> retry(() -> "OK", 0));

        AtomicInteger exhausted = new AtomicInteger();
        expectFailure(RetryableFailure.class, () -> retry(() -> {
            exhausted.incrementAndGet();
            throw new RetryableFailure();
        }, 2).get());
        check(exhausted.get() == 2, "attempt budget");

        AtomicInteger permanent = new AtomicInteger();
        expectFailure(IllegalArgumentException.class, () -> retry(() -> {
            permanent.incrementAndGet();
            throw new IllegalArgumentException("invalid request");
        }, 3).get());
        check(permanent.get() == 1, "do not retry permanent failure");

        System.out.println("outside: metricCalls=1, targetCalls=3");
        System.out.println("inside: metricCalls=3, targetCalls=3");
        System.out.println(
                "WrapperOrderDemo OK: ordering, exhaustion, classification, budget");
    }
}

Lưu đúng tên file, biên dịch và chạy:

javac --release 21 -Xlint:all WrapperOrderDemo.java
java WrapperOrderDemo

Kết quả xác định của stub này, không phải số đo throughput:

outside: metricCalls=1, targetCalls=3
inside: metricCalls=3, targetCalls=3
WrapperOrderDemo OK: ordering, exhaustion, classification, budget

Acceptance: cả hai cách trả về cùng kết quả, cùng ba target calls; counter ngoài retry bằng 1, counter trong retry bằng 3. Hết budget phải trả lỗi; lỗi không retryable chỉ gọi target một lần; budget bằng 0 bị từ chối ngay.

Giới hạn quan trọng: stub chỉ lỗi trước khi trả kết quả, chưa mô phỏng side effect thành công rồi mất response. Counter không chứng minh idempotency hoặc exactly-once. Retry minh họa không có backoff, deadline, cancellation hay chính sách bảo vệ downstream; không bê nguyên wrapper này vào payment/remote I/O production.

Artifact tự học đề xuất: giữ hai file Java, output chạy và một đoạn giải thích metric đang đếm logical operation hay attempt. Với bài toán thật, bổ sung trace timeline và kiểm tra business side effect dưới failure window phù hợp; không dùng kết quả stub làm bằng chứng cho hệ thống phân tán.

8. Checklist tự kiểm trước khi chọn pattern

Bổ sung biên soạn · Sáu mục review cho bài này, không thay đổi question/lab counts của những trang khác.

  1. Problem trước tên: mô tả coupling hoặc variation có thật và chỉ ra ví dụ thay đổi mà thiết kế hiện tại khó đáp ứng.
  2. Vai trò và scope: kể được ai tạo, ai gọi, ai sở hữu state/resource; ghi rõ instance, thread, process, container hay distributed boundary nào đang được nói tới.
  3. Đối chiếu intent: giải thích đủ sáu cặp ở mục 5; nêu vì sao giải pháp đang bàn không chỉ là pattern “trông giống” trên class diagram.
  4. Contract và failure: có test hoặc test plan cho invariant, thứ tự wrapper, error propagation, lifecycle, copy hay concurrency thực sự liên quan; không đánh đồng transport success với business correctness.
  5. Trade-off và đường đơn giản hơn: chỉ ra class/indirection mới phải trả, trường hợp không nên dùng, và vì sao một conditional hoặc hàm thông thường chưa đủ — hoặc thừa nhận chúng đã đủ.
  6. Evidence và caveat: lưu output/trace hoặc review artifact, dẫn đúng phiên bản API/framework khi khẳng định guarantee; không gọi 12 tên ngoài GoF là GoF và không lấy hai demo cục bộ làm production certification.
Câu chốt phỏng vấn đề xuất: “Tôi chọn pattern này để cô lập điểm thay đổi X trong scope Y. Nó giúp Z nhưng phải trả chi phí C; không bảo đảm D. Tôi kiểm chứng bằng E và sẽ dùng giải pháp đơn giản hơn nếu variation đó không tồn tại.”

9. Tài liệu và phạm vi kiểm chứng

Ba mục tham khảo trên được giữ từ nguồn; Refactoring.Guru là catalog giải thích thứ cấp, không phải đặc tả Java/Spring. Các bổ sung kỹ thuật sử dụng tài liệu API, tài liệu framework hoặc mô tả của tác giả pattern dưới đây. Truy cập ngày 12/09/2026; tài liệu Spring là URL cập nhật theo thời gian, cần đối chiếu phiên bản dự án. Không có dependency mạng để đọc nội dung trang.