Khám phá Learn Stream About Jokes
COHORT 2 Foundation — Claude Code — 3 buổi Zoom · 21 · 24 · 28/07, 20:00 Đăng ký →
Bài viết

Bản đồ đầy đủ: 23 design pattern cho người vibe code

Bài trước là 6 pattern bạn gặp nhiều nhất. Đây là bản đồ đủ 23 — mỗi cái một ý chính và một ví dụ app, để lần sau gặp đúng vấn đề bạn biết nó có tên và gọi tên được cho AI. Bạn không cần dùng hết, chỉ cần nhận ra.

Bản đồ đầy đủ: 23 design pattern cho người vibe code

Bản đồ đầy đủ: 23 design pattern cho người vibe code

Ở bài trước, mình chọn ra 6 design pattern mà người vibe code dễ gặp nhất. Bài này là tấm bản đồ đầy đủ: 23 pattern, đủ để bạn tra lại khi thấy code bắt đầu rối hoặc khi muốn nói chính xác với AI mình cần gì. Bạn sẽ không dùng hết cả 23, nhưng nhận ra đúng tên pattern sẽ giúp prompt rõ hơn rất nhiều: “làm chỗ này theo Strategy”, “bọc API bằng Adapter”, hay “tạo một Facade cho flow thanh toán”.

Nếu bạn đang build AI agent, mình có một bản cho người build agent system với những pattern gần hơn với orchestration, tool use và memory. Còn nếu mới làm quen với design pattern, hãy đọc 6 pattern bạn gặp nhiều nhất trước vì bài đó đi sâu hơn vào những cái cốt lõi.

Bài bạn đang đọc là bản đồ đủ 23 pattern. Mỗi pattern chỉ có một ý chính và một ví dụ app cụ thể, để lần sau gặp đúng vấn đề, bạn biết nó có tên.

Mỗi pattern mình để một hình hai bên: trái là “Không dùng” — code lúc chưa có pattern, thường rối, rải rác hoặc phình ra; phải là “Dùng” — cùng bài toán đó sau khi áp pattern. Bạn nhìn cái mess bên trái trước, thấy nó giống chỗ nào trong app mình, rồi mới cần nhớ tên. Đó mới là lúc pattern có nghĩa.

Một lưu ý nhỏ: “object” trong bài này có thể hiểu đơn giản là một phần trong app có data và behavior riêng. Bạn không cần học thuộc định nghĩa. Chỉ cần nhận ra hình dáng của vấn đề là đã đủ để steer AI tốt hơn.

Creational — mấy pattern lo việc TẠO ra object

Nhóm này trả lời câu hỏi: app nên tạo object như thế nào để code không bị dính chặt vào từng class cụ thể?

Simple Factory — đưa yêu cầu vào, nhận đúng object ra

Simple Factory: không dùng thì mỗi chỗ tự if rồi new provider (đổi provider = sửa nhiều chỗ); dùng thì một Factory tạo, gọi tên là xong

Thay vì nhiều chỗ tự viết if paymentType === "stripe" rồi khởi tạo từng payment provider, bạn gom logic đó vào một factory. App chỉ gọi createPaymentProvider("stripe") và nhận về object phù hợp mà không cần biết nó được dựng ra sao. Cái này có bản đi sâu ở bài 6 pattern.

Factory Method — để từng nhánh tự quyết định object cần tạo

Factory Method: không dùng thì một flow chung nhét if từng loại; dùng thì mỗi class con tự chọn sender, thêm loại mới không sửa flow

Factory Method hữu ích khi có một flow chung nhưng mỗi loại lại cần tạo dependency khác nhau. Ví dụ app có EmailCampaignPushCampaign; cả hai cùng chạy một quy trình gửi campaign, nhưng mỗi class tự chọn sender tương ứng. So với Simple Factory, quyền quyết định nằm trong từng class con thay vì một hàm factory trung tâm.

Abstract Factory — tạo cả một bộ object đi cùng nhau

