Tổng hợp 56 câu hỏi phỏng vấn Design Patterns kèm đáp án chi tiết, song ngữ Việt–Anh — sắp xếp từ cơ bản đến nâng cao. Ôn luyện miễn phí, tự động lưu tiến độ.
SRP quy định mỗi class/module chỉ nên có một lý do để thay đổi, tức là chỉ chịu trách nhiệm cho một chức năng duy nhất. Ví dụ trong TypeScript: Lợi ích: code dễ test, dễ maintain. Dấu hiệu vi phạm: class có nhiều method…
OCP quy định một module phải mở để mở rộng nhưng đóng để sửa đổi — thêm tính năng mới mà không cần chỉnh sửa code hiện có. Ví dụ TypeScript: OCP thường được triển khai qua Strategy pattern, Template Method hoặc polymorphism. Vi phạm…
LSP quy định rằng các object của subclass phải có thể thay thế object của superclass mà không làm hỏng tính đúng đắn của chương trình. Ví dụ vi phạm thường gặp: class Rectangle có setWidth/setHeight, class Square extends Rectangle override cả hai để giữ…
ISP quy định không nên bắt client implement những interface mà nó không sử dụng — thay vì một interface lớn, hãy tách thành nhiều interface nhỏ, chuyên biệt. Ví dụ: thay vì interface Animal { fly(): void; swim(): void; run(): void } bắt Dog…
DIP có hai quy tắc: (1) module high-level không nên phụ thuộc module low-level, cả hai nên phụ thuộc abstraction; (2) abstraction không nên phụ thuộc detail, mà detail phụ thuộc abstraction. Ví dụ: UserService không nên new MySQLDatabase() trực tiếp mà nhận DatabaseInterface qua…
Ba nguyên tắc cơ bản của software engineering: - DRY (Don't Repeat Yourself): mỗi piece of knowledge chỉ nên được định nghĩa ở đúng một chỗ — trùng lặp logic nên được extract thành function/module tái sử dụng. - KISS (Keep It Simple, Stupid): ưu…
SOLID giúp viết code có khả năng bảo trì cao (maintainable), dễ mở rộng (extensible) và dễ test (testable) — ba yếu tố quyết định tuổi thọ của một dự án phần mềm. Khi team scale up, SOLID giúp nhiều developer làm việc song song…
Trong React/Next.js, SRP áp dụng cho component (mỗi component một trách nhiệm), custom hook (tách logic ra khỏi UI), và service layer (tách API call ra file riêng). OCP áp dụng qua component composition và render props/children thay vì if/else trong component. DIP áp dụng…
Singleton đảm bảo một class chỉ có duy nhất một instance và cung cấp global access point đến instance đó. Triển khai TypeScript: Vấn đề: (1) khó unit test vì global state — mock singleton phức tạp; (2) trong Node.js multi-worker hoặc serverless, mỗi worker/function…
Factory Method định nghĩa interface để tạo object nhưng để subclass quyết định class nào sẽ được instantiate — class cha không hard-code class con cụ thể. Ví dụ TypeScript: Khác với Simple Factory (không phải GoF pattern): Simple Factory chỉ là một method/class có…
Abstract Factory cung cấp interface để tạo ra một family các object liên quan mà không cần chỉ rõ concrete class. WindowsUIFactory và MacUIFactory cùng implement interface này — nhờ vậy Button và Checkbox luôn thuộc cùng một theme. Khác Factory Method ở chỗ: Factory…
Builder tách quá trình xây dựng object phức tạp thành các bước riêng biệt, cho phép tạo nhiều biểu diễn khác nhau của cùng một object. Ví dụ TypeScript với method chaining: Ưu điểm so với constructor nhiều tham số: không cần nhớ thứ tự…
Prototype cho phép copy (clone) object hiện có mà không phụ thuộc vào class của nó — tạo object mới bằng cách sao chép prototype. JavaScript đặc biệt phù hợp vì vốn kế thừa theo prototype. Ví dụ: const config = { timeout: 3000, retries:…
Object Pool giữ sẵn một tập object đã khởi tạo để tái sử dụng thay vì liên tục tạo rồi huỷ — đáng dùng khi object tốn kém lúc khởi tạo. Ứng dụng phổ biến nhất là database connection pool: thay vì mở connection mới…
DI là technique mà object nhận dependencies từ bên ngoài thay vì tự tạo — là cơ chế triển khai DIP. Ba loại DI: Framework DI: NestJS dùng IoC container với decorators @Injectable(), @Inject() — tự động resolve dependency tree. Trong Go không có framework…
Creational patterns giải quyết vấn đề tạo object một cách linh hoạt. Cách chọn: - Singleton: cần đúng một instance shared toàn app (logger, config) — nhưng ưu tiên DI container thay vì tự implement. - Factory Method: framework cần cho phép subclass override cách…
Adapter cho phép các interface incompatible làm việc cùng nhau bằng cách bọc một object trong wrapper cung cấp interface mà client kỳ vọng. Ví dụ thực tế: tích hợp third-party payment SDK: Hai loại Adapter: Object Adapter (composition, prefer này) và Class Adapter (multiple…
Bridge tách abstraction khỏi implementation để cả hai có thể thay đổi độc lập — giải quyết 'Cartesian product' explosion khi có nhiều dimension. Ví dụ: Shape (Circle, Square) × Renderer (SVGRenderer, CanvasRenderer) = 4 class nếu dùng inheritance, nhưng với Bridge chỉ cần 2+2…
Composite cho phép compose objects thành tree structures để biểu diễn part-whole hierarchies — client xử lý individual objects và compositions đồng nhất qua cùng một interface. Ví dụ: file system: Client gọi getSize() trên cả File và Folder mà không cần biết loại. Trong…
Decorator đính kèm thêm behavior vào object tại runtime bằng cách bọc chúng trong decorator objects — thay thế cho inheritance khi cần linh hoạt. Ví dụ TypeScript: Khác Inheritance: Decorator thêm behavior tại runtime và có thể stack nhiều lớp; inheritance static tại compile…
Facade cung cấp simplified interface cho một hệ thống phức tạp, subsystem hoặc library — giảm dependency giữa client code và internals phức tạp. Ví dụ: thay vì client gọi trực tiếp 5 service (AuthService, UserService, ProfileService, CacheService, LogService), ta tạo UserFacade với method đơn…
Proxy cung cấp surrogate object thay thế cho object khác — control access đến object gốc và có thể thêm logic trước/sau. Các loại phổ biến: (1) Virtual Proxy (lazy initialization): chỉ tạo object nặng khi thực sự cần — ví dụ lazy load image;…
Flyweight giảm memory usage bằng cách chia sẻ state chung (intrinsic state) giữa nhiều objects tương tự — chỉ lưu state riêng (extrinsic state) trong object cụ thể. Ví dụ game: render 10,000 cây trong rừng — thay vì mỗi Tree object lưu texture/mesh riêng,…
Structural patterns giải quyết cách compose class và object thành larger structures. Chọn theo use case: - Adapter: tích hợp incompatible interface (thường là third-party lib) - Bridge: thiết kế mới cần tách abstraction-implementation để scale độc lập - Composite: dữ liệu có dạng tree,…
Observer định nghĩa one-to-many dependency: khi một object (Subject/Observable) thay đổi state, tất cả dependents (Observers) được notify tự động. Ví dụ TypeScript: Trong JavaScript: EventEmitter của Node.js, addEventListener trong DOM, RxJS Observable đều là Observer pattern. React: useEffect với dependency array là lazy Observer;…
Strategy định nghĩa family of algorithms, encapsulate mỗi cái, và làm chúng interchangeable — cho phép algorithm thay đổi độc lập với client sử dụng nó. Ví dụ TypeScript: So với if/else: Strategy tuân thủ OCP — thêm algorithm mới không cần sửa Sorter; if/else…
Command encapsulate một request thành object, cho phép parameterize clients với different requests, queue/log requests, và support undoable operations. Cấu trúc: Ứng dụng thực tế: text editor (Ctrl+Z), transaction trong database, task queue (Bull, BullMQ), HTTP request retry. Trong Redux, mỗi dispatch(action) là Command —…
Template Method định nghĩa skeleton của algorithm trong method của superclass, để subclass override các bước cụ thể mà không thay đổi cấu trúc tổng thể. Ví dụ: Khác Strategy: Template Method dùng inheritance (lúc compile time); Strategy dùng composition (lúc runtime). Template Method khi…
Chain of Responsibility cho phép pass request qua chain of handlers — mỗi handler quyết định xử lý hoặc pass cho handler tiếp theo. Express.js middleware là ví dụ điển hình: app.use(authMiddleware, rateLimitMiddleware, validationMiddleware, routeHandler) — mỗi middleware gọi next() để pass request. Ví dụ…
State pattern cho phép object đổi hành vi khi state nội tại đổi — nhìn từ ngoài như thể object đổi class. Thay vì rải if (state === 'idle') ... else if (state === 'loading') ... khắp codebase, mỗi state là một class mang hành…
Iterator cung cấp cách access tuần tự các elements của collection mà không expose underlying representation. JavaScript hỗ trợ sẵn Iterator Protocol: object là iterator nếu có next() method trả về { value, done }. Ví dụ custom iterator: Generator functions (function) là sugar syntax…
Ba architectural patterns tách UI logic: - MVC (Model-View-Controller): Controller xử lý input, update Model, View render Model — Controller và View biết nhau; truyền thống trong server-side (Rails, Laravel, Express). - MVP (Model-View-Presenter): Presenter xử lý logic thay Controller, View passive hơn chỉ nhận…
- Encapsulation (đóng gói): gói dữ liệu và hành vi thao tác trên dữ liệu vào một object, ẩn trạng thái nội bộ; bên ngoài chỉ truy cập qua interface công khai. Bảo vệ bất biến (invariant), giảm coupling. - Abstraction (trừu tượng): lộ ra…
Ý tưởng: chụp và lưu lại trạng thái nội bộ của một object để sau này khôi phục, mà không phá vỡ tính đóng gói (không lộ chi tiết bên trong ra ngoài). Ba vai: - Originator: object có trạng thái cần lưu; tạo memento…
Vấn đề: code rải rác if (obj != null) obj.doSomething() gây rối, dễ quên, và dẫn tới NullPointerException. Ý tưởng: thay vì trả null khi "không có gì", trả về một object triển khai cùng interface nhưng hành vi trung tính / không làm gì…
Ba mức quan hệ giữa object, ràng buộc vòng đời mạnh dần: - Association (kết hợp): hai object có liên hệ và dùng nhau nhưng độc lập vòng đời. Vd: Teacher và Student — biết nhau nhưng tồn tại riêng. UML: đường thẳng. - Aggregation…
- Cohesion (độ gắn kết): mức độ các phần bên trong một module cùng phục vụ một mục đích rõ ràng. High cohesion = mọi hàm/thuộc tính trong module liên quan chặt tới một trách nhiệm (gần với SRP). - Coupling (độ phụ thuộc): mức…
Đây là code smell switch statements thường gặp: một hàm phân nhánh theo loại và phải sửa mỗi khi có loại mới. Cách xử lý là tách mỗi nhánh thành một strategy cùng interface, rồi chọn strategy qua một bảng tra. Thêm hãng mới =…
Đó là telescoping constructor — người đọc không biết true ở vị trí thứ 6 nghĩa là gì, và hoán đổi hai tham số cùng kiểu thì compiler không bắt được. Hai hướng: 1. Object tham số (đủ dùng cho phần lớn trường hợp trong…
Factory Method giải quyết một loại object: bạn có một hàm/method quyết định tạo instance nào dựa trên input, phần code gọi chỉ thấy interface. Abstract Factory giải quyết một nhóm object phải khớp nhau. Ví dụ chọn nhà thanh toán: mỗi nhà có client…
Bản thân "một instance duy nhất" không sai — vấn đề nằm ở cách Singleton cổ điển tự quản lý instance và được gọi thẳng từ mọi nơi. Hệ quả: - Phụ thuộc ẩn. OrderService gọi Database.getInstance() bên trong, nên signature của hàm không hề…
Vấn đề là markPaid đang biết quá nhiều về hệ thống xung quanh: thêm một việc phải-làm-khi-thanh-toán là lại sửa nó. Tách theo hướng Observer / publish-subscribe: markPaid chỉ cập nhật trạng thái rồi phát ra một sự kiện, các bên quan tâm tự đăng…
Dùng Decorator: bọc object gốc trong một object cùng interface, thêm hành vi trước/sau rồi giao phần việc chính cho object bên trong. Mỗi concern nằm ở một lớp riêng, ghép được theo thứ tự tuỳ ý và class gốc không đổi một dòng nào.…
Định nghĩa interface theo ngôn ngữ nghiệp vụ của bạn, rồi viết một Adapter dịch từ interface đó sang SDK. Code nghiệp vụ chỉ nhìn thấy PaymentGateway. Đổi cổng = viết adapter mới, phần còn lại không đổi. Test cũng dễ vì thay adapter bằng…
Có — nhưng gom bằng Facade (một điểm vào cho một quy trình), không phải một class "Manager" chứa tất cả. Controller chỉ còn: validate request → gọi register → map response. Quy trình đăng ký nằm một chỗ, test được mà không cần dựng…
Repository đặt một lớp trung gian giữa domain và tầng lưu trữ: domain làm việc với một tập object trong bộ nhớ, còn chuyện SQL/ORM nằm sau interface. Nó giải quyết được: - Truy vấn nghiệp vụ có tên rõ ràng, dùng lại được, thay…
Cả hai đều tách việc tạo dependency ra khỏi việc dùng, nhưng khác nhau ở hướng luồng. DI — dependency được đưa vào từ ngoài: Service Locator — class tự đi lấy từ một registry: Vì sao DI được ưa hơn: - Phụ thuộc hiện…
Template Method khớp hơn khi khung xử lý cố định và chỉ vài bước thay đổi. Lớp cha giữ trình tự, lớp con điền phần khác nhau. Strategy khớp hơn khi phần thay đổi là một hành vi độc lập cần chọn lúc chạy hoặc…