patterns

Patterns - Software Design & Architecture

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "patterns" with this command: npx skills add dsantiagomj/dsmj-ai-toolkit/dsantiagomj-dsmj-ai-toolkit-patterns

Patterns - Software Design & Architecture

Know when to use (and when NOT to use) design patterns

When to Use This Skill

Use this skill when:

  • Architecting new systems or refactoring existing ones

  • Identifying code smells and anti-patterns

  • Choosing appropriate design patterns for specific problems

  • Applying SOLID principles to improve code quality

  • Making code more testable, maintainable, and extensible

Don't use this skill when:

  • Simple, straightforward code is sufficient

  • Premature optimization or abstraction would add complexity

  • The problem doesn't warrant pattern application

Critical Patterns

Pattern 1: SOLID Principles

When: Designing maintainable, testable code

Single Responsibility Principle (SRP):

// ✅ Good: Each class has one responsibility class UserRepository { save(user: User) { /* DB logic */ } }

class EmailService { sendWelcomeEmail(user: User) { /* Email logic */ } }

class UserReportGenerator { generate(user: User) { /* Report logic */ } }

// ❌ Bad: God class doing everything class UserService { saveUser(user: User) { /* ... / } sendEmail(user: User) { / ... / } generateReport(user: User) { / ... */ } }

Dependency Inversion Principle (DIP):

// ✅ Good: Depend on abstractions interface MessageSender { send(message: string): void; }

class EmailService implements MessageSender { send(message: string) { /* SMTP logic */ } }

class UserRegistration { constructor(private messageSender: MessageSender) {}

register(user: User) { this.messageSender.send(Welcome ${user.name}); } }

// ❌ Bad: Tight coupling to concrete class class UserRegistration { private emailService = new EmailService();

register(user: User) { this.emailService.send(Welcome ${user.name}); } }

Why: SOLID principles create code that's easier to test, maintain, and extend. They prevent common pitfalls like tight coupling and god classes.

For complete SOLID deep dive: SOLID Principles Reference

Pattern 2: Factory Pattern

When: Creating objects with complex initialization or multiple variants

Good:

interface Button { render(): void; }

class WindowsButton implements Button { render() { console.log('Render Windows button'); } }

class MacButton implements Button { render() { console.log('Render Mac button'); } }

class ButtonFactory { static create(os: 'windows' | 'mac'): Button { switch (os) { case 'windows': return new WindowsButton(); case 'mac': return new MacButton(); default: throw new Error('Unknown OS'); } } }

// Usage const button = ButtonFactory.create('windows'); button.render();

When to use:

  • Object creation is complex or varies by context

  • Need to decouple object creation from usage

  • Creating families of related objects

When NOT to use:

  • Simple object creation (just use new )

  • Only one type of object exists

Pattern 3: Strategy Pattern

When: Swapping algorithms or behaviors at runtime

Good:

interface PaymentStrategy { pay(amount: number): void; }

class CreditCardPayment implements PaymentStrategy { pay(amount: number) { console.log(Paid $${amount} with credit card); } }

class PayPalPayment implements PaymentStrategy { pay(amount: number) { console.log(Paid $${amount} with PayPal); } }

class ShoppingCart { constructor(private paymentStrategy: PaymentStrategy) {}

checkout(amount: number) { this.paymentStrategy.pay(amount); } }

// Usage const cart = new ShoppingCart(new CreditCardPayment()); cart.checkout(100);

Bad:

// ❌ Multiple if/else statements class ShoppingCart { checkout(amount: number, paymentType: string) { if (paymentType === 'creditCard') { // Process credit card } else if (paymentType === 'paypal') { // Process PayPal } else if (paymentType === 'crypto') { // Process crypto } } }

Why: Strategy pattern eliminates conditional logic and makes adding new payment methods easy without modifying existing code (Open/Closed Principle).

Pattern 4: Observer Pattern

When: Implementing event-driven systems or pub/sub

Good:

interface Observer { update(data: any): void; }

class Subject { private observers: Observer[] = [];

subscribe(observer: Observer) { this.observers.push(observer); }

notify(data: any) { this.observers.forEach(observer => observer.update(data)); } }

class EmailSubscriber implements Observer { update(data: any) { console.log(Email sent: ${data}); } }

class SMSSubscriber implements Observer { update(data: any) { console.log(SMS sent: ${data}); } }

// Usage const newsletter = new Subject(); newsletter.subscribe(new EmailSubscriber()); newsletter.subscribe(new SMSSubscriber()); newsletter.notify('New article published!');

When to use:

  • One-to-many dependencies

  • Event systems

  • State synchronization across components

Pattern 5: Adapter Pattern

When: Integrating incompatible interfaces

Good:

// Legacy interface class OldPaymentGateway { processPayment(amount: number) { console.log(Old gateway: $${amount}); } }