Abstract Factory: không dùng thì dễ ghép nhầm client Stripe với verifier PayPal; dùng thì chọn một factory là nhận đúng cả bộ

Abstract Factory dùng khi bạn không chỉ tạo một object, mà cần một bộ object phải tương thích với nhau. Ví dụ app hỗ trợ nhiều payment provider: bộ Stripe gồm payment client, refund client và webhook verifier của Stripe; bộ PayPal cũng có ba phần tương ứng. Chọn một factory là nhận đúng cả bộ, tránh chuyện dùng client của Stripe nhưng verifier của PayPal.

Builder — ráp một object phức tạp theo từng bước

Builder: không dùng thì constructor dài mười mấy tham số dễ sai; dùng thì ráp từng bước withDateRange() groupBy() build()

Builder hợp với những object có nhiều lựa chọn, nhất là khi constructor bắt đầu dài và khó đọc. Ví dụ một report có khoảng thời gian, metric, filter, group, chart type và export format; bạn có thể lần lượt gọi withDateRange(), groupBy() rồi exportAs() trước khi build(). Cách này cũng giúp AI thêm option mới mà không phải sửa một constructor có mười mấy tham số.

Prototype — tạo object mới bằng cách clone một mẫu có sẵn

Prototype: không dùng thì dựng object từ đầu mỗi lần; dùng thì clone một mẫu có sẵn rồi thay vài field

Nếu việc dựng một object từ đầu tốn công nhưng phần lớn cấu hình vẫn giống nhau, bạn có thể clone một prototype rồi thay vài field. Ví dụ người dùng duplicate một automation workflow: app copy toàn bộ trigger, action và condition từ workflow cũ, sau đó cấp ID mới và đổi tên thành “Copy”. Pattern này gần với tính năng “Duplicate” mà nhiều app có sẵn.

Singleton — đảm bảo app chỉ có một instance dùng chung

Singleton: không dùng thì mỗi module tự tạo một connection; dùng thì mọi nơi chia sẻ đúng một instance

Singleton giữ cho một class chỉ được tạo một lần trong phạm vi app. Ví dụ bạn có một config store, analytics client hoặc database connection pool mà mọi phần đều dùng chung, thay vì mỗi component tự tạo một bản mới. Nhưng đừng biến mọi service thành Singleton; state dùng chung quá nhiều thường làm test và debug khó hơn.

Structural — mấy pattern lo việc GHÉP các phần lại

Nhóm này không tập trung vào việc tạo object, mà vào cách bọc, nối hoặc tổ chức những phần đã có để chúng làm việc với nhau.

Adapter — đổi interface lạ thành interface app đang hiểu

Adapter: không dùng thì code phải biết định dạng riêng của từng service; dùng thì app nói một ngôn ngữ, Adapter dịch qua lại

Adapter hữu ích khi bạn tích hợp một service có cách gọi khác với phần còn lại của app. Ví dụ app của bạn muốn mọi storage provider đều có upload(file) và trả về một URL, nhưng Cloudinary, S3 và Supabase Storage dùng input lẫn response khác nhau. Mỗi Adapter dịch qua lại để code phía trên chỉ cần nói cùng một ngôn ngữ. Adapter cũng nằm trong 6 pattern cốt lõi ở bài trước.

Bridge — tách hai chiều thay đổi để chúng không nhân bản lẫn nhau

Bridge: không dùng thì mỗi nội dung nhân mỗi kênh thành một class; dùng thì tách hai trục rồi ghép lúc chạy

Bridge dùng khi một tính năng có hai trục có thể thay đổi độc lập. Ví dụ notification có nhiều nội dung như order confirmation, password reset và shipping update; đồng thời có nhiều kênh như email, SMS và push. Thay vì tạo class cho mọi tổ hợp, bạn tách notification khỏi delivery channel rồi ghép chúng lúc chạy.

Composite — đối xử với một phần tử và cả một nhóm theo cùng cách

