Software Engineering Patterns

FREE
intermediatev1.0.0tokenshrink-v2
# Software Engineering Patterns Knowledge Pack

## SOLID Principles

### SRP — Single Responsibility Principle
A cls should have only one reason to change. This means each cls encapsulates one cohesive responsibility. Violation indicators: cls names containing "And" or "Manager" that do many things, cls with methods operating on unrelated data, changes to one feature requiring edits to unrelated methods in the same cls.

Example: A `UserService` that handles authentication, profile updates, AND email sending violates SRP. Refactor into `AuthService`, `ProfileService`, and `EmailService`. Each can change independently.

### OCP — Open-Closed Principle
Software entities should be open for extension but closed for modification. When new behavior is needed, extend through new code rather than modifying existing working code.

Impl strategy: Use abs cls or iface to define contracts. New behavior is added as new conc cls that impl the iface. Strategy pattern is the canonical OCP impl. Example: Instead of a switch statement over payment types inside a `processPayment` fn, define a `PaymentProcessor` iface with a `process` method, then create `CreditCardProcessor`, `PayPalProcessor`, etc. Adding a new payment type means adding a new cls, not editing existing code.

### LSP — Liskov Substitution Principle
Subtypes must be substitutable for their base types without altering correctness. If cls B extends cls A, anywhere A is used, B should work without surprises.

Common violations: Square extending Rectangle (changing width changes height — breaks Rectangle behavior), throwing NotImplementedException in overridden methods, strengthening preconditions or weakening postconditions in subtype.

Test: Can you use the derived cls everywhere the base cls is expected without checking the type? If you need `instanceof` checks, you're violating LSP.

### ISP — Interface Segregation Principle
Clients should not be forced to depend on methods they don't use. Prefer many small, focused ifaces over one large "fat" iface.

Example: A `Worker` iface with `work()`, `eat()`, `sleep()` forces a `Robot` cls to impl `eat()` and `sleep()`. Better: split into `Workable`, `Eatable`, `Sleepable` ifaces. `Human` impls all three, `Robot` impls only `Workable`.

### DIP — Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abs. Abs should not depend on details — details should depend on abs.

Practical impl: A `NotificationService` (high-level) should depend on a `MessageSender` iface (abs), not directly on `SmtpEmailSender` (low-level detail). The conc impl is provided via DI. This allows swapping email for SMS or push notifications without changing the svc logic.

## Design Patterns

### Creational Patterns

**Factory Method**: Define an iface for creating objs, but let subclasses decide which cls to inst. Use when: a cls can't anticipate which objs it needs to create, or you want subclasses to specify created objs.

```
iface Logger { log(msg: string): void }
cls FileLogger impls Logger { ... }
cls ConsoleLogger impls Logger { ... }
cls LoggerFactory { create(type: string): Logger }
```

**Abstract Factory**: Provides an iface for creating families of related objs without specifying conc cls. Use when: system should be independent of how products are created, or you need to ensure related objs are used together (e.g., UI themes: DarkButton + DarkTextBox, not DarkButton + LightTextBox).

**Builder**: Separates construction of a complex obj from its representation. Use when: constructor has many parameters (especially optional ones), or obj requires step-by-step construction. Fluent API style: `new QueryBuilder().select('name').from('users').where('active = true').build()`

**Singleton**: Ensures a cls has exactly one inst with a global access point. Use sparingly — often a sign of hidden global state. Acceptable uses: cfg managers, connection pools, loggers. Prefer DI container managing lifecycle over raw singleton.

### Structural Patterns

**Adapter**: Converts the iface of a cls into another iface clients expect. Use when integrating third-party libraries or legacy code with incompatible ifaces. Example: wrapping a legacy XML API to expose a JSON iface.

**Decorator**: Attaches additional responsibilities to an obj dynamically. Alternative to subclassing for extending functionality. Example: `LoggingRepo` wraps `DatabaseRepo`, adds logging before/after each method call without modifying the original cls. Decorators are composable: `CachingRepo(LoggingRepo(DatabaseRepo()))`.

**Facade**: Provides a simplified iface to a complex subsystem. Use when: you want to reduce coupling between clients and subsystem, or provide a simple entry point for common use cases while keeping the full subsystem accessible for advanced use.

### Behavioral Patterns

**Observer**: Defines a one-to-many dep between objs so that when one obj changes state, all dependents are notified. Foundation of event-driven architecture. Key components: Subject (maintains observer list, notifies on change), Observer (defines update iface). Modern impls: EventEmitter, RxJS Observables, pub/sub systems.