// New interface interface PaymentProcessor { pay(amount: number, currency: string): void; }

// Adapter class PaymentAdapter implements PaymentProcessor { constructor(private oldGateway: OldPaymentGateway) {}

pay(amount: number, currency: string) { if (currency !== 'USD') { throw new Error('Old gateway only supports USD'); } this.oldGateway.processPayment(amount); } }

When to use:

  • Integrating third-party libraries

  • Working with legacy code

  • Reusing existing classes with different interfaces

Anti-Patterns

❌ Anti-Pattern 1: Premature Abstraction

Don't do this:

// ❌ Over-engineered for simple config interface ConfigStrategy { get(key: string): any; } class JSONConfigStrategy implements ConfigStrategy { /* ... / } class YAMLConfigStrategy implements ConfigStrategy { / ... / } class ConfigFactory { / ... */ }

Do this instead:

// ✅ Start simple const config = { apiUrl: 'https://api.example.com', timeout: 5000 };

Why: YAGNI (You Aren't Gonna Need It). Add abstraction when you have a concrete need, not "just in case."

❌ Anti-Pattern 2: God Object

Don't do this:

// ❌ One class doing everything class Application { handleAuth() { /* ... / } renderUI() { / ... / } saveToDatabase() { / ... / } sendEmail() { / ... / } generateReports() { / ... */ } // 50 more methods... }

Do this instead:

// ✅ Apply Single Responsibility Principle class AuthService { /* ... / } class UIRenderer { / ... / } class DatabaseService { / ... / } class EmailService { / ... / } class ReportGenerator { / ... */ }

❌ Anti-Pattern 3: Copy-Paste Programming

Don't do this:

// ❌ Duplicated code function validateEmail(email: string) { return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email); }

function validateUserEmail(user: User) { return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(user.email); }

Do this instead:

// ✅ DRY (Don't Repeat Yourself) function isValidEmail(email: string) { return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email); }

function validateUserEmail(user: User) { return isValidEmail(user.email); }

Code Examples

Example 1: Factory Pattern for Payment Processing

interface PaymentProcessor { process(amount: number): Promise<void>; }

class StripeProcessor implements PaymentProcessor { async process(amount: number) { console.log(Processing $${amount} via Stripe); } }

class PayPalProcessor implements PaymentProcessor { async process(amount: number) { console.log(Processing $${amount} via PayPal); } }

class PaymentFactory { static create(provider: 'stripe' | 'paypal'): PaymentProcessor { switch (provider) { case 'stripe': return new StripeProcessor(); case 'paypal': return new PayPalProcessor(); } } }

// Usage const processor = PaymentFactory.create('stripe'); await processor.process(99.99);

Example 2: Dependency Injection for Testability

interface Logger { log(message: string): void; }

class UserService { constructor(private logger: Logger) {}

createUser(name: string) { this.logger.log(Creating user: ${name}); // User creation logic } }

// Production const service = new UserService(new ConsoleLogger());

// Testing const mockLogger = { log: jest.fn() }; const testService = new UserService(mockLogger);

For comprehensive examples and detailed implementations, see the references/ folder.

Quick Reference

When to Use Each Pattern

Pattern Use When Don't Use When

Factory Complex object creation, multiple variants Simple new is sufficient

Builder Many optional parameters Few parameters, simple construction

Singleton Truly need ONE global instance Just want convenience (use DI)

Adapter Integrating incompatible interfaces Interfaces already compatible

Decorator Adding responsibilities dynamically Static behavior is fine

Facade Simplifying complex subsystem Subsystem is already simple

Observer Event-driven, one-to-many updates Simple callbacks work

Strategy Swappable algorithms at runtime Fixed algorithm

Command Undo/redo, queuing operations Direct method calls work

SOLID Quick Checklist

  • S: Each class has single, focused responsibility

  • O: New features added via extension, not modification

  • L: Subtypes don't break parent class behavior

  • I: Interfaces are small and focused

  • D: Depend on abstractions, inject dependencies

Progressive Disclosure

For detailed implementations and examples:

  • Design Patterns Guide - Complete pattern implementations (Factory, Builder, Singleton, Adapter, Decorator, Facade, Observer, Strategy, Command)

  • SOLID Principles - Deep dive into SRP, OCP, LSP, ISP, DIP with real-world examples

References

  • Design Patterns Guide

  • SOLID Principles

  • Refactoring.Guru - Design Patterns

  • Refactoring.Guru - SOLID Principles

Maintained by dsmj-ai-toolkit

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

vercel-ai-sdk

No summary provided by upstream source.

Repository SourceNeeds Review
General

caching

No summary provided by upstream source.

Repository SourceNeeds Review
General

react

No summary provided by upstream source.

Repository SourceNeeds Review