Composite: không dùng thì viết hai flow riêng cho element và group; dùng thì cả hai gọi cùng một interface

Composite phù hợp với dữ liệu có cấu trúc cây. Ví dụ trong page builder, một button là một component, còn một section chứa nhiều component khác; cả hai đều có thể hỗ trợ render(), move()delete(). Nhờ vậy, thao tác trên một element hay nguyên một group dùng cùng interface, không cần viết hai flow riêng.

Decorator — bọc thêm behavior mà không sửa object gốc

Decorator: không dùng thì nhồi hết tính năng vào một class; dùng thì bọc thêm behavior theo lớp

Decorator thêm khả năng bằng cách bọc một object trong object khác. Ví dụ bạn có một uploader cơ bản, rồi bọc thêm image compression, virus scan hoặc logging tùy từng màn hình. Phần upload gốc không phải biết hết mọi tính năng phụ, và bạn có thể xếp nhiều lớp decorator với nhau khi cần.

Facade — mở một cửa đơn giản trước một hệ thống phức tạp

Facade: không dùng thì UI tự điều phối cả sáu bước checkout; dùng thì UI gọi một hàm, facade lo phần còn lại

Facade giấu nhiều bước nội bộ sau một interface dễ gọi. Ví dụ checkout() có thể phải validate cart, kiểm tra tồn kho, áp coupon, tạo payment intent, lưu order và gửi confirmation; UI chỉ nên gọi một hàm thay vì tự điều phối cả chuỗi đó. Đây cũng là một trong 6 pattern cốt lõi ở bài trước.

Flyweight — chia sẻ phần dữ liệu giống nhau để đỡ tốn tài nguyên

Flyweight: không dùng thì mỗi marker giữ một bản icon/style riêng tốn RAM; dùng thì dùng chung phần giống nhau

Flyweight có ích khi app phải tạo rất nhiều object gần như giống nhau. Ví dụ một map hiển thị hàng chục nghìn marker: icon, màu và style của từng loại địa điểm có thể được dùng chung, còn mỗi marker chỉ giữ tọa độ và ID riêng. Bạn sẽ ít gặp pattern này trong CRUD app nhỏ, nhưng nó đáng nhớ khi render feed lớn, canvas, game hoặc dữ liệu bản đồ.

Proxy — đặt một lớp đứng trước object thật để kiểm soát truy cập

Proxy: không dùng thì gọi thẳng API mỗi lần; dùng thì proxy chen một lớp cache, permission, log

Proxy có cùng interface với object thật nhưng chen thêm một lớp kiểm soát. Ví dụ UI gọi getUserProfile() qua proxy; proxy kiểm tra cache trước, chỉ gọi API thật nếu chưa có data, rồi lưu kết quả lại. Proxy cũng có thể dùng để check permission, lazy-load file lớn hoặc ghi log mà code gọi không cần biết.

Behavioral — mấy pattern lo việc các phần NÓI CHUYỆN với nhau

Nhóm này xử lý cách object phối hợp, truyền yêu cầu và thay đổi behavior. Đây thường là chỗ app bắt đầu khó sửa khi mọi component gọi trực tiếp lẫn nhau.

Chain of Responsibility — chuyền request qua nhiều chặng cho tới khi được xử lý

Chain of Responsibility: không dùng thì một hàm dài ôm hết mọi bước; dùng thì request đi qua từng chặng handler

Một request đi qua một chuỗi handler, mỗi handler có thể xử lý, từ chối hoặc chuyển tiếp. Ví dụ request upload lần lượt qua kiểm tra đăng nhập, file type, dung lượng, virus scan rồi mới tới storage. Bạn có thể thêm hoặc đổi thứ tự từng bước mà không nhét toàn bộ logic vào một hàm dài.

Command — biến một hành động thành object riêng

Command: không dùng thì toolbar gọi thẳng canvas khó làm undo; dùng thì mỗi hành động thành object có execute/undo

