-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreational_patterns.cpp
More file actions
485 lines (430 loc) · 19.5 KB
/
Copy pathcreational_patterns.cpp
File metadata and controls
485 lines (430 loc) · 19.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
// =============================================================================
// CREATIONAL PATTERNS - C++ Implementations
// Based on: Design Patterns: Elements of Reusable Object-Oriented Software
// Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (GoF)
//
// Each pattern section contains:
// PROJECT: a real-world mini-project
// WHY: decision process: why this pattern fits
// DECISION: what alternatives were ruled out and why
// CODE: full C++ implementation with demo
// =============================================================================
#include <cassert>
#include <memory>
#include <print>
#include <string>
#include <unordered_map>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
/// ─────────────────────────────────────────────────────────────────────────────
/// PATTERN 1: ABSTRACT FACTORY
/// ─────────────────────────────────────────────────────────────────────────────
///
/// PROJECT: Cross-Platform UI Widget Toolkit
/// A desktop app needs to render Buttons and Checkboxes. It must look native
/// on both Windows and macOS without scattering "if (windows)…else…" checks
/// everywhere. The set of widget *kinds* (Button, Checkbox) is stable, but
/// more look-and-feel themes may be added later.
///
/// WHY ABSTRACT FACTORY?
/// ✓ We have a FAMILY of related products (Button + Checkbox) that must
/// match - we never want a macOS button next to a Windows checkbox.
/// ✓ We want to swap the whole theme by swapping one factory object.
/// ✓ Client code should never call `new WindowsButton` directly.
///
/// ALTERNATIVES RULED OUT:
/// Factory Method alone: only handles one product per creator; can't enforces
/// family consistency across Button + Checkbox
/// together.
/// Direct if/else: spreads platform knowledge everywhere; nightmare
/// when a third theme (Linux GTK) is added.
///
/// KEY TRADE-OFF: Adding a *new kind* of widget (e.g., Slider) requires touching
/// every concrete factory - this is the pattern's known liability.
/// ─────────────────────────────────────────────────────────────────────────────
namespace abstract_factory {
/// Abstract products
struct Button {
virtual void render() const = 0;
virtual ~Button() = default;
};
struct Checkbox {
virtual void render() const = 0;
virtual ~Checkbox() = default;
};
/// Concrete products - Windows family
struct WindowsButton : Button {
void render() const override { std::println("[Windows] Rendering a rectangular button"); }
};
struct WindowsCheckbox : Checkbox {
void render() const override { std::println("[Windows] Rendering a square checkbox"); }
};
/// Concrete products - macOS family
struct MacButton : Button {
void render() const override { std::println("[macOS] Rendering a rounded button"); }
};
struct MacCheckbox : Checkbox {
void render() const override { std::println("[macOS] Rendering a circular checkbox"); }
};
/// Abstract factory: one creation method per product KIND
struct WidgetFactory {
virtual std::unique_ptr<Button> createButton() const = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
virtual ~WidgetFactory() = default;
};
/// Concrete factories: one per FAMILY
struct WindowsFactory : WidgetFactory {
std::unique_ptr<Button> createButton() const override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<Checkbox> createCheckbox() const override {
return std::make_unique<WindowsCheckbox>();
}
};
struct MacFactory : WidgetFactory {
std::unique_ptr<Button> createButton() const override { return std::make_unique<MacButton>(); }
std::unique_ptr<Checkbox> createCheckbox() const override {
return std::make_unique<MacCheckbox>();
}
};
/// Client: works only through the abstract factory; never touches concrete types
void renderUI(const WidgetFactory& factory) {
auto btn = factory.createButton();
auto chk = factory.createCheckbox();
btn->render();
chk->render();
}
void demo() {
std::println("\n Abstract Factory: Cross-Platform UI ");
WindowsFactory winFac;
MacFactory macFac;
std::println("On Windows:");
renderUI(winFac);
std::println("On macOS:");
renderUI(macFac);
}
} // namespace abstract_factory
/// ─────────────────────────────────────────────────────────────────────────────
/// PATTERN 2: BUILDER
/// ─────────────────────────────────────────────────────────────────────────────
///
/// PROJECT: Custom Computer Configurator
/// Users assemble a PC by choosing CPU, RAM, storage, and GPU. Not all
/// fields are required (a server skips GPU; a workstation skips gaming GPU).
/// The final Product is a complex object assembled step-by-step.
///
/// WHY BUILDER?
/// ✓ The product (Computer) has 4+ independent optional parts.
/// ✓ The same Director algorithm produces a GamingPC and a ServerPC just by
/// swapping the ConcreteBuilder - construction process is shared.
/// ✓ A telescoping constructor (Computer(cpu, ram, storage, gpu, cooling…))
/// would be unreadable and fragile.
///
/// ALTERNATIVES RULED OUT:
/// Abstract Factory: creates *families* in one call; can't build one complex
/// object part-by-part.
/// Factory Method: no incremental assembly; just returns one object.
///
/// KEY TRADE-OFF: More classes than a simple constructor, but the Director
/// separates "how to build" from "what is built," which is the payoff.
/// ─────────────────────────────────────────────────────────────────────────────
namespace builder {
struct Computer {
std::string cpu;
std::string ram;
std::string storage;
std::string gpu;
void describe() const {
std::println(
"Computer: CPU={} RAM={} Storage={} GPU={}",
cpu,
ram,
storage,
gpu.empty() ? "None" : gpu
);
}
};
/// Builder interface: declares every possible construction step
struct ComputerBuilder {
virtual void setCPU(const std::string& cpu) = 0;
virtual void setRAM(const std::string& ram) = 0;
virtual void setStorage(const std::string& str) = 0;
virtual void setGPU(const std::string& gpu) = 0;
virtual Computer getResult() = 0;
virtual ~ComputerBuilder() = default;
};
/// ConcreteBuilder: assembles parts into a Computer
struct PCBuilder : ComputerBuilder {
Computer result;
void setCPU(const std::string& cpu) override { result.cpu = cpu; }
void setRAM(const std::string& ram) override { result.ram = ram; }
void setStorage(const std::string& str) override { result.storage = str; }
void setGPU(const std::string& gpu) override { result.gpu = gpu; }
Computer getResult() override { return result; }
};
/// Director: knows the correct order/steps for each configuration
struct Director {
ComputerBuilder* builder = nullptr;
void buildGamingPC() {
builder->setCPU("Intel i9-14900K");
builder->setRAM("64GB DDR5");
builder->setStorage("2TB NVMe SSD");
builder->setGPU("RTX 4090");
}
void buildServer() {
builder->setCPU("AMD EPYC 9654");
builder->setRAM("512GB ECC DDR5");
builder->setStorage("8TB NVMe RAID");
// no GPU - servers rarely need one
}
};
void demo() {
std::println("\n Builder: Custom Computer Configurator ");
PCBuilder bld;
Director dir;
dir.builder = &bld;
dir.buildGamingPC();
bld.getResult().describe();
bld = {}; // reset builder for new configuration
dir.buildServer();
bld.getResult().describe();
}
} // namespace builder
/// ─────────────────────────────────────────────────────────────────────────────
/// PATTERN 3: FACTORY METHOD
/// ─────────────────────────────────────────────────────────────────────────────
///
/// PROJECT: Game Enemy Spawner
/// A game has different levels: ForestLevel spawns Goblins, DungeonLevel
/// spawns Skeletons. The level class orchestrates enemy waves (same logic),
/// but each subclass decides which Enemy to spawn. New levels added without
/// touching wave logic.
///
/// WHY FACTORY METHOD?
/// ✓ The Creator (Level) has an algorithm (spawnWave) that needs an Enemy,
/// but can't predict which concrete Enemy type it will use.
/// ✓ Subclasses (ForestLevel, DungeonLevel) should decide the product -
/// this is the textbook Factory Method scenario.
/// ✓ The wave-spawning logic is shared; only the enemy type varies.
///
/// ALTERNATIVES RULED OUT:
/// Abstract Factory: we have one product (Enemy), not a family; overkill.
/// Prototype: would work, but requires every level to hold a
/// prototype object; Factory Method is simpler here.
/// Direct new: hard-codes the concrete type; levels can't be reused
/// in a different enemy context.
/// ─────────────────────────────────────────────────────────────────────────────
namespace factory_method {
struct Enemy {
virtual void attack() const = 0;
virtual ~Enemy() = default;
};
struct Goblin : Enemy {
void attack() const override { std::println("Goblin attacks with a club!"); }
};
struct Skeleton : Enemy {
void attack() const override { std::println("Skeleton attacks with a sword!"); }
};
struct Dragon : Enemy {
void attack() const override { std::println("Dragon breathes fire!"); }
};
/// Creator: contains the template algorithm; declares the factory method
struct Level {
virtual std::unique_ptr<Enemy> createEnemy() const = 0; // <- factory method
/// Template algorithm that uses the factory method
void spawnWave(int count) const {
std::println("Spawning wave of {} enemies:", count);
for (int idx = 0; idx < count; ++idx) {
auto enemy = createEnemy();
enemy->attack();
}
}
virtual ~Level() = default;
};
/// Concrete creators: override only the factory method
struct ForestLevel : Level {
std::unique_ptr<Enemy> createEnemy() const override { return std::make_unique<Goblin>(); }
};
struct DungeonLevel : Level {
std::unique_ptr<Enemy> createEnemy() const override { return std::make_unique<Skeleton>(); }
};
struct BossLevel : Level {
std::unique_ptr<Enemy> createEnemy() const override { return std::make_unique<Dragon>(); }
};
void demo() {
std::println("\n Factory Method: Game Enemy Spawner ");
ForestLevel forest;
DungeonLevel dungeon;
BossLevel boss;
std::println("[Forest Level]");
forest.spawnWave(2);
std::println("[Dungeon Level]");
dungeon.spawnWave(2);
std::println("[Boss Level]");
boss.spawnWave(1);
}
} // namespace factory_method
/// ─────────────────────────────────────────────────────────────────────────────
/// PATTERN 4: PROTOTYPE
/// ─────────────────────────────────────────────────────────────────────────────
///
/// PROJECT: Magic Spell System (Spell Palette)
/// A game has a palette of preconfigured spells (Fireball lv3, Ice Storm
/// lv5, etc.). When a player casts a spell, the system clones the registered
/// prototype rather than recreating the spell from scratch. Runtime-loaded
/// spell mods can register new prototypes without new C++ subclasses.
///
/// WHY PROTOTYPE?
/// ✓ Spells vary in *state* (level, damage, duration), not just class.
/// Two Fireballs at different levels shouldn't require two subclasses.
/// ✓ Spell prototypes are registered at runtime (from config/mods).
/// ✓ Cloning is cheaper than reconfiguring from scratch for complex objects.
///
/// ALTERNATIVES RULED OUT:
/// Factory Method: would need one Level subclass per spell variant; class
/// explosion (FireballLv1, FireballLv2, FireballLv3…).
/// Abstract Factory: for families, not state-varied singletons.
/// Direct new: requires knowing the concrete class at call site;
/// mods can't register new types at runtime.
/// ─────────────────────────────────────────────────────────────────────────────
namespace prototype {
struct Spell {
std::string name;
int damage;
int duration;
virtual std::unique_ptr<Spell> clone() const = 0;
virtual void describe() const {
std::println("Spell: {} | DMG={} | DUR={}s", name, damage, duration);
}
virtual ~Spell() = default;
};
struct Fireball : Spell {
std::unique_ptr<Spell> clone() const override {
return std::make_unique<Fireball>(*this); // copy constructor
}
};
struct IceStorm : Spell {
std::unique_ptr<Spell> clone() const override { return std::make_unique<IceStorm>(*this); }
};
/// PrototypeManager (registry): maps string keys to prototype instances
struct SpellRegistry {
std::unordered_map<std::string, std::unique_ptr<Spell>> registry;
void registerSpell(const std::string& key, std::unique_ptr<Spell> spell) {
registry[key] = std::move(spell);
}
std::unique_ptr<Spell> cast(const std::string& key) const {
auto itr = registry.find(key);
if (itr == registry.end()) {
std::println(stderr, "Unknown spell: {}", key);
return nullptr;
}
return itr->second->clone(); // clone the prototype
}
};
void demo() {
std::println("\n Prototype: Magic Spell Palette ");
SpellRegistry reg;
// Register prototype spells
auto fbl = std::make_unique<Fireball>();
fbl->name = "Fireball";
fbl->damage = 80;
fbl->duration = 3;
auto iceStm = std::make_unique<IceStorm>();
iceStm->name = "Ice Storm";
iceStm->damage = 60;
iceStm->duration = 7;
reg.registerSpell("fireball", std::move(fbl));
reg.registerSpell("icestorm", std::move(iceStm));
// Cast = clone prototype and use
auto cast1 = reg.cast("fireball");
cast1->describe();
auto cast2 = reg.cast("icestorm");
cast2->describe();
// Modify the clone without affecting the prototype
auto cast3 = reg.cast("fireball");
cast3->damage = 120; // power-up for this cast only
cast3->name = "MEGA Fireball";
cast3->describe();
reg.cast("fireball")->describe(); // original prototype unchanged
}
} // namespace prototype
/// ─────────────────────────────────────────────────────────────────────────────
/// PATTERN 5: SINGLETON
/// ─────────────────────────────────────────────────────────────────────────────
///
/// PROJECT: Application Configuration Manager
/// A desktop app reads its settings (theme, language, max_threads) from a
/// config file once at startup. Many subsystems (UI, networking, logging)
/// need to read these settings. There must be exactly one config object -
/// two instances would risk reading different values or double-parsing the
/// file.
///
/// WHY SINGLETON?
/// ✓ Uniqueness is a *domain requirement*, not a convenience: two config
/// instances parsing the same file would be a bug.
/// ✓ Global accessibility is needed by disparate subsystems.
/// ✓ Lazy initialization defers disk I/O until first use.
///
/// ALTERNATIVES RULED OUT:
/// Global variable: doesn't enforce uniqueness; anyone can create another.
/// Dependency injection of the same object: perfectly valid and more
/// testable, but here uniqueness enforcement justifies the Singleton.
///
/// WARNING APPLIED: Singleton makes unit testing harder. Use it only when
/// uniqueness is a genuine system constraint, not just "convenient."
///
/// THREAD SAFETY: Using C++11 Meyers Singleton - local static initialization
/// is guaranteed thread-safe by the standard.
/// ─────────────────────────────────────────────────────────────────────────────
namespace singleton {
class AppConfig {
public:
/// Meyers Singleton - thread-safe in C++11 and later
static AppConfig& instance() {
static AppConfig inst; // initialized exactly once, lazily
return inst;
}
/// Delete copy/move to prevent accidental duplication
AppConfig(const AppConfig&) = delete;
AppConfig(AppConfig&&) = delete;
AppConfig& operator=(const AppConfig&) = delete;
AppConfig& operator=(AppConfig&&) = delete;
/// Configuration values (in a real app, loaded from a file)
std::string theme = "dark";
std::string language = "en_US";
int maxThreads = 8;
void describe() const {
std::println("Config: theme={} lang={} threads={}", theme, language, maxThreads);
}
private:
AppConfig() {
// Would parse config.json here
std::println("[AppConfig] Loaded from disk (happens exactly once)");
}
};
void demo() {
std::println("\n Singleton: Application Config Manager ");
// Multiple call sites - all get the same instance
AppConfig& cfg1 = AppConfig::instance();
AppConfig& cfg2 = AppConfig::instance();
cfg1.describe();
cfg2.maxThreads = 16; // change via cfg2…
cfg1.describe(); // …visible through cfg1 - same object
assert(&cfg1 == &cfg2);
std::println("cfg1 and cfg2 are the same object: {}", &cfg1 == &cfg2);
}
} // namespace singleton
/// Main function
int main() {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
abstract_factory::demo();
builder::demo();
factory_method::demo();
prototype::demo();
singleton::demo();
return 0;
}