Design Patterns Cheatsheet

Base.vn Interview Prep · Middle-level Full-stack

// last updated 2026-05-16

10 Patterns + SOLID

Quick reference cho buổi interview Base.vn (Thứ 3, 2026-05-19, 15:00). Mỗi pattern: What → When → Code → Interview Q.

5 Backend 5 Frontend SOLID YAGNI/KISS/DRY

Part 1 · Backend Patterns

Áp dụng cho NestJS, Express, FastAPI. Sample code: TypeScript.

1. Repository

Data Access

Là gì: Tách query DB ra khỏi business logic.

Khi dùng: Mọi service tương tác DB — đừng để service gọi Prisma trực tiếp.

class UserRepository {
  async findById(id: string) {
    return prisma.user.findUnique({ where: { id } });
  }
}

class UserService {
  constructor(private userRepo: UserRepository) {}
  async getUserProfile(id: string) {
    const user = await this.userRepo.findById(id);
    if (!user) throw new NotFoundException();
    return this.sanitize(user);
  }
}

Interview Q: Khi nào Repository over-engineering? → App nhỏ < 5 tables, không có nhu cầu đổi ORM.

2. Service Layer

Business Logic

Là gì: Tách business logic khỏi controller. Controller chỉ parse HTTP + delegate.

Khi dùng: Luôn dùng khi có hơn 5-line logic per endpoint.

// controller — thin
@Controller('orders')
class OrderController {
  constructor(private orderService: OrderService) {}
  @Post() create(@Body() dto: CreateOrderDto) {
    return this.orderService.createOrder(dto);
  }
}

// service — thick
class OrderService {
  async createOrder(dto: CreateOrderDto) {
    // validation, calculation, side effects
  }
}

Interview Q: Tại sao controller phải thin? → Service không phụ thuộc HTTP framework → reuse được trong CLI, queue worker, GraphQL.

3. Dependency Injection

Loose Coupling

Là gì: Class không tự tạo dependencies, nhận từ ngoài (constructor).

Khi dùng: Luôn dùng trong NestJS. Manual trong Express với tsyringe.

// ❌ Hard-coded — không test được
class OrderService {
  private emailer = new EmailService();
}

// ✅ DI — inject từ ngoài
class OrderService {
  constructor(private emailer: IEmailService) {}
}
// Test: new OrderService(mockEmailer)

Interview Q: DI khác Service Locator? → DI push vào constructor (minh bạch). Service Locator code tự kéo (ẩn). DI testable hơn.

4. Strategy

Runtime Swap

Là gì: Đóng gói thuật toán có thể đổi runtime.

Khi dùng: Nhiều "cách làm cùng việc" — vd: 3 payment provider (Stripe, Paypal, SePay).

interface PaymentStrategy {
  charge(amount: number): Promise<Result>;
}

class StripeStrategy implements PaymentStrategy { /* ... */ }
class SepayStrategy implements PaymentStrategy { /* ... */ }

class CheckoutService {
  constructor(private payment: PaymentStrategy) {}
  async process(amount: number) {
    return this.payment.charge(amount);
  }
}

Interview Q: Strategy khác if-else? → If-else vi phạm Open-Closed (sửa code cũ). Strategy mở rộng = thêm class mới.

5. Factory

Creation

Là gì: Tách logic tạo object phức tạp ra class/function riêng.

Khi dùng: Object có nhiều variant, hoặc init phức tạp.

class NotificationFactory {
  static create(type: 'email' | 'sms' | 'push'): INotification {
    switch (type) {
      case 'email': return new EmailNotification();
      case 'sms':   return new SmsNotification();
      case 'push':  return new PushNotification();
    }
  }
}
// NotificationFactory.create('email').send(...)

Interview Q: Factory vs Strategy? → Factory tạo object. Strategy đổi behavior của object đã tạo. Thường kết hợp: Factory trả về Strategy.

Part 2 · Frontend Patterns

React-focused. Hooks là modern approach, classics vẫn quan trọng.

6. Custom Hooks

Modern

Là gì: Tách stateful logic ra hàm useXxx().

Khi dùng: Logic 2+ component cùng dùng.

function useDebouncedValue<T>(value: T, delayMs: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delayMs);
    return () => clearTimeout(id);
  }, [value, delayMs]);
  return debounced;
}

// const debouncedSearch = useDebouncedValue(search, 300);

Interview Q: Khi nào extract? → Rule of 3 — chờ thấy 3 lần duplicate mới extract.

7. Container / Presentational

Classic

Là gì: Tách "smart" (fetch + state) khỏi "dumb" (chỉ render).

Khi dùng: Storybook + design system. Tanstack Query nay thay phần lớn.

// Container — fetches data
function UserListContainer() {
  const { data, isLoading } = useUsers();
  if (isLoading) return <Spinner />;
  return <UserList users={data} />;
}

// Presentational — pure UI
function UserList({ users }: { users: User[] }) {
  return <ul>{users.map(u => <li>{u.name}</li>)}</ul>;
}

Interview Q: Còn dùng không? → Vẫn cho design system: Presentational components dễ document và visual test.