Command đóng gói một hành động cùng dữ liệu cần để thực hiện nó. Ví dụ editor biến thao tác “move block”, “delete text” hay “resize image” thành các command riêng; toolbar chỉ gửi command mà không cần biết canvas xử lý thế nào. Khi mỗi command có thêm undo(), bạn cũng có thể xây undo/redo khá tự nhiên.

Iterator — đi qua một collection mà không cần biết bên trong lưu thế nào

Iterator: không dùng thì mỗi nguồn dữ liệu một cách duyệt riêng; dùng thì tất cả cùng gọi next()

Iterator cung cấp cách lấy lần lượt từng phần tử mà không để code phía ngoài phụ thuộc vào cấu trúc dữ liệu. Ví dụ feed có thể lấy item từ array local, API phân trang hoặc database cursor, nhưng UI vẫn dùng một cách gọi kiểu next(). Pattern này thường đã nằm sẵn trong ngôn ngữ hoặc framework, nên bạn dùng nhiều hơn bạn tưởng dù ít khi phải tự viết.

Mediator — cho các phần nói chuyện qua một đầu mối

Mediator: không dùng thì năm component gọi chéo nhau; dùng thì mọi thay đổi đi qua một đầu mối

Mediator giảm việc các component gọi chéo trực tiếp lẫn nhau. Ví dụ trong form checkout, chọn quốc gia có thể ảnh hưởng tỉnh thành, shipping method, thuế và payment option; thay vì năm component tự subscribe lẫn nhau, một checkout mediator điều phối các thay đổi. Đổi một component khi đó ít kéo theo sửa hàng loạt component khác.

Memento — chụp lại state để có thể quay về

Memento: không dùng thì kéo thả sai là mất layout cũ; dùng thì chụp state để quay lại bản trước

Memento lưu một snapshot của state mà không buộc bên lưu phải hiểu chi tiết bên trong. Ví dụ một form builder tự lưu từng phiên bản layout để người dùng quay lại bản trước sau khi kéo thả sai. Pattern này hợp với draft history, undo nhiều bước, restore settings hoặc versioning nội dung.

Observer — một chỗ đổi, những chỗ quan tâm được báo

Observer: không dùng thì payment tự gọi cứng từng bên; dùng thì phát một event, ai quan tâm tự nhận

Observer tạo quan hệ subscribe: khi một object phát event, các listener đã đăng ký sẽ được thông báo. Ví dụ sau khi order chuyển sang paid, inventory trừ hàng, email service gửi receipt và analytics ghi conversion mà payment service không phải gọi cứng từng bên. Đây cũng là một pattern cốt lõi ở bài trước.

Visitor — thêm một thao tác mới mà không sửa các object hiện có

Visitor: không dùng thì thêm một thao tác phải sửa mọi block class; dùng thì một visitor thêm thao tác ở một chỗ

Visitor hữu ích khi bạn có nhiều loại object ổn định nhưng thường xuyên cần thêm operation chạy qua tất cả chúng. Ví dụ document editor có text block, image block và table block; bạn muốn thêm export sang PDF, accessibility audit hoặc word count mà không nhồi từng operation vào mọi block class. Đây là pattern khá hiếm trong app thông thường, và đôi khi một vòng lặp cùng vài hàm riêng đã đủ dễ hiểu hơn.

Strategy — đổi cách làm mà không đổi flow đang gọi nó

Strategy: không dùng thì một hàm đầy if/else; dùng thì các chiến lược thay thế được sau một lời gọi

Strategy tách nhiều cách giải cùng một việc thành các implementation có thể thay thế. Ví dụ checkout có chiến lược tính shipping theo nội thành, toàn quốc hoặc quốc tế; flow checkout chỉ gọi calculateShipping() mà không chứa một đống if/else. Đây là một trong 6 pattern cốt lõi ở bài trước.

State — cùng một object nhưng hành xử khác theo trạng thái hiện tại