**Strategy**: Defines a family of algorithms, encapsulates each one, makes them interchangeable. Eliminates conditional statements for selecting behavior. Example: sorting — pass a `Comparator` strategy rather than hardcoding sort logic. Compression: accept a `CompressionStrategy` iface with conc impls for gzip, brotli, zstd.

**Command**: Encapsulates a req as an obj, allowing parameterization of clients with different reqs, queuing, logging, and undo operations. Components: Command (execute iface), ConcreteCommand (binds receiver + action), Invoker (triggers command), Receiver (performs actual work). Enables undo/redo stacks, macro recording, and transactional behavior.

**Template Method**: Defines the skeleton of an algorithm in a base cls, letting subclasses override specific steps without changing the algorithm's structure. Example: a `DataMiner` base cls with final method `mine()` that calls `openFile()`, `extractData()`, `parseData()`, `analyzeData()`, `sendReport()`. Subclasses override `extractData()` and `parseData()` for CSV, JSON, XML formats.

## Clean Architecture

### Layer Structure (inside-out)
1. **Entities** (innermost): Enterprise business rules. Pure domain objs with no deps on frameworks. Example: `Invoice` with `calculateTotal()`, `isOverdue()` — these rules exist regardless of delivery mechanism
2. **Use Cases**: Application-specific business rules. Orchestrate data flow to/from entities. One cls per use case: `CreateOrderUseCase`, `CancelSubscriptionUseCase`. Depend on entity layer only
3. **Interface Adapters**: Convert data between use case format and external format. Includes ctrl (web), presenters, repo impls, dto mappers. The repo pattern lives here — iface in use case layer, impl in adapter layer
4. **Frameworks & Drivers** (outermost): Web framework, database, UI, external svcs. Most volatile layer — should be swappable without affecting inner layers

### Dependency Rule
Deps only point inward. Inner layers know nothing about outer layers. A use case defines a `UserRepo` iface; the adapter layer provides `PostgresUserRepo` impl. DI wires them together at composition root (application startup).

### Practical Patterns
- **Repo pattern**: abs data access behind an iface. Use case calls `repo.findById(id)` without knowing if it's SQL, NoSQL, API, or in-memory
- **dto**: Carry data between layers without exposing domain entities to external concerns. Map at boundaries
- **Composition root**: Single place where all deps are resolved and injected. Typically in main/startup. No service locator anti-pattern

## Code Review Practices

### What to Review
1. **Correctness**: Does the code do what it claims? Edge cases handled? Off-by-one errors?
2. **Design**: Does it follow established patterns? Are deps in the right direction? Is complexity warranted?
3. **Readability**: Can a new team member understand this? Are names descriptive? Is the flow clear?
4. **Testing**: Are tests meaningful (not just covering lines)? Do they test behavior, not impl? Are edge cases covered?
5. **Security**: Input validation? SQL inj? XSS? Auth checks? Secrets exposed?
6. **Perf**: O(n^2) where O(n) is possible? N+1 queries? Unbounded memory growth? Missing indexes?

### Review Etiquette
- Comment on the code, not the person. "This fn could be clearer" not "You wrote confusing code"
- Distinguish: must-fix (blocking), should-fix (important), nit (style, optional)
- Explain why, not just what. "Consider extracting this because it violates SRP and will make testing harder" vs "Extract this"
- Approve when good enough — don't block on perfection. The perfect is the enemy of the shipped
- Small PRs reviewed faster and more thoroughly. Target <400 lines changed

## Testing Strategies

### Test Pyramid
- **Unit tests** (base, most numerous): Test individual fns/cls in isolation. Fast, deterministic, no I/O. Mock external deps at boundaries. Aim for high coverage of business logic
- **Integration tests** (middle): Test interaction between components. Real database (use test containers), real file system, real HTTP between svcs. Slower but catch wiring issues
- **E2E tests** (top, fewest): Test full user workflows through the entire stack. Slowest, most brittle. Cover critical paths only: signup, purchase, core features

### Testing Principles
- **Arrange-Act-Assert** (AAA): Setup test data, execute the fn under test, verify the result. Each test should have exactly one reason to fail
- **Test behavior, not impl**: Test public iface, not private methods. Tests should survive refactoring if behavior is unchanged
- **FIRST**: Fast, Independent (no test depends on another), Repeatable (deterministic), Self-validating (pass/fail, no manual inspection), Timely (written with or before code)
- **Test doubles**: Stub (returns canned data), Mock (verifies interactions), Fake (working impl with shortcuts, like in-memory db), Spy (records calls for later assertion)
- **Mutation testing**: Modify code (change `>` to `>=`, delete lines) and verify tests catch it. Reveals weak tests that pass regardless of correctness

3.4K

tokens

13.0%

savings

Downloads0
Sign in to DownloadCompressed by TokenShrink