8. Compound Components

Composition

Là gì: Component chia nhiều sub-component, share state ngầm qua Context.

Khi dùng: Tabs, Accordion, Select — cần flexibility.

<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Tab value="profile">Profile</Tabs.Tab>
    <Tabs.Tab value="settings">Settings</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="profile">...</Tabs.Panel>
  <Tabs.Panel value="settings">...</Tabs.Panel>
</Tabs>

Interview Q: Vì sao không 1 component lớn nhận tabs prop? → Compound = layout flexibility, dev tự custom.

9. Provider (Context)

Shared State

Là gì: Wrap subtree, share state cho descendants không cần prop drilling.

Khi dùng: Theme, Auth, i18n — data ÍT thay đổi.

const AuthContext = createContext<AuthState | null>(null);

function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  return <AuthContext.Provider value={{ user, setUser }}>
    {children}
  </AuthContext.Provider>;
}

function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be inside AuthProvider');
  return ctx;
}

Interview Q: Khi nào Context KHÔNG nên dùng? → Data đổi mỗi giây → re-render storm → dùng Zustand/Redux.

10. Render Props / HOC

Legacy

Là gì: Cách cũ share logic giữa components — trước khi có hooks.

Khi dùng: Hiện hầu như thay bằng hooks. Vẫn thấy ở React Router < v6, Formik, etc.

// Render prop
<DataFetcher url="/api/users">
  {({ data, loading }) => loading ? <Spinner /> : <List items={data} />}
</DataFetcher>

// HOC (Higher-Order Component)
const EnhancedComponent = withAuth(MyComponent);

Interview Q: Render Props vs Custom Hooks? → Hooks won. Không thêm tree depth, không có "wrapper hell", reuse state logic mà không reuse UI.

Part 3 · Principles

SOLID + composition + KISS — PHẢI nói được khi interviewer hỏi.

SOLID

Letter Name Sentence
SSingle Responsibility1 class = 1 reason to change. Vi phạm: class vừa fetch DB vừa send email.
OOpen/ClosedMở rộng = thêm class mới, không sửa class cũ. Strategy pattern là ví dụ.
LLiskov SubstitutionSubclass thay được parent mà không break. Vi phạm: Square extends Rectangle.
IInterface SegregationInterface nhỏ, focused. Không buộc implement method không liên quan.
DDependency InversionDepend on abstractions (interfaces), không depend on concretes. DI = implement nguyên lý này.

Interview Q: Đưa ví dụ SRP trong code của bạn? → Suy nghĩ trước 1 ví dụ thực tế từ Shiru.

Composition over Inheritance

Slogan: "has-a" tốt hơn "is-a".

// ❌ Inheritance — rigid
class FlyingDuck extends Duck { fly() {} }

// ✅ Composition — flexible
class Duck {
  constructor(private flyBehavior: FlyBehavior) {}
  fly() { this.flyBehavior.fly(); }
}
// new Duck(new RealFly()) hoặc new Duck(new NoFly())

React favors composition — props.children là composition.

YAGNI · KISS · DRY

  • YAGNI: You Aren't Gonna Need It. Đừng code feature "biết đâu sau dùng".
  • KISS: Keep It Simple, Stupid. Code đơn giản nhất giải quyết được = best.
  • DRY: Don't Repeat Yourself. Duplicate 3+ lần → extract.

Cẩn thận: DRY over-applied → abstraction sớm sai. Rule of 3: chờ 3 lần duplicate.

1-Page Cheat Sheet

// ═════════════ BACKEND ═════════════
Repository  → tách data access (DB) khỏi service
Service     → business logic; controller thin → service thick
DI          → constructor inject, testable, swap impl
Strategy    → swap algorithm runtime (Stripe / SePay / ...)
Factory     → tạo object phức tạp / nhiều variant

// ═════════════ FRONTEND ═════════════
Custom Hook              → reuse stateful logic (modern, hooks)
Container/Presentational → tách fetch khỏi UI (cho design system)
Compound                 → flexible composition (Tabs, Accordion)
Provider                 → share infrequent state (theme, auth)
Render Props/HOC         → legacy, mostly replaced by hooks

// ═════════════ PRINCIPLES ═════════════
SOLID:
  S - Single Responsibility
  O - Open/Closed (extend without modify)
  L - Liskov Substitution (subclass replaceable)
  I - Interface Segregation (small focused interfaces)
  D - Dependency Inversion (depend on abstractions)

Composition over Inheritance — favor "has-a"
YAGNI / KISS / DRY (rule of 3)

// ═════════════ COMMON Q ═════════════
- Khi nào Repository over-engineering? → app nhỏ < 5 tables
- Controller phải thin vì sao? → service reusable cross-protocol
- DI khác Service Locator? → DI minh bạch (push), Locator ẩn (pull)
- Strategy khác Factory? → Strategy đổi behavior, Factory tạo object
- Custom hook khi nào extract? → rule of 3
- Context không nên dùng khi? → data đổi mỗi giây (re-render storm)
- SRP example trong code bạn? → kể 1 case thực từ Shiru