State: không dùng thì if status rải khắp app; dùng thì mỗi state tự biết action nào được phép

State phù hợp khi behavior phụ thuộc mạnh vào trạng thái. Ví dụ một order ở trạng thái pending có thể cancel, order paid có thể refund, còn order shipped chỉ có thể yêu cầu return. Thay vì rải điều kiện if status khắp app, mỗi state định nghĩa những action được phép và cách chuyển sang state tiếp theo.

Template Method — giữ nguyên khung flow, cho từng loại thay vài bước

Template Method: không dùng thì mỗi importer chép lại cả quy trình; dùng thì khung chung cố định chỉ thay vài bước

Template Method định nghĩa một quy trình chung, sau đó để class con tùy biến những bước cụ thể. Ví dụ import dữ liệu luôn đi qua đọc file, validate, transform, save và report lỗi; importer CSV và importer JSON chỉ thay phần parse cùng validation riêng. Pattern này hợp khi flow thật sự ổn định, còn nếu các bước thay đổi tự do thì composition thường dễ chịu hơn inheritance.

Vậy nên nhớ cái nào

Bạn không cần học thuộc 23 cái rồi mới được build app. Với một vibe coder, sáu cái xuất hiện thường xuyên nhất vẫn là Factory, Adapter, Facade, Observer, Strategy và Singleton. Khi app lớn hơn, mình sẽ thêm StateCommand vào nhóm nên nhận ra sớm, vì status flow và undoable action rất dễ biến thành một đống điều kiện nếu không có cấu trúc.

Những pattern còn lại chủ yếu là từ vựng để tra cứu. Khi thấy constructor có quá nhiều option, nhớ tới Builder. Khi có nhiều object dạng cây, nhớ Composite. Khi mọi component gọi chéo nhau, thử nhìn Mediator. Khi AI đề xuất Visitor hoặc Flyweight cho một app CRUD nhỏ, bạn cũng đủ hiểu để hỏi lại: vấn đề hiện tại có thật sự cần pattern này không?

Tên pattern không phải huy hiệu kiến thức. Nó chỉ là cách nén một ý thiết kế vào vài chữ để bạn và AI nói chuyện nhanh hơn. “Refactor đi” là một yêu cầu rất rộng; “tách phần tính phí thành Strategy để mình đổi rule theo từng loại khách hàng” thì đã có hướng rõ ràng.

Nếu bạn mới bắt đầu, quay lại bài 6 design pattern cho vibe coder và thử nhận ra chúng trong chính app mình đang build. Những pattern hiếm hơn cứ để bản đồ này ở đây; tới lúc gặp đúng vấn đề, cái tên sẽ bắt đầu có nghĩa.

Trong app bạn đang làm, chỗ nào hiện có nhiều if/else, nhiều service gọi chéo nhau, hoặc một flow dài mà bạn nghi là đã có pattern để gỡ?

#design-patterns #vibe-coding #ai-coding #reference #beginners

Bài viết liên quan

10 design pattern cũ đang sống lại trong thế giới AI agent

idea

23 Gang-of-Four design pattern tưởng là chuyện của một thời OOP. Nhưng khi nối agent, subagent, MCP, hook và tool-call thành workflow, mình vẫn đang dùng đúng những hình đó — object thành agent, method thành tool, application flow thành orchestration. Đây là những pattern map sạch nhất, và mấy cái map lỏng lẻo mình không cố kéo qua.

6 design pattern giúp bạn bảo AI viết app đỡ rối

idea

Bạn vibe code một app, nó chạy được, nhưng thêm feature là code rối thêm — và bạn không biết bảo AI sửa theo hướng nào. Biết tên 6 pattern thường gặp là đủ để chỉ đường cho AI: tách theo Strategy, bọc bằng Adapter, phát event theo Observer. Đây là bản cho người vibe code, không phải cho người build agent.

0:00

Chia sẻ ảnh

Bắt đầu gõ để tìm kiếm...