specky-sdd 2.0.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +803 -88
- package/SECURITY.md +110 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +66 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +100 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +112 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/schemas/environment.d.ts +12 -37
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/infrastructure.d.ts +22 -42
- package/dist/schemas/infrastructure.d.ts.map +1 -1
- package/dist/schemas/input.d.ts +13 -34
- package/dist/schemas/input.d.ts.map +1 -1
- package/dist/schemas/integration.d.ts +12 -80
- package/dist/schemas/integration.d.ts.map +1 -1
- package/dist/schemas/pipeline.d.ts +24 -230
- package/dist/schemas/pipeline.d.ts.map +1 -1
- package/dist/schemas/quality.d.ts +27 -39
- package/dist/schemas/quality.d.ts.map +1 -1
- package/dist/schemas/quality.js +13 -0
- package/dist/schemas/quality.js.map +1 -1
- package/dist/schemas/testing.d.ts +23 -0
- package/dist/schemas/testing.d.ts.map +1 -0
- package/dist/schemas/testing.js +26 -0
- package/dist/schemas/testing.js.map +1 -0
- package/dist/schemas/transcript.d.ts +18 -40
- package/dist/schemas/transcript.d.ts.map +1 -1
- package/dist/schemas/utility.d.ts +33 -65
- package/dist/schemas/utility.d.ts.map +1 -1
- package/dist/schemas/visualization.d.ts +28 -39
- package/dist/schemas/visualization.d.ts.map +1 -1
- package/dist/services/test-generator.d.ts +61 -0
- package/dist/services/test-generator.d.ts.map +1 -0
- package/dist/services/test-generator.js +217 -0
- package/dist/services/test-generator.js.map +1 -0
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +12 -0
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/integration.d.ts.map +1 -1
- package/dist/tools/integration.js +24 -0
- package/dist/tools/integration.js.map +1 -1
- package/dist/tools/quality.d.ts +3 -2
- package/dist/tools/quality.d.ts.map +1 -1
- package/dist/tools/quality.js +84 -3
- package/dist/tools/quality.js.map +1 -1
- package/dist/tools/testing.d.ts +9 -0
- package/dist/tools/testing.d.ts.map +1 -0
- package/dist/tools/testing.js +130 -0
- package/dist/tools/testing.js.map +1 -0
- package/dist/tools/utility.d.ts.map +1 -1
- package/dist/tools/utility.js +36 -1
- package/dist/tools/utility.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/hooks/auto-docs.md +53 -0
- package/hooks/auto-test.md +61 -0
- package/hooks/changelog.md +74 -0
- package/hooks/security-scan.md +72 -0
- package/hooks/spec-sync.md +80 -0
- package/hooks/srp-validator.md +86 -0
- package/package.json +14 -5
- package/references/design-patterns.md +434 -0
- package/references/ears-notation.md +605 -0
- package/references/spec-templates.md +936 -0
- package/templates/analysis.md +1 -0
- package/templates/api-docs.md +1 -0
- package/templates/bugfix.md +1 -0
- package/templates/checklist.md +1 -0
- package/templates/compliance.md +1 -0
- package/templates/constitution.md +1 -0
- package/templates/cross-analysis.md +1 -0
- package/templates/data-model.md +1 -0
- package/templates/design.md +1 -0
- package/templates/devcontainer.md +1 -0
- package/templates/dockerfile.md +1 -0
- package/templates/onboarding.md +1 -0
- package/templates/research.md +1 -0
- package/templates/runbook.md +1 -0
- package/templates/specification.md +1 -0
- package/templates/sync-report.md +1 -0
- package/templates/tasks.md +3 -2
- package/templates/terraform.md +1 -0
- package/templates/test-stub.md +34 -0
- package/templates/user-stories.md +1 -0
- package/templates/verification.md +1 -0
- package/templates/work-items.md +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Software Design Patterns Reference for Architecture and Design
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
date: 2026-03-21
|
|
5
|
+
author: Specky
|
|
6
|
+
description: Curated reference of software design patterns used in SDD architecture and design phases
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Software Design Patterns Reference
|
|
10
|
+
|
|
11
|
+
## Introduction
|
|
12
|
+
|
|
13
|
+
This reference covers the design patterns most relevant to software systems designed with the **Spec-Driven Development (SDD)** methodology. Each pattern includes intent, structure, participants, when to use, when to avoid, and a pseudocode example to anchor the DESIGN.md author.
|
|
14
|
+
|
|
15
|
+
Use this guide during the **Design phase** (`sdd_write_design`) to select appropriate patterns and justify architecture decisions in DESIGN.md.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Creational Patterns
|
|
20
|
+
|
|
21
|
+
### Factory Method
|
|
22
|
+
|
|
23
|
+
**Intent:** Define an interface for creating an object, but let subclasses decide which class to instantiate.
|
|
24
|
+
|
|
25
|
+
**When to use:**
|
|
26
|
+
- The exact type of object to create is determined at runtime
|
|
27
|
+
- You want to decouple client code from concrete implementations
|
|
28
|
+
- You anticipate adding new product types in the future
|
|
29
|
+
|
|
30
|
+
**When to avoid:**
|
|
31
|
+
- There are only one or two concrete implementations that will never change
|
|
32
|
+
- The overhead of the abstraction outweighs the flexibility benefit
|
|
33
|
+
|
|
34
|
+
**Example:**
|
|
35
|
+
```typescript
|
|
36
|
+
interface DocumentParser {
|
|
37
|
+
parse(content: string): ParsedDocument;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class PdfParser implements DocumentParser { ... }
|
|
41
|
+
class DocxParser implements DocumentParser { ... }
|
|
42
|
+
|
|
43
|
+
function createParser(mimeType: string): DocumentParser {
|
|
44
|
+
if (mimeType === "application/pdf") return new PdfParser();
|
|
45
|
+
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") return new DocxParser();
|
|
46
|
+
throw new Error(`Unsupported MIME type: ${mimeType}`);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**SDD usage:** `DocumentConverter` service uses this pattern to dispatch to format-specific parsers based on file extension.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### Builder
|
|
55
|
+
|
|
56
|
+
**Intent:** Separate the construction of a complex object from its representation so the same construction process can create different representations.
|
|
57
|
+
|
|
58
|
+
**When to use:**
|
|
59
|
+
- Object construction requires many parameters or multi-step initialization
|
|
60
|
+
- You want immutable objects with many optional fields
|
|
61
|
+
- The same construction process should produce different output types
|
|
62
|
+
|
|
63
|
+
**When to avoid:**
|
|
64
|
+
- The object is simple with few fields
|
|
65
|
+
- The object is mutable and doesn't require a staged construction
|
|
66
|
+
|
|
67
|
+
**Example:**
|
|
68
|
+
```typescript
|
|
69
|
+
class ReportBuilder {
|
|
70
|
+
private title = "";
|
|
71
|
+
private sections: Section[] = [];
|
|
72
|
+
|
|
73
|
+
withTitle(title: string): this { this.title = title; return this; }
|
|
74
|
+
addSection(section: Section): this { this.sections.push(section); return this; }
|
|
75
|
+
build(): Report { return new Report(this.title, this.sections); }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**SDD usage:** `TemplateEngine.renderWithFrontmatter()` acts as a builder — assembles YAML frontmatter + Markdown body into a final document.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### Singleton
|
|
84
|
+
|
|
85
|
+
**Intent:** Ensure a class has only one instance and provide a global access point to it.
|
|
86
|
+
|
|
87
|
+
**When to use:**
|
|
88
|
+
- Shared resource management (connection pool, configuration, logger)
|
|
89
|
+
- When instantiating multiple instances would be incorrect or expensive
|
|
90
|
+
|
|
91
|
+
**When to avoid:**
|
|
92
|
+
- Unit testing scenarios where test isolation is critical
|
|
93
|
+
- When state leakage between tests is a concern (prefer dependency injection)
|
|
94
|
+
|
|
95
|
+
**Example:**
|
|
96
|
+
```typescript
|
|
97
|
+
class ConfigManager {
|
|
98
|
+
private static instance: ConfigManager;
|
|
99
|
+
private constructor() { /* load config */ }
|
|
100
|
+
static getInstance(): ConfigManager {
|
|
101
|
+
if (!ConfigManager.instance) ConfigManager.instance = new ConfigManager();
|
|
102
|
+
return ConfigManager.instance;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Structural Patterns
|
|
110
|
+
|
|
111
|
+
### Adapter
|
|
112
|
+
|
|
113
|
+
**Intent:** Convert the interface of a class into another interface clients expect. Lets classes work together that couldn't otherwise because of incompatible interfaces.
|
|
114
|
+
|
|
115
|
+
**When to use:**
|
|
116
|
+
- Integrating a third-party library with an incompatible interface
|
|
117
|
+
- Wrapping legacy code with a modern interface
|
|
118
|
+
- Building a unified interface over multiple external APIs
|
|
119
|
+
|
|
120
|
+
**When to avoid:**
|
|
121
|
+
- The external interface is stable and simple — thin wrappers add unnecessary complexity
|
|
122
|
+
|
|
123
|
+
**Example:**
|
|
124
|
+
```typescript
|
|
125
|
+
// External SDK with shape we don't control
|
|
126
|
+
class ExternalMcpClient {
|
|
127
|
+
sendRequest(payload: unknown): Promise<unknown> { ... }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Our interface
|
|
131
|
+
interface McpGateway {
|
|
132
|
+
call(tool: string, args: Record<string, unknown>): Promise<McpResult>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class McpClientAdapter implements McpGateway {
|
|
136
|
+
constructor(private client: ExternalMcpClient) {}
|
|
137
|
+
async call(tool: string, args: Record<string, unknown>): Promise<McpResult> {
|
|
138
|
+
const raw = await this.client.sendRequest({ tool, arguments: args });
|
|
139
|
+
return parseMcpResult(raw);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**SDD usage:** MCP-to-MCP routing in `sdd_create_pr` / `sdd_export_work_items` wraps GitHub/Azure DevOps MCP servers through adapter-style wrappers.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Facade
|
|
149
|
+
|
|
150
|
+
**Intent:** Provide a simplified interface to a complex subsystem.
|
|
151
|
+
|
|
152
|
+
**When to use:**
|
|
153
|
+
- You want to provide a simple interface to a complex body of code
|
|
154
|
+
- You want to layer your subsystem and use a facade to define entry points at each level
|
|
155
|
+
- Clients should be shielded from the complexity of subsystems
|
|
156
|
+
|
|
157
|
+
**When to avoid:**
|
|
158
|
+
- The simplified interface still exposes too much complexity
|
|
159
|
+
- You need fine-grained control over the subsystem that the facade would hide
|
|
160
|
+
|
|
161
|
+
**Example:**
|
|
162
|
+
```typescript
|
|
163
|
+
class SpecPipelineFacade {
|
|
164
|
+
constructor(
|
|
165
|
+
private fileManager: FileManager,
|
|
166
|
+
private stateMachine: StateMachine,
|
|
167
|
+
private templateEngine: TemplateEngine,
|
|
168
|
+
) {}
|
|
169
|
+
|
|
170
|
+
async runFullPipeline(featureDir: string): Promise<void> {
|
|
171
|
+
await this.stateMachine.advance(featureDir, "init");
|
|
172
|
+
const spec = await this.fileManager.readSpecFile(featureDir, "SPECIFICATION.md");
|
|
173
|
+
const analyzed = await this.analyzeSpec(spec);
|
|
174
|
+
await this.fileManager.writeSpecFile(featureDir, "ANALYSIS.md", analyzed);
|
|
175
|
+
await this.stateMachine.advance(featureDir, "analyze");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**SDD usage:** Each MCP tool is a facade over the service layer. `sdd_auto_pipeline` is the highest-level facade — it wraps the full 10-phase pipeline.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Composite
|
|
185
|
+
|
|
186
|
+
**Intent:** Compose objects into tree structures to represent part-whole hierarchies. Lets clients treat individual objects and compositions uniformly.
|
|
187
|
+
|
|
188
|
+
**When to use:**
|
|
189
|
+
- You need to represent hierarchies (file trees, requirement hierarchies, task trees)
|
|
190
|
+
- You want clients to ignore the difference between leaf and composite objects
|
|
191
|
+
|
|
192
|
+
**Example:**
|
|
193
|
+
```typescript
|
|
194
|
+
interface Requirement {
|
|
195
|
+
id: string;
|
|
196
|
+
validate(): ValidationResult;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class LeafRequirement implements Requirement {
|
|
200
|
+
validate(): ValidationResult { return earsValidator.validate(this.text); }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class RequirementGroup implements Requirement {
|
|
204
|
+
private children: Requirement[] = [];
|
|
205
|
+
add(r: Requirement): void { this.children.push(r); }
|
|
206
|
+
validate(): ValidationResult {
|
|
207
|
+
const results = this.children.map((c) => c.validate());
|
|
208
|
+
return aggregateResults(results);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Behavioral Patterns
|
|
216
|
+
|
|
217
|
+
### Strategy
|
|
218
|
+
|
|
219
|
+
**Intent:** Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
|
|
220
|
+
|
|
221
|
+
**When to use:**
|
|
222
|
+
- You need different variants of an algorithm (e.g., different export formats, different diagram types)
|
|
223
|
+
- You want to eliminate conditional logic that selects behavior at runtime
|
|
224
|
+
|
|
225
|
+
**When to avoid:**
|
|
226
|
+
- There are only 2 strategies and they rarely change
|
|
227
|
+
- Clients must be aware of different strategies, which complicates client code
|
|
228
|
+
|
|
229
|
+
**Example:**
|
|
230
|
+
```typescript
|
|
231
|
+
interface ExportStrategy {
|
|
232
|
+
export(tasks: Task[]): string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class GitHubIssuesExporter implements ExportStrategy { ... }
|
|
236
|
+
class AzureBoardsExporter implements ExportStrategy { ... }
|
|
237
|
+
class JiraExporter implements ExportStrategy { ... }
|
|
238
|
+
|
|
239
|
+
class WorkItemExporter {
|
|
240
|
+
constructor(private strategy: ExportStrategy) {}
|
|
241
|
+
export(tasks: Task[]): string { return this.strategy.export(tasks); }
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**SDD usage:** `WorkItemExporter` uses Strategy — the `platform` parameter (`github`, `azure_boards`, `jira`) selects the export strategy at runtime.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### State Machine
|
|
250
|
+
|
|
251
|
+
**Intent:** Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
|
|
252
|
+
|
|
253
|
+
**When to use:**
|
|
254
|
+
- An object's behavior depends on its state and must change at runtime
|
|
255
|
+
- Operations have large, multipart conditional statements that depend on the object's state
|
|
256
|
+
|
|
257
|
+
**When to avoid:**
|
|
258
|
+
- The state transitions are trivial and only 2-3 states exist
|
|
259
|
+
- Simpler boolean flags convey the same intent more clearly
|
|
260
|
+
|
|
261
|
+
**Example:**
|
|
262
|
+
```typescript
|
|
263
|
+
type Phase = "init" | "discover" | "specify" | "design" | "tasks" | "analyze" | "implement" | "verify" | "release";
|
|
264
|
+
|
|
265
|
+
class PipelineStateMachine {
|
|
266
|
+
private state: Phase = "init";
|
|
267
|
+
private readonly transitions: Record<Phase, Phase[]> = {
|
|
268
|
+
init: ["discover"],
|
|
269
|
+
discover: ["specify"],
|
|
270
|
+
specify: ["clarify", "design"],
|
|
271
|
+
clarify: ["design"],
|
|
272
|
+
design: ["tasks"],
|
|
273
|
+
tasks: ["analyze"],
|
|
274
|
+
analyze: ["implement"],
|
|
275
|
+
implement: ["verify"],
|
|
276
|
+
verify: ["release"],
|
|
277
|
+
release: [],
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
advance(to: Phase): void {
|
|
281
|
+
if (!this.transitions[this.state].includes(to)) {
|
|
282
|
+
throw new Error(`Invalid transition: ${this.state} → ${to}`);
|
|
283
|
+
}
|
|
284
|
+
this.state = to;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**SDD usage:** `StateMachine` service implements this pattern to enforce the 10-phase SDD pipeline. Each `sdd_advance_phase` call transitions the state machine.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### Observer
|
|
294
|
+
|
|
295
|
+
**Intent:** Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
|
|
296
|
+
|
|
297
|
+
**When to use:**
|
|
298
|
+
- When a change to one object requires changing others, and you don't know how many objects need to change
|
|
299
|
+
- An object should notify other objects without assuming who those objects are
|
|
300
|
+
|
|
301
|
+
**When to avoid:**
|
|
302
|
+
- The number of observers is small and fixed
|
|
303
|
+
- The notification order matters and must be deterministic
|
|
304
|
+
|
|
305
|
+
**Example:**
|
|
306
|
+
```typescript
|
|
307
|
+
interface PipelineObserver {
|
|
308
|
+
onPhaseChange(from: Phase, to: Phase, featureDir: string): void;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
class StateMachine {
|
|
312
|
+
private observers: PipelineObserver[] = [];
|
|
313
|
+
subscribe(observer: PipelineObserver): void { this.observers.push(observer); }
|
|
314
|
+
private notify(from: Phase, to: Phase, featureDir: string): void {
|
|
315
|
+
for (const obs of this.observers) obs.onPhaseChange(from, to, featureDir);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### Command
|
|
323
|
+
|
|
324
|
+
**Intent:** Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
|
|
325
|
+
|
|
326
|
+
**When to use:**
|
|
327
|
+
- You want to parameterize objects with an action to perform
|
|
328
|
+
- You need to support undo/redo
|
|
329
|
+
- You need to queue operations or log them for audit
|
|
330
|
+
|
|
331
|
+
**Example:**
|
|
332
|
+
```typescript
|
|
333
|
+
interface SpecCommand {
|
|
334
|
+
execute(): Promise<void>;
|
|
335
|
+
undo(): Promise<void>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
class WriteSpecCommand implements SpecCommand {
|
|
339
|
+
async execute(): Promise<void> { await fileManager.writeSpecFile(...); }
|
|
340
|
+
async undo(): Promise<void> { await fileManager.deleteSpecFile(...); }
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Architectural Patterns
|
|
347
|
+
|
|
348
|
+
### Layered Architecture (N-Tier)
|
|
349
|
+
|
|
350
|
+
**Intent:** Organize code into horizontal layers where each layer has a specific role and depends only on the layer directly below it.
|
|
351
|
+
|
|
352
|
+
**Layers in Specky:**
|
|
353
|
+
```
|
|
354
|
+
┌─────────────────────────────┐
|
|
355
|
+
│ MCP Tools (tools/) │ ← Input validation, output formatting
|
|
356
|
+
├─────────────────────────────┤
|
|
357
|
+
│ Services (services/) │ ← Business logic, domain rules
|
|
358
|
+
├─────────────────────────────┤
|
|
359
|
+
│ File Manager │ ← I/O abstraction (single point of disk access)
|
|
360
|
+
└─────────────────────────────┘
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Rules:**
|
|
364
|
+
- Tools MUST NOT access the filesystem directly — always via `FileManager`
|
|
365
|
+
- Services MUST NOT import from tools
|
|
366
|
+
- `FileManager` MUST NOT contain business logic
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### Repository Pattern
|
|
371
|
+
|
|
372
|
+
**Intent:** Mediate between the domain and data mapping layers using a collection-like interface for accessing domain objects.
|
|
373
|
+
|
|
374
|
+
**When to use:**
|
|
375
|
+
- To abstract data access and make business logic testable without I/O
|
|
376
|
+
- To centralize query logic for a data source
|
|
377
|
+
|
|
378
|
+
**SDD usage:** `FileManager` is a repository for spec artifacts. All reads/writes to `.specs/` go through `FileManager`, enabling easy mocking in tests.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### Pipeline / Chain of Responsibility
|
|
383
|
+
|
|
384
|
+
**Intent:** Pass a request along a chain of handlers. Each handler decides either to process the request or pass it to the next handler in the chain.
|
|
385
|
+
|
|
386
|
+
**When to use:**
|
|
387
|
+
- Multiple handlers may process a request
|
|
388
|
+
- You want to decouple sender from receivers
|
|
389
|
+
- The set of handlers can vary dynamically
|
|
390
|
+
|
|
391
|
+
**SDD usage:** `sdd_auto_pipeline` and `sdd_batch_transcripts` implement a pipeline — each phase feeds into the next, and any phase can short-circuit on error.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Anti-Patterns to Avoid
|
|
396
|
+
|
|
397
|
+
| Anti-Pattern | Symptom | Fix |
|
|
398
|
+
|---|---|---|
|
|
399
|
+
| **God Object** | One class/service handles everything | Split into single-responsibility services |
|
|
400
|
+
| **Magic Numbers** | `if (status === 4)` | Use named constants or enums |
|
|
401
|
+
| **Primitive Obsession** | Passing raw strings for typed values (phase names, feature numbers) | Define types or value objects |
|
|
402
|
+
| **Shotgun Surgery** | One change requires modifying many files | Group related behavior using cohesion |
|
|
403
|
+
| **Feature Envy** | Method uses data from another class more than its own | Move method to the class whose data it uses |
|
|
404
|
+
| **Leaky Abstractions** | Implementation details bleed through the API | Strengthen interface boundaries |
|
|
405
|
+
| **Tight Coupling via New** | `new ConcreteService()` inside another service | Inject dependencies via constructor |
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Pattern Selection Guide
|
|
410
|
+
|
|
411
|
+
Use this table during the Design phase to select patterns for your DESIGN.md:
|
|
412
|
+
|
|
413
|
+
| Scenario | Recommended Pattern |
|
|
414
|
+
|---|---|
|
|
415
|
+
| Multiple output formats for the same data | Strategy |
|
|
416
|
+
| Complex multi-step object creation | Builder |
|
|
417
|
+
| Wrapping an incompatible external API | Adapter |
|
|
418
|
+
| Simplifying a complex subsystem | Facade |
|
|
419
|
+
| State-dependent behavior with transitions | State Machine |
|
|
420
|
+
| Hierarchical data structures | Composite |
|
|
421
|
+
| Pluggable algorithms | Strategy |
|
|
422
|
+
| Cross-cutting notifications | Observer |
|
|
423
|
+
| Undo/redo, queued operations | Command |
|
|
424
|
+
| Data access abstraction | Repository |
|
|
425
|
+
| Sequential processing with early exit | Pipeline |
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## References
|
|
430
|
+
|
|
431
|
+
- Gamma, E., Helm, R., Johnson, R., Vlissides, J. (1994). *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley.
|
|
432
|
+
- Fowler, M. (2002). *Patterns of Enterprise Application Architecture*. Addison-Wesley.
|
|
433
|
+
- [Refactoring Guru — Design Patterns](https://refactoring.guru/design-patterns) (comprehensive examples in TypeScript)
|
|
434
|
+
- [Microsoft — Cloud Design Patterns](https://learn.microsoft.com/en-us/azure/architecture/patterns/)
|